Increment variables
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| src/__pycache__ | ||||
| test.bf | ||||
							
								
								
									
										19
									
								
								docs/syntax.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs/syntax.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| ``` | ||||
							
								
								
									
										36
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										242
									
								
								src/codegen.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								src/codegen.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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]}') | ||||
							
								
								
									
										5
									
								
								src/error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/error.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
							
								
								
									
										0
									
								
								src/eval.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/eval.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										40
									
								
								src/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/main.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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!") | ||||
							
								
								
									
										31
									
								
								src/preprocess.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/preprocess.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										5
									
								
								tests/create.basm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/create.basm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| string str1 13 "Hello, world!" | ||||
| string str2 -1 "Testing dynamic string!" | ||||
| int var -1 | ||||
| inc var | ||||
| inc var | ||||
		Reference in New Issue
	
	Block a user