diff --git a/spec/pragma/invalid_spec.lua b/spec/pragma/invalid_spec.lua new file mode 100644 index 000000000..105770481 --- /dev/null +++ b/spec/pragma/invalid_spec.lua @@ -0,0 +1,29 @@ +local util = require("spec.util") + +describe("invalid pragma", function() + it("rejects invalid pragma", util.check_syntax_error([[ + --#invalid_pragma on + ]], { + { y = 1, msg = "invalid token '--#invalid_pragma'" } + })) + + it("pragmas currently do not accept punctuation", util.check_syntax_error([[ + --#pragma something(other) + ]], { + { y = 1, msg = "invalid token '('" }, + { y = 1, msg = "invalid token ')'" }, + })) + + it("pragma arguments need to be in a single line", util.check_syntax_error([[ + --#pragma arity + on + + local function f(x: integer, y: integer) + print(x + y) + end + + print(f(10)) + ]], { + { msg = "expected pragma value" } + })) +end) diff --git a/tl.lua b/tl.lua index d8e10b3e5..8eaa70524 100644 --- a/tl.lua +++ b/tl.lua @@ -804,6 +804,8 @@ end + + @@ -838,6 +840,9 @@ do + + + @@ -874,6 +879,9 @@ do ["number hexfloat"] = "number", ["number power"] = "number", ["number powersign"] = "$ERR invalid_number$", + ["pragma"] = "pragma", + ["pragma any"] = nil, + ["pragma word"] = "pragma_identifier", } local keywords = { @@ -1267,11 +1275,39 @@ do elseif state == "got --" then if c == "[" then state = "got --[" + elseif c == "#" then + state = "pragma" else fwd = false state = "comment short" drop_token() end + elseif state == "pragma" then + if not lex_word[c] then + end_token_prev("pragma") + if tokens[nt].tk ~= "--#pragma" then + add_syntax_error() + end + fwd = false + state = "pragma any" + end + elseif state == "pragma any" then + if c == "\n" then + state = "any" + elseif lex_word[c] then + state = "pragma word" + begin_token() + elseif not lex_space[c] then + begin_token() + end_token_here("$ERR invalid$") + add_syntax_error() + end + elseif state == "pragma word" then + if not lex_word[c] then + end_token_prev("pragma_identifier") + fwd = false + state = (c == "\n") and "any" or "pragma any" + end elseif state == "got 0" then if c == "x" or c == "X" then state = "number hex" @@ -4220,7 +4256,27 @@ do return parse_function(ps, i, "record") end + local function parse_pragma(ps, i) + i = i + 1 + local pragma = new_node(ps, i, "pragma") + + if ps.tokens[i].kind ~= "pragma_identifier" then + return fail(ps, i, "expected pragma name") + end + pragma.pkey = ps.tokens[i].tk + i = i + 1 + + if ps.tokens[i].kind ~= "pragma_identifier" then + return fail(ps, i, "expected pragma value") + end + pragma.pvalue = ps.tokens[i].tk + i = i + 1 + + return i, pragma + end + local parse_statement_fns = { + ["--#pragma"] = parse_pragma, ["::"] = parse_label, ["do"] = parse_do, ["if"] = parse_if, @@ -4589,6 +4645,7 @@ local no_recurse_node = { ["break"] = true, ["label"] = true, ["number"] = true, + ["pragma"] = true, ["string"] = true, ["boolean"] = true, ["integer"] = true, @@ -5547,6 +5604,8 @@ function tl.pretty_print_ast(ast, gen_target, mode) return out end, }, + ["pragma"] = {}, + ["variable"] = emit_exactly_visitor_cbs, ["identifier"] = emit_exactly_visitor_cbs, @@ -12274,6 +12333,11 @@ self:expand_type(node, values, elements) }) return node.newtype end, }, + ["pragma"] = { + after = function(_self, _node, _children) + return NONE + end, + }, ["error_node"] = { after = function(_self, node, _children) return a_type(node, "invalid", {}) diff --git a/tl.tl b/tl.tl index cec12f333..08baa5db3 100644 --- a/tl.tl +++ b/tl.tl @@ -793,6 +793,8 @@ local enum TokenKind "identifier" "number" "integer" + "pragma" + "pragma_identifier" "$ERR unfinished_comment$" "$ERR invalid_string$" "$ERR invalid_number$" @@ -840,6 +842,9 @@ do "number hexfloat" "number power" "number powersign" + "pragma" + "pragma word" + "pragma any" end local last_token_kind : {LexState:TokenKind} = { @@ -874,6 +879,9 @@ do ["number hexfloat"] = "number", ["number power"] = "number", ["number powersign"] = "$ERR invalid_number$", + ["pragma"] = "pragma", + ["pragma any"] = nil, -- never in a token + ["pragma word"] = "pragma_identifier", -- never in a token } local keywords: {string:boolean} = { @@ -1267,11 +1275,39 @@ do elseif state == "got --" then if c == "[" then state = "got --[" + elseif c == "#" then + state = "pragma" else fwd = false state = "comment short" drop_token() end + elseif state == "pragma" then + if not lex_word[c] then + end_token_prev("pragma") + if tokens[nt].tk ~= "--#pragma" then + add_syntax_error() + end + fwd = false + state = "pragma any" + end + elseif state == "pragma any" then + if c == "\n" then + state = "any" + elseif lex_word[c] then + state = "pragma word" + begin_token() + elseif not lex_space[c] then + begin_token() + end_token_here("$ERR invalid$") + add_syntax_error() + end + elseif state == "pragma word" then + if not lex_word[c] then + end_token_prev("pragma_identifier") + fwd = false + state = (c == "\n") and "any" or "pragma any" + end elseif state == "got 0" then if c == "x" or c == "X" then state = "number hex" @@ -1902,6 +1938,7 @@ local enum NodeKind "macroexp" "local_macroexp" "interface" + "pragma" "error_node" end @@ -2100,6 +2137,10 @@ local record Node itemtype: Type decltuple: TupleType + -- pragma + pkey: string + pvalue: string + opt: boolean debug_type: Type @@ -4220,7 +4261,27 @@ local function parse_record_function(ps: ParseState, i: integer): integer, Node return parse_function(ps, i, "record") end +local function parse_pragma(ps: ParseState, i: integer): integer, Node + i = i + 1 -- skip "--#pragma" + local pragma = new_node(ps, i, "pragma") + + if ps.tokens[i].kind ~= "pragma_identifier" then + return fail(ps, i, "expected pragma name") + end + pragma.pkey = ps.tokens[i].tk + i = i + 1 + + if ps.tokens[i].kind ~= "pragma_identifier" then + return fail(ps, i, "expected pragma value") + end + pragma.pvalue = ps.tokens[i].tk + i = i + 1 + + return i, pragma +end + local parse_statement_fns: {string : function(ParseState, integer):(integer, Node)} = { + ["--#pragma"] = parse_pragma, ["::"] = parse_label, ["do"] = parse_do, ["if"] = parse_if, @@ -4589,6 +4650,7 @@ local no_recurse_node: {NodeKind : boolean} = { ["break"] = true, ["label"] = true, ["number"] = true, + ["pragma"] = true, ["string"] = true, ["boolean"] = true, ["integer"] = true, @@ -5547,6 +5609,8 @@ function tl.pretty_print_ast(ast: Node, gen_target: GenTarget, mode?: boolean | return out end, }, + ["pragma"] = { + }, ["variable"] = emit_exactly_visitor_cbs, ["identifier"] = emit_exactly_visitor_cbs, @@ -12274,6 +12338,11 @@ do return node.newtype end, }, + ["pragma"] = { + after = function(_self: TypeChecker, _node: Node, _children: {Type}): Type + return NONE + end, + }, ["error_node"] = { after = function(_self: TypeChecker, node: Node, _children: {Type}): Type return an_invalid(node)