commit 9a66e9ab55b5e7504ad910584c33377eb58cd749 Author: DiamondNether90 Date: Thu Oct 30 16:40:41 2025 +1100 Increment variables diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7aee7ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +src/__pycache__ +test.bf \ No newline at end of file diff --git a/docs/syntax.md b/docs/syntax.md new file mode 100644 index 0000000..44df2f4 --- /dev/null +++ b/docs/syntax.md @@ -0,0 +1,19 @@ +## Work in Progress + +To create a comment, begin a line with a hash (#) symbol, like python. + +Create a string: `string name value bytes` + +Create a boolean: `bool name value` + +Create an integer: `int name value` + +**Note**: A string of dynamic length can be created by setting the bytes to -1. + +Print a string: `print name` + +Example program (prints "Hello, World"): +```cpp +string var 13 "Hello, world!" +print var +``` \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..cccf0a8 --- /dev/null +++ b/readme.md @@ -0,0 +1,36 @@ +## Welcome to BrainAssembly! + +BrainAssembly is a language that compiles to Brainfuck. Similar to Assembly languages, it has simple syntax, and is designed to make Brainfuck easier to write code for. Heavily inspired by Ground. Enjoy! + +## Features + +- Simple syntax: The language is designed to be very simple, and a framework for larger projects. This also means that the syntax is quite easy to learn. + +**NOTE:** Due to the current lack of optimisation in the compiled code, we recommend an optimising intepreter such as [this one](https://copy.sh/brainfuck). + +To run: + +- First clone this repository and install python. +``` +git clone https://chookspace.com/DiamondNether90/BrainAssembly +``` + +- Next, run the following command to compile a .basm file to Brainfuck: +```bash +python3 src/main.py path/to/file.basm path/to/file.bf +``` + +(Replace path/to/file.basm with the actual path to your desired .basm file, and replace path/to/file.bf with where you want the compiled file to be created.) + +This will create or replace the contents of path/to/file.bf with the compiled BrainAssembly code. + +## Test the compiler with our in-built test programs! + +You can access our test programs in the test folder. + +Example: +```bash +python src/main.py tests/create.basm test.bf +``` + +This will create a Brainfuck file named test.bf that contains the compiled code of the 'create' test program. \ No newline at end of file diff --git a/src/codegen.py b/src/codegen.py new file mode 100644 index 0000000..5b858a2 --- /dev/null +++ b/src/codegen.py @@ -0,0 +1,242 @@ +from error import error, warn +import math as m + +class Variable: + def __init__(self, name: str, bytes: int, type: int): + self.name: str = name + self.bytes: int = bytes + self.type: int = type + def __repr__(self) -> str: + return f"[{self.name}, {self.bytes}, {self.type}]" + +variables: list[Variable] = [] +bytesum: int = 0 + +bool = { + "true": 1, + "false": 0, +} + +def getIndex(name: str) -> int: + varnames = [] + for var in variables: + varnames.append(var.name) + try: + return varnames.index(name) + except IndexError: + return -1 + +def generateCode(line: list[str], offset: int) -> str: + keyword = line[0] + if keyword == 'bool': + if len(line) != 3: + error(f'Bool command requires 2 arguments (got {len(line)-1})') + variables.append(Variable(line[1], 1, 0)) + if line[2] == 'true': + returnval = '>' * offset + '[>]' + for var in variables[:-1]: + if var.bytes >= 0: + returnval += '>' * (var.bytes+1) + elif var.bytes == -1: + returnval += '>[>]' + else: + error() + returnval += '>+<' + for var in reversed(variables[:-1]): + if var.bytes >= 0: + returnval += '<' * (var.bytes+1) + elif var.bytes == -1: + returnval += '<[<]' + else: + error() + returnval += '<[<]' + '<' * (offset-1) + return returnval + elif line[2] == 'false': + return '' + else: + error(f"Invalid bool: {line[2]}") + elif keyword == 'string': + if len(line) != 4: + error(f'String command requires 3 arguments (got {len(line)-1})') + try: + variables.append(Variable(line[1], int(line[2]), 1)) + except ValueError: + error(f'Unexpected bytes for string: {line[2]}') + returnval = '>' * offset + '[>]' + for var in variables[:-1]: + if var.bytes >= 0: + returnval += '>' * (var.bytes + 1) + elif var.bytes == -1: + returnval += '>[>]' + else: + error('Cannot have negative byte count (excluding -1)') + + returnval += '>' + # Normal string + if line[3][0] == line[3][-1] == '"': + line[3] = line[3][1:-1] + if (len(line[3]) > int(line[2])) & (line[2] != '-1'): + line[3] = line[3][:(int(line[2])-len(line[3]))] + for char in line[3]: + returnval += '+' * ord(char) + '>' + else: + error(f'Invalid string: {line[3]}') + + # Return to start + for var in reversed(variables): + if var.bytes >= 0: + returnval += '<' * (var.bytes + 1) + elif var.bytes == -1: + returnval += '<[<]' + else: + error() + returnval += '<[<]' + '<' * (offset-1) + + return returnval + elif keyword == 'int': + if len(line) != 3: + error(f'Int requires 2 arguments (got {len(line)-1})') + variables.append(Variable(line[1], 4, 2)) + returnval: str = '>' * offset + '[>]' + for var in variables[:-1]: + if var.bytes >= 0: + returnval += '>' * (var.bytes+1) + elif var.bytes == -1: + returnval += '>[>]' + else: + error() + returnval += '>' + try: + input = int(line[2]) + if abs(input) > 4294967295: + warn(f'Input {line[2]} is too large for int. Overflowing...') + input %= 4294967296 + except ValueError: + error(f'Invalid integer: {line[2]}') + + values: list[int] = [] + for i in reversed(range(4)): + values.append(m.floor(input/(256**i))) + input -= values[-1] * (256**i) + for num in values: + if num < 128: + returnval += '+' * num + '>' + else: + returnval += '-' * (256-num) + '>' + returnval += '<<<<<' + for var in reversed(variables[:-1]): + if var.bytes >= 0: + returnval += '<' * (var.bytes + 1) + elif var.bytes == -1: + returnval += '<[<]' + else: + error() + returnval += '<[<]' + '<' * (offset-1) + + return returnval + elif keyword == 'print': + if len(line) != 2: + error(f'Print command requires 1 argument (got {len(line)-1})') + + returnval = '>' * offset + '[>]' + + idx = getIndex(line[1]) + if idx != -1: + for var in variables[:idx]: + if var.bytes >= 0: + returnval += '>' * (var.bytes+1) + elif var.bytes == -1: + returnval += '>[>]' + else: + error() + + var = variables[idx] + if var.type != 1: + error(f'Can\'t print {var.name}: Invalid type') + if var.bytes >= 0: + returnval += '>' + '.>' * var.bytes + '<' * (var.bytes+1) + elif var.bytes == -1: + returnval += '>[.>]' + '<[<]' + else: + error() + + for var in reversed(variables[:idx]): + if var.bytes >= 0: + returnval += '<' * (var.bytes+1) + elif var.bytes == -1: + returnval += '<[<]' + else: + error() + returnval += '<[<]' + '<' * (offset-1) + else: + error(f'{line[1]} is not a variable') + + return returnval + elif keyword == 'inc': + warn('Increment not correctly implemented yet, do not use this command.') + returnval = '>' * offset + '[>]' + idx = getIndex(line[1]) + for var in variables[:idx]: + if var.bytes > 0: + returnval += '>' * (var.bytes + 1) + elif var.bytes == -1: + returnval += '>[>]' + + # Increment logic + ''' + The logic in pseudo-python is like this: + + byte[3]++ + carry = 1 + if byte[3] != 0: + carry = 0 + if carry == 1: + byte[2]++ + if byte[2] != 0: + carry = 0 + if carry == 1: + byte[1]++ + if byte[1] != 0: + carry = 0 + if carry == 1: + byte[0]++ + carry = 0 + ''' + # Carry will be stored to the immediate right of cells + # byte[3]++; carry = 1 + returnval += '>>>>+>+' + # If byte[3] != 0: carry = 0 + returnval += '<[>-<[<<<<+>>>>-]]<<<<[>>>>+<<<<-]>>>>' + # If carry == 1 + returnval += '>[' + # byte[2]++ + returnval += '<<+' + # if byte[2] != 0: carry = 0 + returnval += '[>>-<<[<<<+>>>-]]' + returnval += '<<<[>>>+<<<-]>>>' + # if carry == 1 + returnval += '>>[' + # byte[1]++ + returnval += '<<<+' + # if byte[1] != 0: carry = 0 + returnval += '[>>>-<<<[<<+>>-]]<<[>>+<<-]>>' + # if carry == 1 + returnval += '>>>[' + # byte[0]++; carry = 0 + returnval += '<<<<+>>>>-]]]' + + # Return to start + returnval += '<<<<<' + for var in reversed(variables[:idx]): + print(var.bytes) + if var.bytes >= 0: + returnval += '<' * (var.bytes+1) + elif var.bytes == -1: + returnval += '<[<]' + else: + error() + + returnval += '<[<]' + '<' * (offset-1) + return returnval + else: + error(f'Invalid keyword: {line[0]}') \ No newline at end of file diff --git a/src/error.py b/src/error.py new file mode 100644 index 0000000..fc1d970 --- /dev/null +++ b/src/error.py @@ -0,0 +1,5 @@ +def error(message: str = "This is a bugged error message. Please report this issue."): + exit(f"\033[91mError: \033[0m{message}") + +def warn(message: str): + print(f"\033[33mWarning: {message}\033[0m") \ No newline at end of file diff --git a/src/eval.py b/src/eval.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..f48931d --- /dev/null +++ b/src/main.py @@ -0,0 +1,40 @@ +# Hopefully this code is actually decent lol +# Note: cells in bf are | 0 | inputs | 0 | variables | +from sys import argv +from preprocess import preprocess +from error import error +from codegen import generateCode + +if (len(argv) < 3): + print(f"Usage: python {argv[0]} file.basm file.bf") + quit() + +# Check if correct argument count +if (len(argv) != 3) | (argv[1][-5:] != ".basm") | (argv[2][-3:] != ".bf"): + print(f"Usage: python {argv[0]} file.basm file.bf") + quit() + +# Get contents of files +try: + with open(argv[1], 'r') as file: + content : str = file.read() +except FileNotFoundError: + error(f"File '{argv[1]}' not found.") + quit() +except IOError: + error(f"Cannot read file '{argv[1]}'.") + quit() + +# Pre-processing of the basm file, from preprocess.py +code = preprocess(content) + +offset: int = 5 +bfcode: str = '>' * offset + ',[>,]<[<]' + '<' * (offset-1) + +open(argv[2], 'w').write('') + +for line in code: + bfcode += generateCode(line, offset) + +with open(argv[2], 'w') as f: + print(f"{f.write(bfcode)} characters successfully compiled!") \ No newline at end of file diff --git a/src/preprocess.py b/src/preprocess.py new file mode 100644 index 0000000..5d493cd --- /dev/null +++ b/src/preprocess.py @@ -0,0 +1,31 @@ +def preprocess(code: str) -> list[list[str]]: + token : str = '' + tokenise : list[list[str]] = [[]] + isString : bool = False + isComment : bool = False + for i in code: + if i == '\n': + isComment = False + if token != '': + tokenise[-1].append(token) + if tokenise[-1]: + tokenise.append([]) + token = '' + elif isComment: + pass + elif i == '#': + isComment = True + elif (i != ' ') | isString: + token += i + if (i == '\"'): + isString = not(isString) + else: + tokenise[-1].append(token) + token = '' + + if token != '': + tokenise[-1].append(token) + token = '' + if tokenise[-1] == []: + tokenise = tokenise[:-1] + return tokenise \ No newline at end of file diff --git a/tests/create.basm b/tests/create.basm new file mode 100644 index 0000000..cd900a7 --- /dev/null +++ b/tests/create.basm @@ -0,0 +1,5 @@ +string str1 13 "Hello, world!" +string str2 -1 "Testing dynamic string!" +int var -1 +inc var +inc var \ No newline at end of file