Numble

From Esolang
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.

Commands
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.

Caption text
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()