From c6195b1b304314384a2b0a2f088b1ccc6bf0f466 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sat, 21 Sep 2024 17:20:35 +0200 Subject: [PATCH] juic: add type annotations (syntax only) --- juic/eval.py | 18 +++++++------- juic/examples/ex1.jui | 9 +++++-- juic/parser.py | 55 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/juic/eval.py b/juic/eval.py index affa6d6..facaef8 100644 --- a/juic/eval.py +++ b/juic/eval.py @@ -241,10 +241,10 @@ class Context: def makeFunction(self, params: list[Tuple[str, bool]], body: Node) \ -> Function: # Check validity of the variadic parameter specification - variadics = [i for i, (_, v) in enumerate(params) if v] + variadics = [i for i, (_, v, _) in enumerate(params) if v] - for (name, _) in params: - count = len([0 for (n, _) in params if n == name]) + for (name, _, _) in params: + count = len([0 for (n, _, _) in params if n == name]) if count > 1: raise JuiStaticError(f"duplicate argument {name}") @@ -261,7 +261,7 @@ class Context: # TODO: Make a full scope not just an expr return Function(closure = self.currentStateClosure(), body = Node(Node.T.SCOPE_EXPR, [body]), - params = [name for (name, v) in params if not v], + params = [name for (name, v, _) in params if not v], variadic = variadic) def makeRecordCtor(self, params: list[Tuple[str, bool]], body: Node) \ @@ -341,7 +341,7 @@ class Context: requireType(x, juiIsUnpackable) raise NotImplementedError("unpack operator o(x_x)o") - case Node.T.OP, ["|", X, F]: + case Node.T.OP, [("|"|"|>"), X, F]: f = self.evalExpr(F) requireType(f, juiIsCallable) return self.evalCall(f, [X]) @@ -412,15 +412,17 @@ class Context: def execStmt(self, node: Node) -> JuiValue: match node.ctor, node.args: - case Node.T.LET_DECL, [name, X]: + case Node.T.LET_DECL, [name, X, let_type]: self.addDefinition(name, self.evalExpr(X)) + # TODO: Check type? return None - case Node.T.FUN_DECL, [name, params, body]: + case Node.T.FUN_DECL, [name, params, body, body_type]: self.addDefinition(name, self.makeFunction(params, body)) + # TODO: Check type when called? return None - case Node.T.REC_DECL, [name, params, body]: + case Node.T.REC_DECL, [name, params, body, body_type]: self.addDefinition(name, self.makeRecordCtor(params, body)) return None diff --git a/juic/examples/ex1.jui b/juic/examples/ex1.jui index 3641f99..9962bf4 100644 --- a/juic/examples/ex1.jui +++ b/juic/examples/ex1.jui @@ -37,7 +37,7 @@ fun stretch(x) = x; jwidget { fullscreen: true; @title jlabel { "title"; }; - @stack jwidget {} | stack | stretch; + @stack jwidget {} |> stack |> stretch; // let x = 4; // jfkeys { x }; @@ -46,7 +46,7 @@ jwidget { // fun test(x, y, ...all) = x + y + sum(all); }; -rec jlabel2(str) = jwidget { text: str }; +rec jlabel2(s) = jwidget { text: s }; jlabel2 {"Hello"}; @@ -107,3 +107,8 @@ my_widget { bg: none; }; */ + +let x: int = 4; +fun fun2(x: int, y: str): int = x + len(y); +fun fun3(x, y: str) = x + len(y); +let alias2: (int, str) -> int = fun2; diff --git a/juic/parser.py b/juic/parser.py index d6f4fcb..2734ee1 100644 --- a/juic/parser.py +++ b/juic/parser.py @@ -233,7 +233,7 @@ class JuiLexer(NaiveRegexLexer): RE_INT = r'0|[1-9][0-9]*|0b[0-1]+|0o[0-7]+|0[xX][0-9a-fA-F]+' # RE_FLOAT = r'([0-9]*\.[0-9]+|[0-9]+\.[0-9]*|[0-9]+)([eE][+-]?{INT})?f?' - RE_KW = r'\b(else|fun|if|let|rec|set|this|null|true|false)\b' + RE_KW = r'\b(else|fun|if|let|rec|set|this|null|true|false|int|bool|float|str)\b' RE_IDENT = r'[\w_][\w0-9_]*' RE_ATTR = r'({})\s*(?:@({}))?\s*:'.format(RE_IDENT, RE_IDENT) RE_VAR = r'\$(\.)?' + RE_IDENT @@ -244,7 +244,7 @@ class JuiLexer(NaiveRegexLexer): RE_STRING = r'["]((?:[^\\"]|\\"|\\n|\\t|\\\\)*)["]' RE_PUNCT = r'\.\.\.|[.,:;=(){}]' # TODO: Extend operator language to allow custom operators? - RE_OP = r'<\||>=|<=|!=|==|\|\||&&|<{|[|+*/%-<>!]' + RE_OP = r'<\||\|>|->|>=|<=|!=|==|\|\||&&|<{|[|+*/%-<>!]' TOKEN_REGEX = [ (r'[ \t\n]+', T.WS, None), @@ -277,6 +277,7 @@ class JuiLexer(NaiveRegexLexer): class Node: T = enum.Enum("T", [ "LIT", "IDENT", "OP", "THIS", "PROJ", "CALL", "IF", "SCOPE", + "BASE_TYPE", "FUN_TYPE", "RECORD", "REC_ATTR", "REC_VALUE", "LET_DECL", "FUN_DECL", "REC_DECL", "SET_STMT", "SCOPE_EXPR", "UNIT_TEST"]) @@ -373,7 +374,7 @@ class JuiParser(LL1Parser): # | expr0 "<{" record_entry,* "}" (record update) # | expr0 "(" expr,* ")" (function call) # | expr0 "." ident (projection, same prec as call) - @LL1Parser.binaryOpsLeft(mkOpNode, ["|"]) + @LL1Parser.binaryOpsLeft(mkOpNode, ["|","|>"]) @LL1Parser.binaryOpsLeft(mkOpNode, ["<|"]) @LL1Parser.binaryOpsLeft(mkOpNode, ["||"]) @LL1Parser.binaryOpsLeft(mkOpNode, ["&&"]) @@ -426,6 +427,23 @@ class JuiParser(LL1Parser): def expr(self): return self.expr2() + # type ::= int | bool | float | str + # | (type,*) -> type + def type(self): + builtin_type_kws = ["int", "bool", "float", "str"] + t = self.expect([JuiLexer.T.KW, "("]) + if t.type == JuiLexer.T.KW: + if t.value in builtin_type_kws: + return Node(Node.T.BASE_TYPE, [t.value]) + else: + self.raiseErrorAt(t, "not a type keyword") + if t.type == "(": + args_types = self.separatedList(self.type, sep=",", term=")") + self.expect(")") + self.expect("->") + ret_type = self.type() + return Node(Node.T.FUN_TYPE, [args_types, ret_type]) + # record_literal ::= "{" record_entry,* "}" # record_entry ::= LABEL? ATTR? expr # | let_decl @@ -463,20 +481,32 @@ class JuiParser(LL1Parser): case _, _: return Node(Node.T.REC_VALUE, [self.expr()]) - # let_decl ::= "let" ident "=" expr + # let_decl ::= "let" ident (":" type)? "=" expr def let_decl(self): self.expectKeyword("let") - ident = self.expect(JuiLexer.T.IDENT).value + t_ident = self.expect([JuiLexer.T.IDENT, JuiLexer.T.ATTR]) + if t_ident.type == JuiLexer.T.IDENT: + ident = t_ident.value + let_type = None + if t_ident.type == JuiLexer.T.ATTR: + ident = t_ident.value[0] + let_type = self.type() self.expect("=") expr = self.expr() - return Node(Node.T.LET_DECL, [ident, expr]) + return Node(Node.T.LET_DECL, [ident, expr, let_type]) # fun_rec_decl ::= ("fun" | "rec") ident "(" fun_rec_param,* ")" "=" expr - # fun_rec_param ::= "..."? ident + # fun_rec_param ::= "..."? ident (":" type)? def fun_rec_param(self): variadic = self.expect("...", optional=True) is not None - ident = self.expect(JuiLexer.T.IDENT).value - return (ident, variadic) + t_ident = self.expect([JuiLexer.T.IDENT, JuiLexer.T.ATTR]) + if t_ident.type == JuiLexer.T.IDENT: + ident = t_ident.value + arg_type = None + if t_ident.type == JuiLexer.T.ATTR: + ident = t_ident.value[0] + arg_type = self.type() + return (ident, variadic, arg_type) def fun_rec_decl(self): t = self.expectKeyword("fun", "rec") @@ -484,10 +514,15 @@ class JuiParser(LL1Parser): self.expect("(") params = self.separatedList(self.fun_rec_param, sep=",", term=")") self.expect(")") + if self.la.type == ":": + self.expect(":") + body_type = self.type() + else: + body_type = None self.expect("=") body = self.expr() return Node(Node.T.FUN_DECL if t == "fun" else Node.T.REC_DECL, - [ident, params, body]) + [ident, params, body, body_type]) # TODO: Check variadic param validity