Numble
Jump to navigation
Jump to search
Numble is an esoteric programming language, made by User:Cobl703, similar to Forte. It is Turing complete.
Commands
Each command in Numble consists of a single byte indicating which command it is, followed by its arguments. Integers are encoded using ZigZag encoding.
| Starting Byte | Arguments | Action |
|---|---|---|
00 |
Integer, Expression | Sets argument 0 to the value of argument 1. |
01 |
Integer | Sets argument 0 to a single byte from the input. If an EOF occurs, argument 0 is set to 256. |
02 |
Expression | Outputs a byte given by the value of argument 0 (mod 256). |
03 |
Expression | Creates a label named using the value of argument 0. If this label already exists, it is overwritten. |
04 |
00, Expression, Expression |
Runs the next command only if the value of argument 1 is less than that of argument 2. |
04 |
01, Expression, Expression |
Runs the next command only if the value of argument 1 is equal to that of argument 2. |
04 |
02, Expression, Expression |
Runs the next command only if the value of argument 1 is less than or equal to that of argument 2. |
04 |
03, Expression, Expression |
Runs the next command only if the value of argument 1 is not equal to that of argument 2. |
05 |
Expression | Goes to the label given by the value of argument 0. If such a label does not exist yet, the rest of the program is searched for a label that does (without running the code in between). If no such label is found, the program ends. |
Expressions
Expressions are similar to commands, with the difference being that they have a value instead of preforming an action. They have starting bytes and arguments like commands.
| Starting Byte | Arguments | Output Value |
|---|---|---|
00 |
Integer | Argument 0 |
01 |
Expression, Expression | The sum of the value of argument 0 and that of argument 1 |
02 |
Expression, Expression | The difference of the value of argument 0 and that of argument 1 |
03 |
Expression, Expression | The product of the value of argument 0 and that of argument 1 |
04 |
Expression, Expression | The floor quotient of the value of argument 0 and that of argument 1 |
Examples
Hello world
02 00 90 01 02 00 CA 01 02 00 D8 01 02 00 D8 01 02 00 DE 01 02 00 40 02 00 EE 01 02 00 DE 01 02 00 E4 01 02 00 D8 01 02 00 C8 01
Truth machine
01 00 03 00 62 02 00 00 05 00 00
Interpreter
import collections.abc
import io
import collections
import sys
import typing
class NumbleProgram:
file: typing.BinaryIO
prgm_output: typing.BinaryIO
prgm_input: typing.BinaryIO
variables: dict[int, int]
labels: dict[int, int]
def __init__(self, file: typing.BinaryIO | str, prgm_output: typing.BinaryIO | str, prgm_input: typing.BinaryIO | str | None = None) -> None:
if isinstance(file, str):
self.file = open(file, "rb")
else:
self.file = file
if isinstance(prgm_output, str):
self.prgm_output = open(prgm_output, "ab")
else:
self.prgm_output = prgm_output
if prgm_input == None:
self.prgm_input = io.BytesIO()
elif isinstance(prgm_input, str):
self.prgm_input = open(prgm_input, "rb")
else:
self.prgm_input = prgm_input
self.variables = {}
self.labels = {}
def read_zigzag(self) -> int:
shift: int = 0
result: int = 0
while True:
try:
byte: int = self.file.read(1)[0]
except IndexError:
raise EOFError(f"Unexpected EOF while reading varint: [EOF]{result:0>{shift}b}")
result |= (byte & 0x7f) << shift
if not byte & 0x80:
break
shift += 7
if result & 1:
return -result >> 1
return result >> 1
def get_var(self, name: int) -> int:
result: int = name
while True:
try:
result = self.variables[result]
except KeyError:
return result
def eval_expression(self) -> int:
operation_stack: list[int] = []
stack: list[int] = []
while True:
try:
operation: int = self.file.read(1)[0]
except IndexError:
raise EOFError("Unexpected EOF while reading expression")
if operation:
operation_stack.append(operation)
else:
stack.append(self.get_var(self.read_zigzag()))
while True:
if not operation_stack:
return stack[-1]
match operation_stack[-1]:
case 1:
# Addition
if len(stack) < 2:
break
operation_stack.pop()
second: int = stack.pop()
first: int = stack.pop()
stack.append(self.get_var(first + second))
case 2:
# Subtraction
if len(stack) < 1:
break
operation_stack.pop()
second: int = stack.pop()
first: int = stack.pop()
stack.append(self.get_var(first - second))
case 3:
# Multiplication
if len(stack) < 2:
break
operation_stack.pop()
second: int = stack.pop()
first: int = stack.pop()
stack.append(self.get_var(first * second))
case 4:
# Division
if len(stack) < 2:
break
operation_stack.pop()
second: int = stack.pop()
first: int = stack.pop()
stack.append(self.get_var(first // second))
case _:
raise SyntaxError(f"Unknown operation: {operation}")
def run_command(self, command: int) -> None:
match command:
case 0:
# Set
name: int = self.read_zigzag()
self.variables[name] = self.eval_expression()
case 1:
# Input
name: int = self.read_zigzag()
try:
self.variables[name] = self.prgm_input.read(1)[0]
except IndexError:
self.variables[name] = 256
case 2:
# Output
self.prgm_output.write(bytes([self.eval_expression() % 256]))
case 3:
# Label
label: int = self.eval_expression()
self.labels[label] = self.file.tell()
case 4:
# If
try:
comparison: int = self.file.read(1)[0]
except IndexError:
raise EOFError("Unexpected EOF while reading command")
match comparison:
case 0:
comparison_function: collections.abc.Callable[[int, int], bool] = int.__lt__
case 1:
comparison_function: collections.abc.Callable[[int, int], bool] = int.__eq__
case 2:
comparison_function: collections.abc.Callable[[int, int], bool] = int.__le__
case 3:
comparison_function: collections.abc.Callable[[int, int], bool] = int.__ne__
case _:
raise SyntaxError(f"Unknown comparison: {comparison}")
exp1: int = self.eval_expression()
exp2: int = self.eval_expression()
if not comparison_function(exp1, exp2):
self.skip_command()
case 5:
# Goto
label: int = self.eval_expression()
try:
self.file.seek(self.labels[label])
except KeyError:
while True:
finished: None | bool = self.skip_command_unless_label(label)
if finished:
self.file.seek(self.labels[label])
return
if finished == None:
return
case _:
raise SyntaxError(f"Unknown command: {command}")
def skip_command(self) -> None:
try:
command: int = self.file.read(1)[0]
except IndexError:
raise EOFError("Unexpected EOF while reading command")
match command:
case 0:
# Set
self.read_zigzag()
self.eval_expression()
case 1:
# Input
self.read_zigzag()
case 2:
# Output
self.eval_expression()
case 3:
# Label
self.eval_expression()
case 4:
# If
try:
self.file.read(1)[0]
except IndexError:
raise EOFError("Unexpected EOF while reading command")
self.eval_expression()
self.eval_expression()
case 5:
# Goto
self.eval_expression()
case _:
raise SyntaxError(f"Unknown command: {command}")
def skip_command_unless_label(self, label) -> None | bool:
try:
command: int = self.file.read(1)[0]
except IndexError:
return
match command:
case 0:
# Set
self.read_zigzag()
self.eval_expression()
case 1:
# Input
self.read_zigzag()
case 2:
# Output
self.eval_expression()
case 3:
# Label
clabel: int = self.eval_expression()
if clabel == label:
self.labels[label] = self.file.tell()
return True
case 4:
# If
try:
self.file.read(1)[0]
except IndexError:
raise EOFError("Unexpected EOF while reading command")
self.eval_expression()
self.eval_expression()
case 5:
# Goto
self.eval_expression()
case _:
raise SyntaxError(f"Unknown command: {command}")
return False
def run(self) -> None:
while True:
try:
command: int = self.file.read(1)[0]
except IndexError:
break
self.run_command(command)
if __name__ == "__main__":
try:
file: str = sys.argv[1]
except IndexError:
file: str = input("Enter filename: ")
np: NumbleProgram = NumbleProgram(file, sys.stdout.buffer, sys.stdin.buffer)
np.run()