Compare commits
19 Commits
e180adea8c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bcbc6c650 | |||
| 5e9c2273a9 | |||
| 976e9dfaa0 | |||
| 89065711ce | |||
| 9ad3bdbc92 | |||
| 176c07151a | |||
| 1ceecbb99e | |||
| 0710369871 | |||
| 5fa47dc377 | |||
| c98f248245 | |||
| 333a1241e8 | |||
| 74494711f6 | |||
| ea8d651171 | |||
| f2336c8463 | |||
| ea55468a10 | |||
| 11f708a9fd | |||
| 937cdfa3ef | |||
| 91f090dc12 | |||
| 3ccf7f845f |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
src/__pycache__
|
||||
test.bf
|
||||
@@ -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"`
|
||||
21
readme.md
21
readme.md
@@ -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!
|
||||
|
||||
## 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.
|
||||
@@ -10,10 +16,21 @@ 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.
|
||||
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
301
src/codegen.py
Normal 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
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")
|
||||
140
src/main.py
140
src/main.py
@@ -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
|
||||
file = open(argv[1]).readlines();
|
||||
bfcode = ""
|
||||
variables = []
|
||||
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)
|
||||
from preprocess import preprocess
|
||||
from error import error
|
||||
from codegen import generateCode
|
||||
|
||||
for i in file:
|
||||
if (i.split(" ")[0] == "create"):
|
||||
if (i[len(i)-1] == "\n"):
|
||||
i = removeEnd(i, 1)
|
||||
bfcode += ">>>>>>>>>>>>[[>>]>>]"
|
||||
|
||||
bytes = int(removeChar(i.split("*", 1)[1].split("=", 1)[0], " "))
|
||||
varbytes.append(bytes)
|
||||
|
||||
a = i.split("=", 1)[1]
|
||||
if (a[0] == " "):
|
||||
a = a.split(" ", 1)[1]
|
||||
|
||||
# Get type
|
||||
if ((a[0] == "\"") & (a[len(a)-1] == "\"")):
|
||||
type = "string"
|
||||
else:
|
||||
type = "undefined"
|
||||
|
||||
if (type == "string"):
|
||||
a = a.split("\"", 1)[1]
|
||||
a = removeEnd(a, 1)
|
||||
for j in range(bytes):
|
||||
bfcode += "+++>"
|
||||
if (j < len(a)):
|
||||
bfcode += "+" * ord(a[j]) + ">"
|
||||
else:
|
||||
bfcode += ">"
|
||||
bfcode += "<<[[<<]<<]<<<<<<<<"
|
||||
|
||||
variables.append(i.split(" ")[1].split("*")[0])
|
||||
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)
|
||||
|
||||
elif (i.split(" ")[0] == "print"):
|
||||
a = i.split(" ", 1)[1]
|
||||
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)
|
||||
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)
|
||||
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
28
syntax.md
Normal 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
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
create str1 *5 = "Hello"
|
||||
create str2 *10 = "Hello!"
|
||||
|
||||
print str1
|
||||
print str2
|
||||
|
||||
set str1 = "World!"
|
||||
print str1
|
||||
7
tests/test.basm
Normal file
7
tests/test.basm
Normal file
@@ -0,0 +1,7 @@
|
||||
string str1 -1 "Hello, world!"
|
||||
int num 100
|
||||
bool cond true
|
||||
|
||||
if cond
|
||||
inc num
|
||||
end if
|
||||
Reference in New Issue
Block a user