🐐 NVLLVS and string concatenation
This commit is contained in:
@@ -67,6 +67,14 @@ Strings are written as text in quotes (`'` or `"`).
|
|||||||
DESIGNA x VT "this is a string"
|
DESIGNA x VT "this is a string"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Strings are concatenated with `:`:
|
||||||
|
|
||||||
|
```
|
||||||
|
DESIGNA greeting VT "Hello, " : "world!"
|
||||||
|
```
|
||||||
|
|
||||||
|
`NVLLVS` coerces to an empty string when used with `:`. Note: `+` is for arithmetic only — using it on strings raises an error.
|
||||||
|
|
||||||
### Integers
|
### Integers
|
||||||
Integers must be written in roman numerals using the following symbols:
|
Integers must be written in roman numerals using the following symbols:
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ def rep_join(l):
|
|||||||
OP_STR = {
|
OP_STR = {
|
||||||
"SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-",
|
"SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-",
|
||||||
"SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/",
|
"SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/",
|
||||||
|
"SYMBOL_COLON": ":",
|
||||||
"KEYWORD_EST": "EST", "KEYWORD_MINVS": "MINVS",
|
"KEYWORD_EST": "EST", "KEYWORD_MINVS": "MINVS",
|
||||||
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
|
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
|
||||||
}
|
}
|
||||||
@@ -411,24 +412,32 @@ class BinOp(Node):
|
|||||||
lv, rv = left.value(), right.value()
|
lv, rv = left.value(), right.value()
|
||||||
match self.op:
|
match self.op:
|
||||||
case "SYMBOL_PLUS":
|
case "SYMBOL_PLUS":
|
||||||
return vtable, ValInt(lv + rv)
|
if isinstance(lv, str) or isinstance(rv, str):
|
||||||
|
raise TypeError("Use : for string concatenation, not +")
|
||||||
|
if lv is None and rv is None:
|
||||||
|
return vtable, ValNul()
|
||||||
|
return vtable, ValInt((lv or 0) + (rv or 0))
|
||||||
|
case "SYMBOL_COLON":
|
||||||
|
lv = lv if lv is not None else ""
|
||||||
|
rv = rv if rv is not None else ""
|
||||||
|
return vtable, ValStr(lv + rv)
|
||||||
case "SYMBOL_MINUS":
|
case "SYMBOL_MINUS":
|
||||||
return vtable, ValInt(lv - rv)
|
return vtable, ValInt((lv or 0) - (rv or 0))
|
||||||
case "SYMBOL_TIMES":
|
case "SYMBOL_TIMES":
|
||||||
return vtable, ValInt(lv * rv)
|
return vtable, ValInt((lv or 0) * (rv or 0))
|
||||||
case "SYMBOL_DIVIDE":
|
case "SYMBOL_DIVIDE":
|
||||||
# TODO: Fractio
|
# TODO: Fractio
|
||||||
return vtable, ValInt(lv // rv)
|
return vtable, ValInt((lv or 0) // (rv or 0))
|
||||||
case "KEYWORD_MINVS":
|
case "KEYWORD_MINVS":
|
||||||
return vtable, ValBool(lv < rv)
|
return vtable, ValBool((lv or 0) < (rv or 0))
|
||||||
case "KEYWORD_PLVS":
|
case "KEYWORD_PLVS":
|
||||||
return vtable, ValBool(lv > rv)
|
return vtable, ValBool((lv or 0) > (rv or 0))
|
||||||
case "KEYWORD_EST":
|
case "KEYWORD_EST":
|
||||||
return vtable, ValBool(lv == rv)
|
return vtable, ValBool(lv == rv)
|
||||||
case "KEYWORD_ET":
|
case "KEYWORD_ET":
|
||||||
return vtable, ValBool(bool(lv) and bool(rv))
|
return vtable, ValBool(bool(left) and bool(right))
|
||||||
case "KEYWORD_AVT":
|
case "KEYWORD_AVT":
|
||||||
return vtable, ValBool(bool(lv) or bool(rv))
|
return vtable, ValBool(bool(left) or bool(right))
|
||||||
case _:
|
case _:
|
||||||
raise Exception(self.op)
|
raise Exception(self.op)
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ symbol_tokens = [
|
|||||||
("SYMBOL_MINUS", r"\-"),
|
("SYMBOL_MINUS", r"\-"),
|
||||||
("SYMBOL_TIMES", r"\*"),
|
("SYMBOL_TIMES", r"\*"),
|
||||||
("SYMBOL_DIVIDE", r"\/"),
|
("SYMBOL_DIVIDE", r"\/"),
|
||||||
|
("SYMBOL_COLON", r":"),
|
||||||
("SYMBOL_COMMA", r",")
|
("SYMBOL_COMMA", r",")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Parser():
|
|||||||
('left', ["KEYWORD_AVT"]),
|
('left', ["KEYWORD_AVT"]),
|
||||||
('left', ["KEYWORD_ET"]),
|
('left', ["KEYWORD_ET"]),
|
||||||
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
|
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
|
||||||
('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
('left', ["SYMBOL_COLON", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
||||||
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]),
|
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]),
|
||||||
('right', ["UMINUS"]),
|
('right', ["UMINUS"]),
|
||||||
('left', ["INDEX"]),
|
('left', ["INDEX"]),
|
||||||
@@ -174,6 +174,7 @@ class Parser():
|
|||||||
def expression_nullus(_):
|
def expression_nullus(_):
|
||||||
return ast_nodes.Nullus()
|
return ast_nodes.Nullus()
|
||||||
|
|
||||||
|
@self.pg.production('expression : expression SYMBOL_COLON expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_MINUS expression')
|
@self.pg.production('expression : expression SYMBOL_MINUS expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_PLUS expression')
|
@self.pg.production('expression : expression SYMBOL_PLUS expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_TIMES expression')
|
@self.pg.production('expression : expression SYMBOL_TIMES expression')
|
||||||
|
|||||||
@@ -86,6 +86,8 @@
|
|||||||
\item \textbf{string}: \\ Any text encased in " characters.
|
\item \textbf{string}: \\ Any text encased in " characters.
|
||||||
\item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
|
\item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
|
||||||
\item \textbf{bool}: \\ VERITAS or FALSITAS.
|
\item \textbf{bool}: \\ VERITAS or FALSITAS.
|
||||||
|
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{EST} (equality), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{:} (string concatenation).
|
||||||
|
\item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not).
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
||||||
27
tests.py
27
tests.py
@@ -375,6 +375,8 @@ error_tests = [
|
|||||||
("DEFINI f (x, y) VT { REDI(x) }\nINVOCA f (I)", TypeError), # too few args
|
("DEFINI f (x, y) VT { REDI(x) }\nINVOCA f (I)", TypeError), # too few args
|
||||||
("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", TypeError), # args to zero-param function
|
("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", TypeError), # args to zero-param function
|
||||||
("SI NVLLVS TVNC { DESIGNA r VT I }", TypeError), # NVLLVS cannot be used as boolean
|
("SI NVLLVS TVNC { DESIGNA r VT I }", TypeError), # NVLLVS cannot be used as boolean
|
||||||
|
("NVLLVS AVT VERITAS", TypeError), # NVLLVS cannot be used as boolean in AVT
|
||||||
|
('"hello" + " world"', TypeError), # use : for string concatenation, not +
|
||||||
("[I, II][III]", IndexError), # index too high
|
("[I, II][III]", IndexError), # index too high
|
||||||
("CVM SVBNVLLA\n[I, II][-I]", IndexError), # negative index
|
("CVM SVBNVLLA\n[I, II][-I]", IndexError), # negative index
|
||||||
("[I, II][-I]", ValueError), # negative value
|
("[I, II][-I]", ValueError), # negative value
|
||||||
@@ -583,6 +585,12 @@ arithmetic_edge_tests = [
|
|||||||
("I / V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), # integer division → 0
|
("I / V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), # integer division → 0
|
||||||
("M * M", Program([], [ExpressionStatement(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_TIMES"))]), ValInt(1000000)), # large intermediate (not displayed)
|
("M * M", Program([], [ExpressionStatement(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_TIMES"))]), ValInt(1000000)), # large intermediate (not displayed)
|
||||||
("(I + II) * (IV - I)", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), BinOp(Numeral("IV"), Numeral("I"), "SYMBOL_MINUS"), "SYMBOL_TIMES"))]), ValInt(9)), # nested parens
|
("(I + II) * (IV - I)", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), BinOp(Numeral("IV"), Numeral("I"), "SYMBOL_MINUS"), "SYMBOL_TIMES"))]), ValInt(9)), # nested parens
|
||||||
|
# NVLLVS coerces to 0 in integer arithmetic
|
||||||
|
("NVLLVS + V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_PLUS"))]), ValInt(5)),
|
||||||
|
("V + NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_PLUS"))]), ValInt(5)),
|
||||||
|
("NVLLVS + NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_PLUS"))]), ValNul()),
|
||||||
|
("NVLLVS - V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-5)),
|
||||||
|
("V - NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_MINUS"))]), ValInt(5)),
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestArithmeticEdge(unittest.TestCase):
|
class TestArithmeticEdge(unittest.TestCase):
|
||||||
@@ -591,6 +599,22 @@ class TestArithmeticEdge(unittest.TestCase):
|
|||||||
run_test(self, source, nodes, value)
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
# --- String concatenation ---
|
||||||
|
|
||||||
|
string_concat_tests = [
|
||||||
|
('"hello" : " world"', Program([], [ExpressionStatement(BinOp(String("hello"), String(" world"), "SYMBOL_COLON"))]), ValStr("hello world")),
|
||||||
|
# NVLLVS coerces to "" in string context
|
||||||
|
('NVLLVS : "hello"', Program([], [ExpressionStatement(BinOp(Nullus(), String("hello"), "SYMBOL_COLON"))]), ValStr("hello")),
|
||||||
|
('"hello" : NVLLVS', Program([], [ExpressionStatement(BinOp(String("hello"), Nullus(), "SYMBOL_COLON"))]), ValStr("hello")),
|
||||||
|
('NVLLVS : NVLLVS', Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_COLON"))]), ValStr("")),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestStringConcat(unittest.TestCase):
|
||||||
|
@parameterized.expand(string_concat_tests)
|
||||||
|
def test_string_concat(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
# --- Comparison operators ---
|
# --- Comparison operators ---
|
||||||
|
|
||||||
comparison_tests = [
|
comparison_tests = [
|
||||||
@@ -611,6 +635,9 @@ comparison_tests = [
|
|||||||
("II MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_MINVS"))]), ValBool(False)),
|
("II MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_MINVS"))]), ValBool(False)),
|
||||||
("II PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"))]), ValBool(True)),
|
("II PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"))]), ValBool(True)),
|
||||||
("I PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_PLVS"))]), ValBool(False)),
|
("I PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_PLVS"))]), ValBool(False)),
|
||||||
|
# NVLLVS coerces to 0 in comparisons
|
||||||
|
("V PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_PLVS"))]), ValBool(True)),
|
||||||
|
("NVLLVS MINVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_MINVS"))]), ValBool(True)),
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestComparisons(unittest.TestCase):
|
class TestComparisons(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user