Compare commits

..

19 Commits

11 changed files with 430 additions and 131 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
src/__pycache__
test.bf

View File

@@ -1,18 +0,0 @@
## create var *bytes = Value
Creates the variable "var" with a set number of bytes allocated to it, and initialises its value.
**IMPORTANT:** The number of bytes cannot be modified later.
Example: `create var *5 = "Hello"`
## print var
Prints the value of `var` to the console, where `var` is a variable.
Example: `print var`
## set var = Value
Changes the value of an already created value.
Example `set var = "World"`

View File

@@ -2,6 +2,12 @@
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! 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: To run:
- First clone this repository and install python. - First clone this repository and install python.
@@ -10,10 +16,21 @@ git clone https://chookspace.com/DiamondNether90/BrainAssembly
``` ```
- Next, run the following command to compile a .basm file to Brainfuck: - 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 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.) (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. 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.

301
src/codegen.py Normal file
View File

@@ -0,0 +1,301 @@
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) + '>'
for char in range(int(line[2])-len(line[3])):
returnval += '>'
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':
if len(line) != 2:
error(f'Inc command requires 1 argument (got {len(line)-1})')
returnval = '>' * offset + '[>]'
idx = getIndex(line[1])
if variables[idx].type != 2:
error(f'Cannot increment {line[1]}: Invalid type')
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]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]' + '<' * (offset-1)
return returnval
elif keyword == 'if':
if len(line) != 2:
error(f'If command requires 1 argument (got {len(line)-1})')
returnval = '>' * offset + '[>]'
idx = getIndex(line[1])
for var in variables[:idx]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
returnval += '>[<+>->+<]<[>+<-]>>[<<'
for var in reversed(variables[:idx]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]<+>>[>]'
for var in variables[:idx]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
returnval += '>>-]<<'
for var in reversed(variables[:idx]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]<[' + '<' * (offset-2) + '+' + '>' * (offset-2) + '-]' + '<' * (offset-2) + '[-'
return returnval
elif keyword == 'end':
if len(line) != 2:
error(f'End command requires 2 arguments (got {len(line)-1})')
if line[1] == 'if':
return ']'
else:
error(f'Invalid argument for end: {line[1]}')
else:
error(f'Invalid keyword: {line[0]}')

5
src/error.py Normal file
View 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")

View File

@@ -1,106 +1,40 @@
import string # Hopefully this code is actually decent lol
# Note: cells in bf are | 0 | inputs | 0 | variables |
from sys import argv from sys import argv
file = open(argv[1]).readlines(); from preprocess import preprocess
bfcode = "" from error import error
variables = [] from codegen import generateCode
varbytes = []
def removeChar(str, char):
ans = ""
for i in str:
if (i != char):
ans += i
return ans
def removeEnd(string, num):
ans = ""
for i in range(len(string)-1):
ans += string[i]
return ans
def ord2(char):
if (len(char) == 1):
return ord(char)
elif (len(char == 0)):
return(0)
for i in file: if (len(argv) < 3):
if (i.split(" ")[0] == "create"): print(f"Usage: python {argv[0]} file.basm file.bf")
if (i[len(i)-1] == "\n"): quit()
i = removeEnd(i, 1)
bfcode += ">>>>>>>>>>>>[[>>]>>]" # Check if correct argument count
if (len(argv) != 3) | (argv[1][-5:] != ".basm") | (argv[2][-3:] != ".bf"):
bytes = int(removeChar(i.split("*", 1)[1].split("=", 1)[0], " ")) print(f"Usage: python {argv[0]} file.basm file.bf")
varbytes.append(bytes) quit()
a = i.split("=", 1)[1] # Get contents of files
if (a[0] == " "): try:
a = a.split(" ", 1)[1] with open(argv[1], 'r') as file:
content : str = file.read()
# Get type except FileNotFoundError:
if ((a[0] == "\"") & (a[len(a)-1] == "\"")): error(f"File '{argv[1]}' not found.")
type = "string" quit()
else: except IOError:
type = "undefined" error(f"Cannot read file '{argv[1]}'.")
quit()
if (type == "string"):
a = a.split("\"", 1)[1] # Pre-processing of the basm file, from preprocess.py
a = removeEnd(a, 1) code = preprocess(content)
for j in range(bytes):
bfcode += "+++>" offset: int = 5
if (j < len(a)): bfcode: str = '>' * offset + ',[>,]<[<]' + '<' * (offset-1)
bfcode += "+" * ord(a[j]) + ">"
else: open(argv[2], 'w').write('')
bfcode += ">"
bfcode += "<<[[<<]<<]<<<<<<<<" for line in code:
bfcode += generateCode(line, offset)
variables.append(i.split(" ")[1].split("*")[0])
elif (i.split(" ")[0] == "print"): with open(argv[2], 'w') as f:
a = i.split(" ", 1)[1] print(f"{f.write(bfcode)} characters successfully compiled!")
if a[len(a)-1] == "\n":
a = removeEnd(a, 1)
if (a in variables):
bfcode += ">>>>>>>>>>>>"
for j in range(variables.index(a)):
bfcode += "[>>]>>"
bfcode += "[>.>]<<[[<<]<<]<<<<<<<<++++++++++.[-]"
else:
raise NameError(f"Could not find variable {a}")
elif (i.split(" ")[0] == "set"):
a = i.split(" ")[1]
if a[len(a)-1] == "\n":
a = removeEnd(a, 1)
if (a in variables):
bfcode += ">>>>>>>>>>>>"
for j in range(variables.index(a)):
bfcode += "[>>]>>"
for j in range(varbytes[variables.index(a)]):
bfcode += "[-]>[-]>"
for j in range(varbytes[variables.index(a)]):
bfcode += "<<"
# Get type
val = i.split("=", 1)[1].split(" ", 1)[1]
if (val[len(val)-1] == "\n"):
val = removeEnd(val, 1)
if (val[0] == "\"") & (val[len(val)-1] == "\""):
val = val.split("\"", 1)[1]
val = removeEnd(val, 1)
type = "string"
if (type == "string"):
for j in range(varbytes[variables.index(a)]):
bfcode += "+++>"
if (j >= len(val)):
bfcode += ">"
else:
bfcode += "+" * ord(val[j]) + ">"
bfcode += "<<[[<<]<<]<<<<<<<<"
else:
raise NameError(f"Could not find variable {a}")
elif ((i != "\n") & (i != "")):
raise ValueError(f"Invalid Command")
with open(argv[2], "w") as file:
file.write(bfcode)

31
src/preprocess.py Normal file
View 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)
elif token != '':
tokenise[-1].append(token)
token = ''
if token != '':
tokenise[-1].append(token)
token = ''
if tokenise[-1] == []:
tokenise = tokenise[:-1]
return tokenise

28
syntax.md Normal file
View File

@@ -0,0 +1,28 @@
## 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`
Increment an int: `inc name`
If statement:
```py
if bool
# Code here
end if
```
Example program (prints "Hello, World"):
```py
string var 13 "Hello, world!" # Or -1 bytes for a dynamic string
print var
```

View File

@@ -1,8 +0,0 @@
create str1 *5 = "Hello"
create str2 *10 = "Hello!"
print str1
print str2
set str1 = "World!"
print str1

View File

7
tests/test.basm Normal file
View File

@@ -0,0 +1,7 @@
string str1 -1 "Hello, world!"
int num 100
bool cond true
if cond
inc num
end if