✨
This commit is contained in:
39
cent
Executable file
39
cent
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#! /home/nikolaj/.pyenv/shims/python
|
||||||
|
"""
|
||||||
|
Usage:
|
||||||
|
cent (-h|--help)
|
||||||
|
cent -i FILE
|
||||||
|
cent -c FILE
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help Print this help screen
|
||||||
|
-i Run the interpreter
|
||||||
|
-c Run the compiler
|
||||||
|
FILE The file to compile/interpret
|
||||||
|
"""
|
||||||
|
from docopt import docopt
|
||||||
|
|
||||||
|
from centvrion.lexer import Lexer
|
||||||
|
from centvrion.parser import Parser
|
||||||
|
from centvrion.ast_nodes import Program
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = docopt(__doc__)
|
||||||
|
file_path = args["FILE"]
|
||||||
|
with open(file_path, "r", encoding="utf-8") as file_pointer:
|
||||||
|
program_text = file_pointer.read() + "\n"
|
||||||
|
|
||||||
|
lexer = Lexer().get_lexer()
|
||||||
|
parser = Parser()
|
||||||
|
|
||||||
|
tokens = lexer.lex(program_text)
|
||||||
|
|
||||||
|
program = parser.parse(tokens)
|
||||||
|
|
||||||
|
if isinstance(program, Program):
|
||||||
|
program.eval()
|
||||||
|
else:
|
||||||
|
raise Exception("Output not of type 'Program'", type(program))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
|
import re
|
||||||
import random
|
import random
|
||||||
import roman
|
|
||||||
|
|
||||||
from rply.token import BaseBox as BB
|
from rply.token import BaseBox
|
||||||
|
|
||||||
NUMERALS = [
|
NUMERALS = {
|
||||||
("I", 1),
|
"I": 1,
|
||||||
("V", 5),
|
"IV": 4,
|
||||||
("X", 10),
|
"V": 5,
|
||||||
("L", 50),
|
"IX": 9,
|
||||||
("C", 100),
|
"X": 10,
|
||||||
("D", 500),
|
"XL": 40,
|
||||||
("M", 1000)
|
"L": 50,
|
||||||
]
|
"XC": 90,
|
||||||
|
"C": 100,
|
||||||
|
"CD": 400,
|
||||||
|
"D": 500,
|
||||||
|
"CM": 900,
|
||||||
|
"M": 1000
|
||||||
|
}
|
||||||
|
|
||||||
def rep_join(l):
|
def rep_join(l):
|
||||||
format_string = ',\n'.join(
|
format_string = ',\n'.join(
|
||||||
@@ -23,24 +29,85 @@ def rep_join(l):
|
|||||||
|
|
||||||
return format_string
|
return format_string
|
||||||
|
|
||||||
# TODO: Magnum
|
def single_num_to_int(i, m):
|
||||||
def num_to_int(n):
|
if i[-1] == "_":
|
||||||
return roman.fromRoman(n)
|
if not m:
|
||||||
|
raise Exception(
|
||||||
|
"Cannot calculate numbers above 3999 without 'MAGNVM' module"
|
||||||
|
)
|
||||||
|
|
||||||
def make_string(n):
|
if i[0] != "I":
|
||||||
|
raise Exception(
|
||||||
|
"Cannot use 'I' with thousands operator, use 'M' instead"
|
||||||
|
)
|
||||||
|
|
||||||
|
return 1000 * single_num_to_int(i[:-1], m)
|
||||||
|
else:
|
||||||
|
return NUMERALS[i]
|
||||||
|
|
||||||
|
def num_to_int(n, m):
|
||||||
|
chars = re.findall(r"[IVXLCDM]_*", n)
|
||||||
|
nums = [single_num_to_int(i, m) for i in chars]
|
||||||
|
new_nums = nums.copy()
|
||||||
|
for x, num in enumerate(nums[:-3]):
|
||||||
|
if all(num == nums[x+i] for i in range(0,3)):
|
||||||
|
raise Exception(n, "is not a valid roman numeral")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
for x, num in enumerate(nums[:-1]):
|
||||||
|
if num < nums[x+1]:
|
||||||
|
if (not nums[x+1] % 5) and (num in [nums[x+1]/5, nums[x+1]/10]):
|
||||||
|
new_nums[x] = nums[x+1] - num
|
||||||
|
new_nums[x+1] = 0
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise Exception(n, "is not a valid roman numeral")
|
||||||
|
|
||||||
|
if new_nums != nums:
|
||||||
|
nums = [i for i in new_nums if i != 0]
|
||||||
|
new_nums = nums
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if nums != sorted(nums)[::-1]:
|
||||||
|
raise Exception(n, "is not a valid roman numeral")
|
||||||
|
|
||||||
|
return sum(nums)
|
||||||
|
|
||||||
|
def int_to_num(n, m):
|
||||||
|
if n > 3999:
|
||||||
|
if not m:
|
||||||
|
raise Exception(
|
||||||
|
"Cannot display numbers above 3999 without 'MAGNVM' module"
|
||||||
|
)
|
||||||
|
thousands_chars = re.findall(r"[IVXLCDM]_*", int_to_num(n//1000, m))
|
||||||
|
thousands = ''.join([
|
||||||
|
"M" if i == "I" else i+"_"
|
||||||
|
for i in thousands_chars
|
||||||
|
])
|
||||||
|
|
||||||
|
return thousands + int_to_num(n % 1000, m)
|
||||||
|
else:
|
||||||
|
nums = []
|
||||||
|
while n > 0:
|
||||||
|
for num, i in list(NUMERALS.items())[::-1]:
|
||||||
|
if n >= i:
|
||||||
|
nums.append(num)
|
||||||
|
n -= i
|
||||||
|
break
|
||||||
|
|
||||||
|
return ''.join(nums)
|
||||||
|
|
||||||
|
def make_string(n, m):
|
||||||
if isinstance(n, str):
|
if isinstance(n, str):
|
||||||
return n
|
return n
|
||||||
elif isinstance(n, int):
|
elif isinstance(n, int):
|
||||||
return roman.toRoman(n)
|
return int_to_num(n, m)
|
||||||
elif isinstance(n, list):
|
elif isinstance(n, list):
|
||||||
return f"[{' '.join([make_string(i) for i in n])}]"
|
return f"[{' '.join([make_string(i, m) for i in n])}]"
|
||||||
else:
|
else:
|
||||||
raise Exception(n)
|
raise Exception(n)
|
||||||
|
|
||||||
class BaseBox(BB):
|
|
||||||
def eval(self, vtable, ftable, modules):
|
|
||||||
return None
|
|
||||||
|
|
||||||
class ExpressionStatement(BaseBox):
|
class ExpressionStatement(BaseBox):
|
||||||
def __init__(self, expression) -> None:
|
def __init__(self, expression) -> None:
|
||||||
self.expression = expression
|
self.expression = expression
|
||||||
@@ -94,8 +161,8 @@ class Numeral(BaseBox):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Numeral({self.value})"
|
return f"Numeral({self.value})"
|
||||||
|
|
||||||
def eval(self, *_):
|
def eval(self, *args):
|
||||||
return num_to_int(self.value)
|
return num_to_int(self.value, "MAGNVM" in args[2])
|
||||||
|
|
||||||
class Bool(BaseBox):
|
class Bool(BaseBox):
|
||||||
def __init__(self, value) -> None:
|
def __init__(self, value) -> None:
|
||||||
@@ -354,15 +421,20 @@ class BuiltIn(BaseBox):
|
|||||||
|
|
||||||
match self.builtin:
|
match self.builtin:
|
||||||
case "AVDI_NVMERVS":
|
case "AVDI_NVMERVS":
|
||||||
return num_to_int(input())
|
return num_to_int(input(), "MAGNVM" in modules)
|
||||||
case "DICE":
|
case "DICE":
|
||||||
print(' '.join(make_string(i) for i in parameters))
|
print(' '.join(
|
||||||
|
make_string(i, "MAGNVM" in modules) for i in parameters)
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
case "ERVMPE":
|
case "ERVMPE":
|
||||||
vtable["ERVMPE"] = True
|
vtable["ERVMPE"] = True
|
||||||
return None
|
return None
|
||||||
case "FORTIS_NVMERVS":
|
case "FORTIS_NVMERVS":
|
||||||
# TODO: Fors
|
if "FORS" not in modules:
|
||||||
|
raise Exception(
|
||||||
|
"Cannot use 'FORTIS_NVMERVS' without module 'FORS'"
|
||||||
|
)
|
||||||
return random.randint(parameters[0], parameters[1])
|
return random.randint(parameters[0], parameters[1])
|
||||||
case _:
|
case _:
|
||||||
raise Exception(self.builtin)
|
raise Exception(self.builtin)
|
||||||
|
|||||||
@@ -17,20 +17,28 @@ class Parser():
|
|||||||
)
|
)
|
||||||
|
|
||||||
def parse(self, tokens_input) -> ast_nodes.BaseBox:
|
def parse(self, tokens_input) -> ast_nodes.BaseBox:
|
||||||
@self.pg.production('program : opt_newline module_calls statements')
|
@self.pg.production('program : opt_newline opt_module_calls opt_newline opt_statements opt_newline')
|
||||||
def program(tokens):
|
def program(tokens):
|
||||||
return ast_nodes.Program(tokens[-2], tokens[-1])
|
return ast_nodes.Program(tokens[1], tokens[3])
|
||||||
|
|
||||||
@self.pg.production('opt_newline : ')
|
@self.pg.production('opt_newline : ')
|
||||||
@self.pg.production('opt_newline : NEWLINE')
|
@self.pg.production('opt_newline : NEWLINE')
|
||||||
def opt_newline(_):
|
def opt_newline(_):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@self.pg.production('module_calls : ')
|
@self.pg.production('opt_module_calls : ')
|
||||||
|
@self.pg.production('opt_module_calls : module_calls')
|
||||||
|
def opt_module_calls(calls):
|
||||||
|
if len(calls) == 0:
|
||||||
|
return calls
|
||||||
|
else:
|
||||||
|
return calls[0]
|
||||||
|
|
||||||
|
@self.pg.production('module_calls : module_call NEWLINE ')
|
||||||
@self.pg.production('module_calls : module_call NEWLINE module_calls')
|
@self.pg.production('module_calls : module_call NEWLINE module_calls')
|
||||||
def module_calls(calls):
|
def module_calls(calls):
|
||||||
if len(calls) == 0:
|
if len(calls) == 2:
|
||||||
return []
|
return [calls[0]]
|
||||||
else:
|
else:
|
||||||
return [calls[0]] + calls[2]
|
return [calls[0]] + calls[2]
|
||||||
|
|
||||||
@@ -38,11 +46,20 @@ class Parser():
|
|||||||
def module_call(tokens):
|
def module_call(tokens):
|
||||||
return ast_nodes.ModuleCall(tokens[1].value)
|
return ast_nodes.ModuleCall(tokens[1].value)
|
||||||
|
|
||||||
@self.pg.production('statements : ')
|
|
||||||
|
@self.pg.production('opt_statements : ')
|
||||||
|
@self.pg.production('opt_statements : statements')
|
||||||
|
def opt_statements(calls):
|
||||||
|
if len(calls) == 0:
|
||||||
|
return calls
|
||||||
|
else:
|
||||||
|
return calls[0]
|
||||||
|
|
||||||
|
@self.pg.production('statements : statement NEWLINE ')
|
||||||
@self.pg.production('statements : statement NEWLINE statements')
|
@self.pg.production('statements : statement NEWLINE statements')
|
||||||
def statements(calls):
|
def statements(calls):
|
||||||
if len(calls) == 0:
|
if len(calls) == 2:
|
||||||
return []
|
return [calls[0]]
|
||||||
else:
|
else:
|
||||||
return [calls[0]] + calls[2]
|
return [calls[0]] + calls[2]
|
||||||
|
|
||||||
@@ -74,7 +91,7 @@ class Parser():
|
|||||||
def expression_id(tokens):
|
def expression_id(tokens):
|
||||||
return tokens[0]
|
return tokens[0]
|
||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_VT SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL')
|
@self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_VT SYMBOL_LCURL opt_newline opt_statements opt_newline SYMBOL_RCURL')
|
||||||
def defini(tokens):
|
def defini(tokens):
|
||||||
return ast_nodes.Defini(tokens[1], tokens[2], tokens[6])
|
return ast_nodes.Defini(tokens[1], tokens[2], tokens[6])
|
||||||
|
|
||||||
@@ -135,19 +152,19 @@ class Parser():
|
|||||||
def erumpe(_):
|
def erumpe(_):
|
||||||
return ast_nodes.Erumpe()
|
return ast_nodes.Erumpe()
|
||||||
|
|
||||||
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL opt_newline aluid_statement')
|
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL opt_newline opt_statements opt_newline SYMBOL_RCURL opt_newline aluid_statement')
|
||||||
def si(tokens):
|
def si(tokens):
|
||||||
return ast_nodes.SiStatement(tokens[1], tokens[5], tokens[9])
|
return ast_nodes.SiStatement(tokens[1], tokens[5], tokens[9])
|
||||||
|
|
||||||
@self.pg.production('dum_statement : KEYWORD_DVM expression KEYWORD_FACE SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL')
|
@self.pg.production('dum_statement : KEYWORD_DVM expression KEYWORD_FACE SYMBOL_LCURL opt_newline opt_statements opt_newline SYMBOL_RCURL')
|
||||||
def dum(tokens):
|
def dum(tokens):
|
||||||
return ast_nodes.DumStatement(tokens[1], tokens[5])
|
return ast_nodes.DumStatement(tokens[1], tokens[5])
|
||||||
|
|
||||||
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL')
|
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL opt_newline opt_statements opt_newline SYMBOL_RCURL')
|
||||||
def per(tokens):
|
def per(tokens):
|
||||||
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[7])
|
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[7])
|
||||||
|
|
||||||
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL')
|
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL opt_newline opt_statements opt_newline SYMBOL_RCURL')
|
||||||
def donicum(tokens):
|
def donicum(tokens):
|
||||||
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
||||||
return ast_nodes.PerStatement(range_array, tokens[1], tokens[9])
|
return ast_nodes.PerStatement(range_array, tokens[1], tokens[9])
|
||||||
@@ -160,7 +177,7 @@ class Parser():
|
|||||||
def aluid_si(tokens):
|
def aluid_si(tokens):
|
||||||
return [tokens[1]]
|
return [tokens[1]]
|
||||||
|
|
||||||
@self.pg.production('aluid_statement : KEYWORD_ALVID SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL aluid_statement')
|
@self.pg.production('aluid_statement : KEYWORD_ALVID SYMBOL_LCURL opt_newline opt_statements opt_newline SYMBOL_RCURL aluid_statement')
|
||||||
def aluid(tokens):
|
def aluid(tokens):
|
||||||
return tokens[3]
|
return tokens[3]
|
||||||
|
|
||||||
@@ -176,9 +193,9 @@ class Parser():
|
|||||||
def range_array(tokens):
|
def range_array(tokens):
|
||||||
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
|
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
|
||||||
|
|
||||||
@self.pg.error
|
# @self.pg.error
|
||||||
def error_handle(token):
|
# def error_handle(token):
|
||||||
raise ValueError(token)
|
# raise ValueError(token)
|
||||||
|
|
||||||
parser = self.pg.build()
|
parser = self.pg.build()
|
||||||
return parser.parse(tokens_input)
|
return parser.parse(tokens_input)
|
||||||
|
|||||||
37
main.py
37
main.py
@@ -1,37 +0,0 @@
|
|||||||
from centvrion.lexer import Lexer
|
|
||||||
from centvrion.parser import Parser
|
|
||||||
from centvrion.ast_nodes import Program
|
|
||||||
|
|
||||||
text_input = """
|
|
||||||
CVM FORS
|
|
||||||
|
|
||||||
DESIGNA correct VT FORTIS_NVMERVS I CXXVIII
|
|
||||||
DESIGNA gvess VT NVLLVS
|
|
||||||
|
|
||||||
DVM FALSITAS FACE {
|
|
||||||
DESIGNA gvess VT AVDI_NVMERVS
|
|
||||||
SI gvess MINVS correct TVNC {
|
|
||||||
DICE "Too low!"
|
|
||||||
} ALVID SI gvess PLVS correct TVNC {
|
|
||||||
DICE "Too high!"
|
|
||||||
} ALVID {
|
|
||||||
ERVMPE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DICE "You guessed correctly!"
|
|
||||||
"""
|
|
||||||
|
|
||||||
lexer = Lexer().get_lexer()
|
|
||||||
parser = Parser()
|
|
||||||
|
|
||||||
tokens = lexer.lex(text_input)
|
|
||||||
#for token in tokens:
|
|
||||||
# print(token)
|
|
||||||
|
|
||||||
program = parser.parse(tokens)
|
|
||||||
#print(x)
|
|
||||||
if isinstance(program, Program):
|
|
||||||
program.eval()
|
|
||||||
else:
|
|
||||||
raise Exception("Output not of type 'Program'", type(program))
|
|
||||||
11
tests/fib.cent
Normal file
11
tests/fib.cent
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
DEFINI fib x VT {
|
||||||
|
SI x EST NVLLVS TVNC {
|
||||||
|
REDI NVLLVS
|
||||||
|
} ALVID SI x EST I TVNC {
|
||||||
|
REDI I
|
||||||
|
} ALVID {
|
||||||
|
REDI ((INVOCA fib (x-II)) + (INVOCA fib (x-I)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DICE INVOCA fib (AVDI_NVMERVS)
|
||||||
Reference in New Issue
Block a user