From 10aafda84c92a8c44ab28a8a33fd439a53504087 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Tue, 7 Jun 2022 21:22:24 +0200 Subject: [PATCH] :sparkles: --- .vscode/settings.json | 3 + README.md | 272 ++++++++++++++++++++++++++ __pycache__/ast.cpython-310.pyc | Bin 0 -> 135 bytes __pycache__/ast_nodes.cpython-310.pyc | Bin 0 -> 8447 bytes __pycache__/lexer.cpython-310.pyc | Bin 0 -> 1907 bytes __pycache__/parser.cpython-310.pyc | Bin 0 -> 7488 bytes ast_nodes.py | 181 +++++++++++++++++ lexer.py | 86 ++++++++ main.py | 31 +++ parser.py | 153 +++++++++++++++ 10 files changed, 726 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 __pycache__/ast.cpython-310.pyc create mode 100644 __pycache__/ast_nodes.cpython-310.pyc create mode 100644 __pycache__/lexer.cpython-310.pyc create mode 100644 __pycache__/parser.cpython-310.pyc create mode 100644 ast_nodes.py create mode 100644 lexer.py create mode 100644 main.py create mode 100644 parser.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a7d0fc7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "esbonio.sphinx.confDir": "" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ab536a --- /dev/null +++ b/README.md @@ -0,0 +1,272 @@ +# About +`CENTURION` is the programming language for the modern roman. + +# Documentation + +## Example code +### Hello World +``` +DESIGNA x UT "Hello World!" +DICE x +``` + +### Recursive Fibonacci number function + +``` +DEFINI fib x UT { + SI x EST NULLUS TUNC { + REDI NULLUS + } ALUID SI x EST I TUNC { + REDI I + } ALUID { + REDI (INVOCA fib (x-II) + INVOCA fib (x-I)) + } +} +``` + +### Number guessing game + +``` +VOCA FORS + +DESIGNA correct UT FORTIS_NUMERUS I C +DESIGNA guess UT NULLUS + +DUM FALSITAS FACE { + DESIGNA guess UT AUDI_NUMERUS + SI guess MINUS correct TUNC { + DICE "Too low!" + } ALUID SI guess PLUS correct TUNC { + DICE "Too high!" + } ALUID { + ERUMPE + } +} + +DICE "You guessed correctly!" +``` + +## Variables +Variables are set with the `DESIGNA` and `UT` keywords. Type is inferred. + +``` +DESIGNA x UT XXVI +``` + +Variable can consist of lower-case letters, numbers, as well as `_`. + +## Data types +### NULLUS +`NULLUS` is a special kind of data type in `CENTURION`, similar to the `null` value in many other languages. `NULLUS` can be 0 if evaluated as an int or float, or an empty string if evaluated as a string. `NULLUS` cannot be evaluated as a boolean. + +### Strings + +Strings are written as text in quotes (`'` or `"`). + +``` +DESIGNA x UT "this is a string" +``` + +### Integers +Integers must be written in roman numerals using the following symbols: + +|Symbol|Value| +|------|-----| +|`I`|1| +|`V`|5| +|`X`|10| +|`L`|50| +|`C`|100| +|`D`|500| +|`M`|1000| + +Each of the symbols written by themself is equal to the value of the symbol. Different symbols written from largest to smallest are equal to the sum of the symbols. Two to three of the same symbol written consecutively is equal to the sum of those symbols (only true for `I`s, `X`s, `C`s or `M`s ). A single `I` written before a `V` or `X` is equal to 1 less than the value of the second symbol. Similarly, an `X` written before a `L` or `C` is 10 less than the second symbol, and a `C` written before a `D` or `M` is 100 less than the second symbol. + +Because of the restrictions of roman numerals, numbers above 3.999 are impossible to write in the base `CENTURION` syntax. If numbers of that size are required, see the `MAGNUM` module. + +The number 0 can be expressed with the keyword `NULLUS`. + +#### Negative numbers +Negative numbers can be expressed as `NULLUS` minus the value. For an explicit definition of negative numbers, see the `SUBNULLA` module. + +### Floats +The base `CENTURION` syntax does not allow for floats. However, the `FRACTIO` module adds a syntax for fractions. + +### Booleans +Booleans are denoted with the keywords `VERITAS` for true and `FALSITAS` for false. + +### Arrays +Arrays are defined using square brackets (`[]`). + +## Conditionals +### SI/TUNC +If-then statements are denoted with the keywords `SI` (if) and `TUNC` (then). Thus, the code + +``` +DESIGNA x UT VERITAS +SI x TUNC { + DICE I + REDI NULLLUS +} + +DICE NULLUS + +> I +``` + +Will return `I` (1), as the conditional evaluates `x` to be true. + +### Boolean expressions +In conditionals, `EST` functions as an equality evaluation, and `MINUS` (<) and `PLUS` (>) function as inequality evaluation. + +### ALUID + +When using `SI`/`TUNC` statements, you can also use `ALUID` as an "else". + +``` +DESIGNA x UT VERITAS +SI x TUNC { + DICE I +} ALUID { + DICE NULLUS +} + +> I +``` + +`SI` statements may follow immediately after `ALUID`. + +``` +DESIGNA x UT II +SI x EST I TUNC + DICE I +ALUID SI x EST II TUNC + DICE II +ALUID + DICE III + +> II +``` + +### Boolean operators + +The keyword `ET` can be used as a boolean "and". The keyword `AUT` can be used as a boolean "or". + +``` +DESIGNA x UT VERITAS +DESIGNA y UT FALSITAS +SI x ET y TUNC { + DICE I +} ALUID SI x AUT y TUNC { + DICE II +} ALUID { + DICE III +} + +> II +``` + +## Loops +### DONICUM loops + +``` +DESIGNA x UT NULLUM +DONICUM y UT NULLUM USQUE X FACE { + DESIGNA x UT x + y +} +DICE x + +> XLV +``` + +### DUM loops +``` +DESIGNA x UT NULLUM +DUM x PLUS X FACE { + DESIGNA x UT x+I +} +DICE x + +> XI +``` + +### PER loops +``` +DESIGNA x UT [I, II, III, IV, V] +PER y IN x FACE { + DICE y +} + +> I +> II +> III +> IV +> V +``` + +## Functions +Functions are defined with the `DEFINI` and `UT` keywords. The `REDI` keyword is used to return. `REDI` must have exactly one parameter. `REDI` can also be used to end the program, if used outside of a function. + +Calling a function is done with the `INVOCA` keyword. + +``` +DEFINI square x UT { + REDI (x*x) +} + +DICE (INVOCA square XI) + +> CXXI +``` + +## Built-ins +### DICE +### AUDI +### AUDI_NUMERUS +### ERUMPE +### LONGITUDO + +## Modules +Modules are additions to the base `CENTURION` syntax. They add or change certain features. Modules are included in your code by having + +```VOCA %MODULE NAME%``` + +In the beginning of your source file. + +Unlike many other programming languages with modules, the modules in `CENTURION` are not libraries that can be "imported" from other scripts written in the language. They are features of the compiler, disabled by default. + +### FORS +```VOCA FORS``` + +The `FORS` module allows you to add randomness to your `CENTURION` program. It adds 2 new built-in functions: `FORTIS_NUMERUS int int` and `FORTIS_ELECTIONIS ['a]`. + +`FORTIS_NUMERUS int int` picks a random int in the (inclusive) range of the two given ints. + +`FORTIS_ELECTIONIS ['a]` picks a random element from the given array. `FORTIS_ELECTIONIS array` is identical to ```array[FORTIS_NUMERUS NULLUS ((LONGITUDO array)-I)]```. + +### FRACTIO +```VOCA FRACTIO``` + +The `FRACTIO` module adds floats, in the form of base 12 fractions. + +In the `FRACTIO` module, `.` represents 1/12, `:` represents 1/6 and `S` represents 1/2. The symbols must be written from highest to lowest. So 3/4 would be written as "`S:.`". + +Fractions can be written as an extension of integers. So 3.5 would be "`IIIS`". + +The symbol `|` can be used to denote that the following fraction symbols are 1 "level down" is base 12. So after the first `|`, the fraction symbols denote 144ths instead of 12ths. So 7 and 100/144 would be "`VIIS:|::`", as "7 + 100/144" is also "7+9/12+4/144". + +A single "set" of fraction symbols can only represent up to 11/12, as 12/12 can be written as 1. + +### MAGNUM +```VOCA MAGNUM``` + +`MAGNUM` adds the ability to write integers larger than `MMMCMXCIX` (3.999) in your code, by adding the thousands operator, "`_`". + +When `_` is added _after_ a numeric symbol, the symbol becomes 1.000 times larger. The operator can be added to the same symbol multiple times. So "`V_`" is 5.000, and "`V__`" is 5.000.000. The strict rules for integers still apply, so 4.999 cannot be written as "`IV_`", but must instead be written as "`MV_CMXCIX`". + +All integer symbols except `I` may be given a `_`. + +### SUBNULLA +```VOCA SUBNULLA``` + +The `SUBNULLA` module adds the ability to write negative numbers as `-II` instead of `NULLUS-II`. diff --git a/__pycache__/ast.cpython-310.pyc b/__pycache__/ast.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc40acca136c4553dea4e9603ff0a858a6406feb GIT binary patch literal 135 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 literal 0 HcmV?d00001 diff --git a/__pycache__/lexer.cpython-310.pyc b/__pycache__/lexer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dad3329c3614c8a53a027283bede63715baa83f0 GIT binary patch literal 1907 zcmaJ>OK%%D5GJ`#>t#D|>!7D1D2i;{SVhl4nzeRqEWBE|`*0fC#zHnXvTQxZ_T^cOVl&3~ppV6Q#(oKw>_>5!6aIcQ2?NY2OM%*P>z{c^d0p#66E%l@AVLceom zwn?Bo0m(+tF~o2Obuq;lF~X!*h{+bZz?3jWXsXa8rZK%msLbXBS`etxLag{JrYbA7 zFx3*0NXsZuKvWPJL|aCz+``L<>Klkv*gW{3zaVsuEd>l#ocv5q3An>l-?X1SZTMDvg|0|WR=>`1 zJLTsU-P^CKhy89+?X{2lo#s)s)@Moeq%pte%|bg-4xsHaO^a zPabm@87FcVr8*485>-q$axAJ^_PXOaRI}{B+3-x5G(4vk)v09h&JIEg%Q( zIH4I()$z6)HIpiJ$BRJqBG-jc+OmBrZ}F+@+m=H~;7~b?yc$)a;Je7CBnqjvZTrHD z%zb<`Q*V}lrpS~!&eTknw6(}_L&qCuhUfMsN2$hDe#Xy0-taRoAw#1jGqRl6i|V$| z`EiZPmILQf0jY1tk`mB4x8ZF#VPrK7LW`CensE^Nj<+$? zcJHk|_;&Xm;3a{JNTxe1>^R%syEUu6xAM`mrgOj&ZeOi8tjM+h|5ygtro2E587`*b z2UJ`4P0kSZn;Q_9>Or)|@5D3;z^4*CtFJZO*xfRH+Y5$d_x=p(U!w+~^HbY3aJgxR zV7xN}`#10&z@@2mE%IG3{c#5KC)i8CiW58jG{9_S#!S2*z#h(Ep;NcH-2=eNldYDs z?N~N|RRA^5e(E}&J(PBP+!=AKVd4Hx^WkuB6dNlCkepkMKY}M9*%#0yh(hJT_kbCB zg{Z_NCc`MhccH?lFbyhE&lOS8x2}vAHFqTnxdf8|x`0xS!Cio^uxF5zFXxJ=z$=`~ zS<-p_!PCt3IBxgagE)4U8N=SniY8uL%1kOa{mj|HlX&;UhA_KOu}x z3D;gr3}5n(5C|E>8s;c(^Dsv^ryPi}tN--GOZ*^cr0PRY8zXk(U8(lU=YlxvVYd@ zu(ZMfe&%tmD7{Xxwvs!IZn=B^wVjk zXoVkR(C$q#M~kdEXik;#gwXg#XgNFY?)Rq`L>FHiwg*Xe(mY6}ceV%tMb!Tynd^_n z{vPv$XWu|qLKRY3CPjkfcNIm~NJV~66oScnLdZKJk$;Fx{s!bPStW1D`#Xhy0jKAo A>i_@% literal 0 HcmV?d00001 diff --git a/__pycache__/parser.cpython-310.pyc b/__pycache__/parser.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f5c41d62e65e50edbac403864080ca6bc4ad799 GIT binary patch literal 7488 zcmb_h-FFkm72gkiAU%-pZJGqdWB zkLM-$`7V!z{7b>ZZTL8!0U;8REooP7$uhPTOW9RhDr>7&s+BU*MuwzF z`h{eu73sN*|E=t*L^34%LL%AcN-IayhE&S!!Dv!R9!}nC+m2zc8Xd!K_q#Sg`L<>0 z{q7T^<7itl%JDx1zEPbO_&75_wxws%mznS7XEKrIr9UfQD=@TdW1m-2Nf|1OSBJ7` zV;@HTeP!u!ch^|zm`}P^duM5-ON^!7)BfXbXKBmm^ar*Htsc{8vG;VCziF9Hf2+IO zyOqH?QGprB&cW9fd_sT7yA2=b91uflfl4if$V7P|wN#>#6nukcNCv*sB#S;6W?bV# zMxydK7Bg}J8hxn`UVJ9$3XvMpD_JY4HVO>$<$7J;T)$h@8bhVG{YoCD9K-s`H`mUh zSWef?j@j4sL-52*n2;%V`=a25R5%5K*p;d9518wjv?KjpCh|)#ho~pETzm0d$5OWqmRn zn+5C`nPXU%WAolP$ANo-Ebwu1oPW~ep920{q{#V)IR7-DXUJJLW}ai`06S0KX4n|V zE&z6slo(dv*d@T;A(t68&ao?iEs`aMO>pe0r^CCzy++8(!;U=zEiV;*N-;!njj>j9b9_l-y?HPIIgb*fLpR*b$CZ0IQNa44dKD zDqx?H8pDoq>~p~Gk~+g?IkpCvM%Ed2jAQoz`#ITQ*c`_ifNhc{!{#}5AFv1H7YsYj zv4?=Q$QKMd!LeTg_9gih!xj+RQHV}{&7M!+2okTg&Vi z#oaC$Scbj@^~NbW{dV8jg(BdD)&efaG<+KH`4MYQA6CLMD1-B`Du^;Ky~LWMYJ2~A z{rdGDd|pG{In;Jw7AP)GD|^qK8dhhUdm^a1r`eN?ltz_3#w$%;oxrKjVy z3rN$+kZ(c7r4b<_XRa2Pb>6HytTnD^mO0}lI(8v;e+*!ihh56 zrCeNFuQcn?U|acA(sbO_!4S#J=qWFK9*x9-op2>BKl@JsD-XyWv4mM%l5=Rx zAKSTx5nxG_P@@8}G-DE`YQg=6yn#j}OTy{=E>4h`&MFLW!&6~C-hCiLCw)D@Stg0h zidBnM2Rv)K)eXuu2FG1OajSpaA5=HVGRU$(<`jB1UXzeKg_ADd0Yi)GPED)fnnEXu zx(4g2@o;TE$ms=_vlw}zCvMs2KZ zd)P_je5PW21&7?&G|v#my946PAIBBC5bqCHke>5Q2smP+TB+@ux?+XeS70Lhd_cL> zhC$+0OWDBr9ObV9p~vIZLLoZm2Me^SlsC(IV{@aXtwx;bDRK@6#*`7eNT$)Z&Ca%c z0U2=_H04jCJ4{wX(GrlPk{0Pf=Y2?Msx{ZD8|7%+lU2=XgI%}~TFJUj`_t$hS9rry z_F}vzqh07p9}7hLPPyKwZI&B!A(nl=x`Et8*R#v1pT}?{Ri3bY9d{Yr*IScfDe<91 zh*Hblt~E?`+F*TLgaU;_e*~iUVK4`a#f(bNA80i8peJHY(wg;pGis5jLk`v6Z-e4V79Y zS+Q}G1ddcs(B-U#ChWr@X?iJ?ULr$k8oNw@qCPv>$Dt!F%LQ+_);iz7Z8A0mGA847 zQD1h9P1q-0F*+CACmj=Z41RuyxKUIQ`#KK2|0$*r9LYg>RxT25VL@R!?`Z(hB!k`m zlSbpAp}zH0N{D7in*JfEVPJ=81Wyy*!D_Rqt%x%9KOL!5y6tl`zyHoT)?XWZo5 zNRH2G{!ZwcSLmtuF0GB@YV25ugC_!#uq3B!jI!yuBHw znLz23J%J<5A~}KN43djT*z1?e*jhru-UYmet@n|9gydr+H<5gTzCTTo05gXrDj|M!y zZS-|!xcyfg1q(-Brc-=QpR1KJ__Gw=a&&!|*LD6TQXdv{{p&&7ayj-SPSQm30EzEo z&(St+RCqUGD(9q}oKvMA3gZkf9U7+Wp7qrIxh!oN-$4%H`yBRmZ None: + self.expression = expression + + def __repr__(self) -> str: + return self.expression.__repr__() + + def eval(self, vtable, ftable, modules): + self.expression.eval(vtable, ftable, modules) + return vtable, ftable + +class String(BaseBox): + def __init__(self, value) -> None: + self.value = value + + def __repr__(self): + return f"String({self.value})" + +class Numeral(BaseBox): + def __init__(self, value) -> None: + self.value = value + + def __repr__(self): + return f"Numeral({self.value})" + +class Bool(BaseBox): + def __init__(self, value) -> None: + self.value = value + + def __repr__(self): + return f"Bool({self.value})" + +class ModuleCall(BaseBox): + def __init__(self, module_name) -> None: + self.module_name = module_name + + def __repr__(self) -> str: + return f"{self.module_name}" + +class ID(BaseBox): + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return f"ID({self.name})" + +class Designa(BaseBox): + def __init__(self, variable: ID, value) -> None: + self.id = variable + self.value = value + + def __repr__(self) -> str: + id_string = repr(self.id).replace('\n', '\n ') + value_string = repr(self.value).replace('\n', '\n ') + return f"Designa(\n {id_string},\n {value_string}\n)" + + def eval(self, vtable, ftable, modules): + vtable[self.id.name] = self.value.eval(vtable, ftable, modules) + return vtable, ftable + +class Defini(BaseBox): + def __init__(self, name, parameters, statements) -> None: + self.name = name + self.parameters = parameters + self.statements = statements + + def __repr__(self) -> str: + parameter_string = f"parameters([{rep_join(self.parameters)}])" + statements_string = f"statements([{rep_join(self.statements)}])" + def_string = rep_join( + [f"{repr(self.name)}", parameter_string, statements_string] + ) + return f"Defini({def_string})" + +class Redi(BaseBox): + def __init__(self, values) -> None: + self.values = values + + def __repr__(self) -> str: + values_string = f"[{rep_join(self.values)}]" + return f"Redi({values_string})" + +class Nullus(BaseBox): + def __repr__(self) -> str: + return "Nullus()" + + def eval(self, *_): + return 0 + +class BinOp(BaseBox): + def __init__(self, left, right, op) -> None: + self.left = left + self.right = right + self.op = op + + def __repr__(self) -> str: + binop_string = rep_join([self.left, self.right, self.op]) + return f"BinOp({binop_string})" + +class SiStatement(BaseBox): + def __init__(self, test, statements, else_part) -> None: + self.test = test + self.statements = statements + self.else_part = else_part + + def __repr__(self) -> str: + test = repr(self.test) + statements = f"statements([{rep_join(self.statements)}])" + else_part = f"statements([{rep_join(self.else_part)}])" + si_string = rep_join([test, statements, else_part]) + return f"Si({si_string})" + +class DumStatement(BaseBox): + def __init__(self, test, statements) -> None: + self.test = test + self.statements = statements + + def __repr__(self) -> str: + test = repr(self.test) + statements = f"statements([{rep_join(self.statements)}])" + dum_string = rep_join([test, statements]) + return f"Dum({dum_string})" + + def eval(self, vtable, ftable, modules): + while not self.test.eval(vtable, ftable, modules): + pass + + return vtable, ftable + +class Invoca(BaseBox): + def __init__(self, name, parameters) -> None: + self.name = name + self.parameters = parameters + + def __repr__(self) -> str: + parameters_string = f"parameters([{rep_join(self.parameters)}])" + invoca_string = rep_join([self.name, parameters_string]) + return f"Invoca({invoca_string})" + +class BuiltIn(BaseBox): + def __init__(self, builtin, parameters) -> None: + self.builtin = builtin + self.parameters = parameters + + def __repr__(self) -> str: + parameter_string = f"parameters([{rep_join(self.parameters)}])" + builtin_string = rep_join([self.builtin, parameter_string]) + return f"Builtin({builtin_string})" + + def eval(self, vtable, ftable, _): + return None + +class Program(BaseBox): + def __init__(self, module_calls: list[ModuleCall], statements) -> None: + self.modules = module_calls + self.statements = statements + + def __repr__(self) -> str: + modules_string = f"modules([{rep_join(self.modules)}])" + statements_string = f"statements([{rep_join(self.statements)}])" + return f"{modules_string},\n{statements_string}" + + def eval(self): + vtable = {} + ftable = {} + modules = [module.module_name for module in self.modules] + + for statement in self.statements: + vtable, ftable = statement.eval(vtable, ftable, modules) diff --git a/lexer.py b/lexer.py new file mode 100644 index 0000000..9d11dbb --- /dev/null +++ b/lexer.py @@ -0,0 +1,86 @@ +from rply import LexerGenerator + +keyword_tokens = [("KEYWORD_"+i, i) for i in [ + "ALUID", + "DEFINI", + "DESIGNA", + "DONICUM", + "DUM", + "EST", + "FACE", + "FALSITAS", + "INVOCA", + "MINUS", + "NULLUS", + "PER", + "PLUS", + "REDI", + "SI", + "TUNC", + "USQUE", + "UT", + "VERITAS", + "VOCA" +]] + +builtin_tokens = [("BUILTIN", i) for i in [ + "AUDI_NUMERUS", + "AUDI", + "DICE", + "ERUMPE", + "FORTIS_NUMERUS", + "FORTIS_ELECTIONIS", + "LONGITUDO" +]] + +data_tokens = [ + ("DATA_STRING", r"\".*?\""), + ("DATA_NUMERAL", r"[IVXLCDM]+") +] + +module_tokens = [("MODULE", i) for i in [ + "FORS", + "FRACTIO", + "MAGNUM", + "SUBNULLA" +]] + +symbol_tokens = [ + ("SYMBOL_LPARENS", r"\("), + ("SYMBOL_RPARENS", r"\)"), + ("SYMBOL_LBRACKET", r"\["), + ("SYMBOL_RBRACKET", r"\]"), + ("SYMBOL_LCURL", r"\{"), + ("SYMBOL_RCURL", r"\}"), + ("SYMBOL_PLUS", r"\+"), + ("SYMBOL_MINUS", r"\-"), + ("SYMBOL_TIMES", r"\*"), + ("SYMBOL_DIVIDE", r"\/") +] + +whitespace_tokens = [ + ("NEWLINE", r"\n+") +] + +all_tokens = ( + keyword_tokens + + builtin_tokens + + module_tokens + + symbol_tokens + + data_tokens + + whitespace_tokens + + [("ID", r"([a-z]|_)+")] +) + +class Lexer(): + def __init__(self): + self.lexer = LexerGenerator() + + def _add_tokens(self): + for token in all_tokens: + self.lexer.add(*token) + self.lexer.ignore(r" +") + + def get_lexer(self): + self._add_tokens() + return self.lexer.build() diff --git a/main.py b/main.py new file mode 100644 index 0000000..70fb4fb --- /dev/null +++ b/main.py @@ -0,0 +1,31 @@ +from lexer import Lexer +from parser import Parser + +text_input = """ +VOCA FORS + +DESIGNA correct UT FORTIS_NUMERUS I C +DESIGNA guess UT NULLUS + +DUM FALSITAS FACE { + DESIGNA guess UT AUDI_NUMERUS + SI guess MINUS correct TUNC { + DICE "Too low!" + } ALUID SI guess PLUS correct TUNC { + DICE "Too high!" + } ALUID { + ERUMPE + } +} + +DICE "You guessed correctly!" +""" + +lexer = Lexer().get_lexer() +pg = Parser() +pg.parse() +parser = pg.get_parser() + +tokens = lexer.lex(text_input) + +print(parser.parse(tokens)) diff --git a/parser.py b/parser.py new file mode 100644 index 0000000..92953f9 --- /dev/null +++ b/parser.py @@ -0,0 +1,153 @@ +from rply import ParserGenerator + +from lexer import all_tokens +import ast_nodes + +ALL_TOKENS = list(set([i[0] for i in all_tokens])) + +class Parser(): + def __init__(self): + self.pg = ParserGenerator(ALL_TOKENS) + + def parse(self): + @self.pg.production('program : opt_newline module_calls statements') + def program(tokens): + return ast_nodes.Program(tokens[-2], tokens[-1]) + + @self.pg.production('opt_newline : ') + @self.pg.production('opt_newline : NEWLINE') + def opt_newline(_): + return None + + @self.pg.production('module_calls : ') + @self.pg.production('module_calls : module_call NEWLINE module_calls') + def module_calls(calls): + if len(calls) == 0: + return [] + else: + return [calls[0]] + calls[2] + + @self.pg.production('module_call : KEYWORD_VOCA MODULE') + def module_call(tokens): + return ast_nodes.ModuleCall(tokens[1].value) + + @self.pg.production('statements : ') + @self.pg.production('statements : statement NEWLINE statements') + def statements(calls): + if len(calls) == 0: + return [] + else: + return [calls[0]] + calls[2] + + @self.pg.production('statement : KEYWORD_DESIGNA id KEYWORD_UT expression') + def statement_designa(tokens): + return ast_nodes.Designa(tokens[1], tokens[3]) + + @self.pg.production('statement : expression') + def statement_expression(tokens): + return ast_nodes.ExpressionStatement(tokens[0]) + + @self.pg.production('expressions : ') + @self.pg.production('expressions : expression expressions') + def expressions(calls): + if len(calls) == 0: + return [] + else: + return [calls[0]] + calls[1] + + @self.pg.production('ids : ') + @self.pg.production('ids : id ids') + def ids(calls): + if len(calls) == 0: + return [] + else: + return [calls[0]] + calls[1] + + @self.pg.production('expression : id') + def expression_id(tokens): + return tokens[0] + + @self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_UT SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL') + def defini(tokens): + return ast_nodes.Defini(tokens[1], tokens[2], tokens[6]) + + @self.pg.production('statement : KEYWORD_REDI expressions') + def redi(tokens): + return ast_nodes.Redi(tokens[1]) + + @self.pg.production('expression : DATA_STRING') + def expression_string(tokens): + return ast_nodes.String(tokens[0].value) + + @self.pg.production('expression : DATA_NUMERAL') + def expression_numeral(tokens): + return ast_nodes.Numeral(tokens[0].value) + + @self.pg.production('expression : KEYWORD_FALSITAS') + @self.pg.production('expression : KEYWORD_VERITAS') + def expression_bool(tokens): + return ast_nodes.Bool(tokens[0].name == "KEYWORD_VERITAS") + + @self.pg.production('expression : KEYWORD_NULLUS') + def expression_nullus(_): + return ast_nodes.Nullus() + + @self.pg.production('expression : expression SYMBOL_MINUS expression') + @self.pg.production('expression : expression SYMBOL_PLUS expression') + @self.pg.production('expression : expression KEYWORD_EST expression') + @self.pg.production('expression : expression KEYWORD_MINUS expression') + @self.pg.production('expression : expression KEYWORD_PLUS expression') + def binop(tokens): + return ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name) + + @self.pg.production('expression : BUILTIN expressions') + def expression_builtin(tokens): + return ast_nodes.BuiltIn(tokens[0].value, tokens[1]) + + @self.pg.production("id : ID") + def id_expression(tokens): + return ast_nodes.ID(tokens[0].value) + + @self.pg.production('expression : KEYWORD_INVOCA id expressions') + def invoca(tokens): + return ast_nodes.Invoca(tokens[1], tokens[2]) + + @self.pg.production('statement : si_statement') + def si_statement(tokens): + return tokens[0] + + @self.pg.production('statement : dum_statement') + def dum_statement(tokens): + return tokens[0] + + @self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TUNC SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL opt_newline aluid_statement') + def si(tokens): + return ast_nodes.SiStatement(tokens[1], tokens[5], tokens[9]) + + @self.pg.production('dum_statement : KEYWORD_DUM expression KEYWORD_FACE SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL') + def dum(tokens): + return ast_nodes.DumStatement(tokens[1], tokens[5]) + + @self.pg.production('aluid_statement : ') + def aluid_empty(_): + return None + + @self.pg.production('aluid_statement : KEYWORD_ALUID si_statement') + def aluid_si(tokens): + return [tokens[1]] + + @self.pg.production('aluid_statement : KEYWORD_ALUID SYMBOL_LCURL opt_newline statements opt_newline SYMBOL_RCURL aluid_statement') + def aluid(tokens): + return tokens[3] + + @self.pg.production('expression : SYMBOL_LPARENS expression SYMBOL_RPARENS') + def parens(tokens): + return tokens[1] + + @self.pg.error + def error_handle(token): + raise ValueError(token) + + + def get_parser(self): + return self.pg.build()