diff options
| author | Li Jin <dragon-fly@qq.com> | 2026-04-23 17:04:06 +0800 |
|---|---|---|
| committer | Li Jin <dragon-fly@qq.com> | 2026-04-23 17:04:06 +0800 |
| commit | acb80dec2706359027c0461073aded3420eaec56 (patch) | |
| tree | de4e3393e6af6911f1e292fa2a4677e788062f04 | |
| parent | 7805872d0a1705fa2fc296b6c37a40f675a39591 (diff) | |
| download | yuescript-acb80dec2706359027c0461073aded3420eaec56.tar.gz yuescript-acb80dec2706359027c0461073aded3420eaec56.tar.bz2 yuescript-acb80dec2706359027c0461073aded3420eaec56.zip | |
Add annotation statements and expand annotation tests
| -rw-r--r-- | CMakeLists.txt | 5 | ||||
| -rw-r--r-- | spec/inputs/annotation.yue | 27 | ||||
| -rw-r--r-- | spec/inputs/annotation_before.yue | 16 | ||||
| -rw-r--r-- | spec/inputs/test/annotation_spec.yue | 160 | ||||
| -rw-r--r-- | spec/inputs/test/format_spec.yue | 4 | ||||
| -rw-r--r-- | spec/outputs/annotation.lua | 48 | ||||
| -rw-r--r-- | spec/outputs/annotation_before.lua | 55 | ||||
| -rw-r--r-- | spec/outputs/test/annotation_spec.lua | 162 | ||||
| -rw-r--r-- | spec/outputs/test/format_spec.lua | 3 | ||||
| -rw-r--r-- | src/yuescript/yue_ast.cpp | 3 | ||||
| -rw-r--r-- | src/yuescript/yue_ast.h | 9 | ||||
| -rw-r--r-- | src/yuescript/yue_compiler.cpp | 114 | ||||
| -rw-r--r-- | src/yuescript/yue_parser.cpp | 4 | ||||
| -rw-r--r-- | src/yuescript/yue_parser.h | 1 |
14 files changed, 584 insertions, 27 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 42d84d3..ea6a0f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -28,6 +28,11 @@ if (LUA_EXEC_NAME MATCHES "luajit") | |||
| 28 | NAMES luajit libluajit | 28 | NAMES luajit libluajit |
| 29 | PATHS ${LUA_LIBDIR} | 29 | PATHS ${LUA_LIBDIR} |
| 30 | NO_DEFAULT_PATH) | 30 | NO_DEFAULT_PATH) |
| 31 | elseif (LUA_VERSION_STRING MATCHES "Lua 5.5") | ||
| 32 | find_library(LUA_LIBRARIES | ||
| 33 | NAMES lua55 lua5.5 liblua55 liblua5.5 lua liblua | ||
| 34 | PATHS ${LUA_LIBDIR} | ||
| 35 | NO_DEFAULT_PATH) | ||
| 31 | elseif (LUA_VERSION_STRING MATCHES "Lua 5.4") | 36 | elseif (LUA_VERSION_STRING MATCHES "Lua 5.4") |
| 32 | find_library(LUA_LIBRARIES | 37 | find_library(LUA_LIBRARIES |
| 33 | NAMES lua54 lua5.4 liblua54 liblua5.4 lua liblua | 38 | NAMES lua54 lua5.4 liblua54 liblua5.4 lua liblua |
diff --git a/spec/inputs/annotation.yue b/spec/inputs/annotation.yue new file mode 100644 index 0000000..2124ad3 --- /dev/null +++ b/spec/inputs/annotation.yue | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | macro ClsDef = (code`ClassDecl) -> | ||
| 2 | className = code\match "^class%s+(%w+)" | ||
| 3 | lines = table.concat [item\gsub "%-%-%-", "---@" for item in code\gmatch "(%-%-%-.-)\n"], "\n" | ||
| 4 | return | ||
| 5 | type: "text" | ||
| 6 | before: false | ||
| 7 | code: | | ||
| 8 | ---@class #{className} | ||
| 9 | #{lines} | ||
| 10 | ---@class #{className}Class | ||
| 11 | ---@operator call:#{className} | ||
| 12 | ---@cast #{className} #{className}Class | ||
| 13 | |||
| 14 | $[ClsDef] | ||
| 15 | class A | ||
| 16 | ---field x number | ||
| 17 | ---field y number | ||
| 18 | new: (@x = 0, @y = 0) => | ||
| 19 | ---field setAdd fun(self: A, x: number, y: number): number Set fields and add number values. | ||
| 20 | setAdd: (@x, @y) => @x + @y | ||
| 21 | |||
| 22 | a = A! | ||
| 23 | res = a::setAdd 1, 2 | ||
| 24 | print(a.x, a.y, a.y, res) | ||
| 25 | |||
| 26 | return | ||
| 27 | |||
diff --git a/spec/inputs/annotation_before.yue b/spec/inputs/annotation_before.yue new file mode 100644 index 0000000..9b11aad --- /dev/null +++ b/spec/inputs/annotation_before.yue | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | macro Tag = (tag, code`ClassDecl) -> | ||
| 2 | className = code\match "^class%s+(%w+)" | ||
| 3 | return | ||
| 4 | type: "text" | ||
| 5 | before: tag == "before" | ||
| 6 | code: "-- #{tag}:#{className}" | ||
| 7 | |||
| 8 | $[Tag("before")] | ||
| 9 | class B | ||
| 10 | getTag: => "before" | ||
| 11 | |||
| 12 | $[Tag("after")] | ||
| 13 | class C | ||
| 14 | getTag: => "after" | ||
| 15 | |||
| 16 | return B!\getTag!, C!\getTag! | ||
diff --git a/spec/inputs/test/annotation_spec.yue b/spec/inputs/test/annotation_spec.yue new file mode 100644 index 0000000..3cd1d5a --- /dev/null +++ b/spec/inputs/test/annotation_spec.yue | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | import to_lua from require "yue" | ||
| 2 | |||
| 3 | compile_and_run = (code, config = {}) -> | ||
| 4 | lua_code, err = to_lua code, config | ||
| 5 | assert.is_nil err | ||
| 6 | assert.is_not_nil lua_code | ||
| 7 | chunk, load_err = load lua_code | ||
| 8 | assert.is_nil load_err | ||
| 9 | assert.is_not_nil chunk | ||
| 10 | chunk! | ||
| 11 | |||
| 12 | describe "annotation", -> | ||
| 13 | it "should append generated text after annotated class by default", -> | ||
| 14 | code = [[ | ||
| 15 | macro ClsDef = (code`ClassDecl) -> | ||
| 16 | className = code\match "^class%s+(%w+)" | ||
| 17 | return | ||
| 18 | type: "text" | ||
| 19 | before: false | ||
| 20 | code: "-- after:" .. className | ||
| 21 | |||
| 22 | $[ClsDef] | ||
| 23 | class A | ||
| 24 | getName: => "A" | ||
| 25 | |||
| 26 | return | ||
| 27 | ]] | ||
| 28 | result, err = to_lua code | ||
| 29 | assert.is_nil err | ||
| 30 | assert.is_not_nil result | ||
| 31 | assert.is_true result\find("__name = \"A\"") != nil | ||
| 32 | assert.is_true result\find("%-%- after:A") != nil | ||
| 33 | assert.is_true result\find("__name = \"A\"") < result\find("%-%- after:A") | ||
| 34 | |||
| 35 | it "should place generated text before the annotated statement when before is true", -> | ||
| 36 | code = [[ | ||
| 37 | macro Before = (code`ClassDecl) -> | ||
| 38 | className = code\match "^class%s+(%w+)" | ||
| 39 | return | ||
| 40 | type: "text" | ||
| 41 | before: true | ||
| 42 | code: "-- before:" .. className | ||
| 43 | |||
| 44 | $[Before] | ||
| 45 | class B | ||
| 46 | getName: => "B" | ||
| 47 | |||
| 48 | return | ||
| 49 | ]] | ||
| 50 | result, err = to_lua code | ||
| 51 | assert.is_nil err | ||
| 52 | assert.is_not_nil result | ||
| 53 | assert.is_true result\find("%-%- before:B") != nil | ||
| 54 | assert.is_true result\find("local B") != nil | ||
| 55 | assert.is_true result\find("%-%- before:B") < result\find("local B") | ||
| 56 | |||
| 57 | it "should support annotation invocation arguments", -> | ||
| 58 | code = [[ | ||
| 59 | macro Tag = (tag, code`ClassDecl) -> | ||
| 60 | className = code\match "^class%s+(%w+)" | ||
| 61 | return | ||
| 62 | type: "text" | ||
| 63 | before: false | ||
| 64 | code: "-- " .. tag .. ":" .. className | ||
| 65 | |||
| 66 | $[Tag("entity")] | ||
| 67 | class C | ||
| 68 | getName: => "C" | ||
| 69 | |||
| 70 | return | ||
| 71 | ]] | ||
| 72 | result, err = to_lua code | ||
| 73 | assert.is_nil err | ||
| 74 | assert.is_not_nil result | ||
| 75 | assert.is_true result\find("%-%- \"entity\":C") != nil | ||
| 76 | |||
| 77 | it "should report an error when annotation is not followed by a statement", -> | ||
| 78 | code = [[ | ||
| 79 | macro Invalid = (code) -> "" | ||
| 80 | $[Invalid] | ||
| 81 | ]] | ||
| 82 | result, err = to_lua code | ||
| 83 | assert.is_nil result | ||
| 84 | assert.is_true err\match("annotation must be followed by a statement") != nil | ||
| 85 | |||
| 86 | it "should wrap annotated function to validate numeric arguments", -> | ||
| 87 | code = [[ | ||
| 88 | macro ValidateNumberArgs = (code) -> | ||
| 89 | funcName = code\match "^(%w+)%s*=" | ||
| 90 | return | ||
| 91 | type: "text" | ||
| 92 | before: false | ||
| 93 | code: table.concat { | ||
| 94 | "local __orig_#{funcName} = #{funcName}" | ||
| 95 | "#{funcName} = function(a, b)" | ||
| 96 | "\tassert(type(a) == \"number\", \"expected number for a\")" | ||
| 97 | "\tassert(type(b) == \"number\", \"expected number for b\")" | ||
| 98 | "\treturn __orig_#{funcName}(a, b)" | ||
| 99 | "end" | ||
| 100 | }, "\n" | ||
| 101 | |||
| 102 | $[ValidateNumberArgs] | ||
| 103 | add = (a, b) -> a + b | ||
| 104 | |||
| 105 | ok, value = pcall -> add 3, 4 | ||
| 106 | bad_ok, bad_err = pcall -> add "3", 4 | ||
| 107 | return ok, value, bad_ok, bad_err | ||
| 108 | ]] | ||
| 109 | ok, value, bad_ok, bad_err = compile_and_run code | ||
| 110 | assert.is_true ok | ||
| 111 | assert.same value, 7 | ||
| 112 | assert.is_false bad_ok | ||
| 113 | assert.is_true bad_err\match("expected number for a") != nil | ||
| 114 | |||
| 115 | it "should wrap annotated function to validate return value", -> | ||
| 116 | code = [[ | ||
| 117 | macro ValidateNumberReturn = (code) -> | ||
| 118 | funcName = code\match "^(%w+)%s*=" | ||
| 119 | return | ||
| 120 | type: "text" | ||
| 121 | before: false | ||
| 122 | code: table.concat { | ||
| 123 | "local __orig_#{funcName} = #{funcName}" | ||
| 124 | "#{funcName} = function(...)" | ||
| 125 | "\tlocal result = __orig_#{funcName}(...)" | ||
| 126 | "\tassert(type(result) == \"number\", \"expected numeric return\")" | ||
| 127 | "\treturn result" | ||
| 128 | "end" | ||
| 129 | }, "\n" | ||
| 130 | |||
| 131 | $[ValidateNumberReturn] | ||
| 132 | toText = (value) -> tostring value | ||
| 133 | |||
| 134 | ok, err = pcall -> toText 42 | ||
| 135 | return ok, err | ||
| 136 | ]] | ||
| 137 | ok, err = compile_and_run code | ||
| 138 | assert.is_false ok | ||
| 139 | assert.is_true err\match("expected numeric return") != nil | ||
| 140 | |||
| 141 | it "should use annotation arguments to register annotated classes", -> | ||
| 142 | code = [[ | ||
| 143 | macro Register = (registry, code`ClassDecl) -> | ||
| 144 | className = code\match "^class%s+(%w+)" | ||
| 145 | return | ||
| 146 | type: "text" | ||
| 147 | before: false | ||
| 148 | code: "#{registry}[\"#{className}\"] = #{className}" | ||
| 149 | |||
| 150 | registry = {} | ||
| 151 | |||
| 152 | $[Register(registry)] | ||
| 153 | class Worker | ||
| 154 | run: => "ok" | ||
| 155 | |||
| 156 | return registry.Worker != nil, registry.Worker!\run! | ||
| 157 | ]] | ||
| 158 | exists, result = compile_and_run code | ||
| 159 | assert.is_true exists | ||
| 160 | assert.same result, "ok" | ||
diff --git a/spec/inputs/test/format_spec.yue b/spec/inputs/test/format_spec.yue index 310b610..a76a5dd 100644 --- a/spec/inputs/test/format_spec.yue +++ b/spec/inputs/test/format_spec.yue | |||
| @@ -26,6 +26,8 @@ files = [ | |||
| 26 | "spec/inputs/export_default.yue" | 26 | "spec/inputs/export_default.yue" |
| 27 | "spec/inputs/with_scope_shadow.yue" | 27 | "spec/inputs/with_scope_shadow.yue" |
| 28 | "spec/inputs/assign.yue" | 28 | "spec/inputs/assign.yue" |
| 29 | "spec/inputs/annotation.yue" | ||
| 30 | "spec/inputs/annotation_before.yue" | ||
| 29 | "spec/inputs/literals.yue" | 31 | "spec/inputs/literals.yue" |
| 30 | "spec/inputs/luarocks_upload.yue" | 32 | "spec/inputs/luarocks_upload.yue" |
| 31 | "spec/inputs/comprehension_nested.yue" | 33 | "spec/inputs/comprehension_nested.yue" |
| @@ -64,6 +66,7 @@ files = [ | |||
| 64 | "spec/inputs/test/continue_spec.yue" | 66 | "spec/inputs/test/continue_spec.yue" |
| 65 | "spec/inputs/test/varargs_assignment_spec.yue" | 67 | "spec/inputs/test/varargs_assignment_spec.yue" |
| 66 | "spec/inputs/test/advanced_macro_spec.yue" | 68 | "spec/inputs/test/advanced_macro_spec.yue" |
| 69 | "spec/inputs/test/annotation_spec.yue" | ||
| 67 | "spec/inputs/test/pipe_spec.yue" | 70 | "spec/inputs/test/pipe_spec.yue" |
| 68 | "spec/inputs/test/export_spec.yue" | 71 | "spec/inputs/test/export_spec.yue" |
| 69 | "spec/inputs/test/existential_spec.yue" | 72 | "spec/inputs/test/existential_spec.yue" |
| @@ -192,4 +195,3 @@ for file in *files | |||
| 192 | assert.is_not_nil ast | 195 | assert.is_not_nil ast |
| 193 | rewriteLineCol ast | 196 | rewriteLineCol ast |
| 194 | assert.same original_ast, ast | 197 | assert.same original_ast, ast |
| 195 | |||
diff --git a/spec/outputs/annotation.lua b/spec/outputs/annotation.lua new file mode 100644 index 0000000..261bd7b --- /dev/null +++ b/spec/outputs/annotation.lua | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | local A | ||
| 2 | do | ||
| 3 | local _class_0 | ||
| 4 | local _base_0 = { | ||
| 5 | setAdd = function(self, x, y) | ||
| 6 | self.x = x | ||
| 7 | self.y = y | ||
| 8 | return self.x + self.y | ||
| 9 | end | ||
| 10 | } | ||
| 11 | if _base_0.__index == nil then | ||
| 12 | _base_0.__index = _base_0 | ||
| 13 | end | ||
| 14 | _class_0 = setmetatable({ | ||
| 15 | __init = function(self, x, y) | ||
| 16 | if x == nil then | ||
| 17 | x = 0 | ||
| 18 | end | ||
| 19 | if y == nil then | ||
| 20 | y = 0 | ||
| 21 | end | ||
| 22 | self.x = x | ||
| 23 | self.y = y | ||
| 24 | end, | ||
| 25 | __base = _base_0, | ||
| 26 | __name = "A" | ||
| 27 | }, { | ||
| 28 | __index = _base_0, | ||
| 29 | __call = function(cls, ...) | ||
| 30 | local _self_0 = setmetatable({ }, _base_0) | ||
| 31 | cls.__init(_self_0, ...) | ||
| 32 | return _self_0 | ||
| 33 | end | ||
| 34 | }) | ||
| 35 | _base_0.__class = _class_0 | ||
| 36 | A = _class_0 | ||
| 37 | end | ||
| 38 | ---@class A | ||
| 39 | ---@field x number | ||
| 40 | ---@field y number | ||
| 41 | ---@field setAdd fun(self: A, x: number, y: number): number Set fields and add number values. | ||
| 42 | ---@class AClass | ||
| 43 | ---@operator call:A | ||
| 44 | ---@cast A AClass | ||
| 45 | local a = A() | ||
| 46 | local res = a:setAdd(1, 2) | ||
| 47 | print(a.x, a.y, a.y, res) | ||
| 48 | return | ||
diff --git a/spec/outputs/annotation_before.lua b/spec/outputs/annotation_before.lua new file mode 100644 index 0000000..874ef37 --- /dev/null +++ b/spec/outputs/annotation_before.lua | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | local B | ||
| 2 | do | ||
| 3 | local _class_0 | ||
| 4 | local _base_0 = { | ||
| 5 | getTag = function(self) | ||
| 6 | return "before" | ||
| 7 | end | ||
| 8 | } | ||
| 9 | if _base_0.__index == nil then | ||
| 10 | _base_0.__index = _base_0 | ||
| 11 | end | ||
| 12 | _class_0 = setmetatable({ | ||
| 13 | __init = function() end, | ||
| 14 | __base = _base_0, | ||
| 15 | __name = "B" | ||
| 16 | }, { | ||
| 17 | __index = _base_0, | ||
| 18 | __call = function(cls, ...) | ||
| 19 | local _self_0 = setmetatable({ }, _base_0) | ||
| 20 | cls.__init(_self_0, ...) | ||
| 21 | return _self_0 | ||
| 22 | end | ||
| 23 | }) | ||
| 24 | _base_0.__class = _class_0 | ||
| 25 | B = _class_0 | ||
| 26 | end | ||
| 27 | -- "before":B | ||
| 28 | local C | ||
| 29 | do | ||
| 30 | local _class_0 | ||
| 31 | local _base_0 = { | ||
| 32 | getTag = function(self) | ||
| 33 | return "after" | ||
| 34 | end | ||
| 35 | } | ||
| 36 | if _base_0.__index == nil then | ||
| 37 | _base_0.__index = _base_0 | ||
| 38 | end | ||
| 39 | _class_0 = setmetatable({ | ||
| 40 | __init = function() end, | ||
| 41 | __base = _base_0, | ||
| 42 | __name = "C" | ||
| 43 | }, { | ||
| 44 | __index = _base_0, | ||
| 45 | __call = function(cls, ...) | ||
| 46 | local _self_0 = setmetatable({ }, _base_0) | ||
| 47 | cls.__init(_self_0, ...) | ||
| 48 | return _self_0 | ||
| 49 | end | ||
| 50 | }) | ||
| 51 | _base_0.__class = _class_0 | ||
| 52 | C = _class_0 | ||
| 53 | end | ||
| 54 | -- "after":C | ||
| 55 | return B():getTag(), C():getTag() | ||
diff --git a/spec/outputs/test/annotation_spec.lua b/spec/outputs/test/annotation_spec.lua new file mode 100644 index 0000000..866b9dc --- /dev/null +++ b/spec/outputs/test/annotation_spec.lua | |||
| @@ -0,0 +1,162 @@ | |||
| 1 | local to_lua | ||
| 2 | do | ||
| 3 | local _obj_0 = require("yue") | ||
| 4 | to_lua = _obj_0.to_lua | ||
| 5 | end | ||
| 6 | local compile_and_run | ||
| 7 | compile_and_run = function(code, config) | ||
| 8 | if config == nil then | ||
| 9 | config = { } | ||
| 10 | end | ||
| 11 | local lua_code, err = to_lua(code, config) | ||
| 12 | assert.is_nil(err) | ||
| 13 | assert.is_not_nil(lua_code) | ||
| 14 | local chunk, load_err = load(lua_code) | ||
| 15 | assert.is_nil(load_err) | ||
| 16 | assert.is_not_nil(chunk) | ||
| 17 | return chunk() | ||
| 18 | end | ||
| 19 | return describe("annotation", function() | ||
| 20 | it("should append generated text after annotated class by default", function() | ||
| 21 | local code = [[macro ClsDef = (code`ClassDecl) -> | ||
| 22 | className = code\match "^class%s+(%w+)" | ||
| 23 | return | ||
| 24 | type: "text" | ||
| 25 | before: false | ||
| 26 | code: "-- after:" .. className | ||
| 27 | |||
| 28 | $[ClsDef] | ||
| 29 | class A | ||
| 30 | getName: => "A" | ||
| 31 | |||
| 32 | return | ||
| 33 | ]] | ||
| 34 | local result, err = to_lua(code) | ||
| 35 | assert.is_nil(err) | ||
| 36 | assert.is_not_nil(result) | ||
| 37 | assert.is_true(result:find("__name = \"A\"") ~= nil) | ||
| 38 | assert.is_true(result:find("%-%- after:A") ~= nil) | ||
| 39 | return assert.is_true(result:find("__name = \"A\"") < result:find("%-%- after:A")) | ||
| 40 | end) | ||
| 41 | it("should place generated text before the annotated statement when before is true", function() | ||
| 42 | local code = [[macro Before = (code`ClassDecl) -> | ||
| 43 | className = code\match "^class%s+(%w+)" | ||
| 44 | return | ||
| 45 | type: "text" | ||
| 46 | before: true | ||
| 47 | code: "-- before:" .. className | ||
| 48 | |||
| 49 | $[Before] | ||
| 50 | class B | ||
| 51 | getName: => "B" | ||
| 52 | |||
| 53 | return | ||
| 54 | ]] | ||
| 55 | local result, err = to_lua(code) | ||
| 56 | assert.is_nil(err) | ||
| 57 | assert.is_not_nil(result) | ||
| 58 | assert.is_true(result:find("%-%- before:B") ~= nil) | ||
| 59 | assert.is_true(result:find("local B") ~= nil) | ||
| 60 | return assert.is_true(result:find("%-%- before:B") < result:find("local B")) | ||
| 61 | end) | ||
| 62 | it("should support annotation invocation arguments", function() | ||
| 63 | local code = [[macro Tag = (tag, code`ClassDecl) -> | ||
| 64 | className = code\match "^class%s+(%w+)" | ||
| 65 | return | ||
| 66 | type: "text" | ||
| 67 | before: false | ||
| 68 | code: "-- " .. tag .. ":" .. className | ||
| 69 | |||
| 70 | $[Tag("entity")] | ||
| 71 | class C | ||
| 72 | getName: => "C" | ||
| 73 | |||
| 74 | return | ||
| 75 | ]] | ||
| 76 | local result, err = to_lua(code) | ||
| 77 | assert.is_nil(err) | ||
| 78 | assert.is_not_nil(result) | ||
| 79 | return assert.is_true(result:find("%-%- \"entity\":C") ~= nil) | ||
| 80 | end) | ||
| 81 | it("should report an error when annotation is not followed by a statement", function() | ||
| 82 | local code = [[macro Invalid = (code) -> "" | ||
| 83 | $[Invalid] | ||
| 84 | ]] | ||
| 85 | local result, err = to_lua(code) | ||
| 86 | assert.is_nil(result) | ||
| 87 | return assert.is_true(err:match("annotation must be followed by a statement") ~= nil) | ||
| 88 | end) | ||
| 89 | it("should wrap annotated function to validate numeric arguments", function() | ||
| 90 | local code = [[macro ValidateNumberArgs = (code) -> | ||
| 91 | funcName = code\match "^(%w+)%s*=" | ||
| 92 | return | ||
| 93 | type: "text" | ||
| 94 | before: false | ||
| 95 | code: table.concat { | ||
| 96 | "local __orig_#{funcName} = #{funcName}" | ||
| 97 | "#{funcName} = function(a, b)" | ||
| 98 | "\tassert(type(a) == \"number\", \"expected number for a\")" | ||
| 99 | "\tassert(type(b) == \"number\", \"expected number for b\")" | ||
| 100 | "\treturn __orig_#{funcName}(a, b)" | ||
| 101 | "end" | ||
| 102 | }, "\n" | ||
| 103 | |||
| 104 | $[ValidateNumberArgs] | ||
| 105 | add = (a, b) -> a + b | ||
| 106 | |||
| 107 | ok, value = pcall -> add 3, 4 | ||
| 108 | bad_ok, bad_err = pcall -> add "3", 4 | ||
| 109 | return ok, value, bad_ok, bad_err | ||
| 110 | ]] | ||
| 111 | local ok, value, bad_ok, bad_err = compile_and_run(code) | ||
| 112 | assert.is_true(ok) | ||
| 113 | assert.same(value, 7) | ||
| 114 | assert.is_false(bad_ok) | ||
| 115 | return assert.is_true(bad_err:match("expected number for a") ~= nil) | ||
| 116 | end) | ||
| 117 | it("should wrap annotated function to validate return value", function() | ||
| 118 | local code = [[macro ValidateNumberReturn = (code) -> | ||
| 119 | funcName = code\match "^(%w+)%s*=" | ||
| 120 | return | ||
| 121 | type: "text" | ||
| 122 | before: false | ||
| 123 | code: table.concat { | ||
| 124 | "local __orig_#{funcName} = #{funcName}" | ||
| 125 | "#{funcName} = function(...)" | ||
| 126 | "\tlocal result = __orig_#{funcName}(...)" | ||
| 127 | "\tassert(type(result) == \"number\", \"expected numeric return\")" | ||
| 128 | "\treturn result" | ||
| 129 | "end" | ||
| 130 | }, "\n" | ||
| 131 | |||
| 132 | $[ValidateNumberReturn] | ||
| 133 | toText = (value) -> tostring value | ||
| 134 | |||
| 135 | ok, err = pcall -> toText 42 | ||
| 136 | return ok, err | ||
| 137 | ]] | ||
| 138 | local ok, err = compile_and_run(code) | ||
| 139 | assert.is_false(ok) | ||
| 140 | return assert.is_true(err:match("expected numeric return") ~= nil) | ||
| 141 | end) | ||
| 142 | return it("should use annotation arguments to register annotated classes", function() | ||
| 143 | local code = [[macro Register = (registry, code`ClassDecl) -> | ||
| 144 | className = code\match "^class%s+(%w+)" | ||
| 145 | return | ||
| 146 | type: "text" | ||
| 147 | before: false | ||
| 148 | code: "#{registry}[\"#{className}\"] = #{className}" | ||
| 149 | |||
| 150 | registry = {} | ||
| 151 | |||
| 152 | $[Register(registry)] | ||
| 153 | class Worker | ||
| 154 | run: => "ok" | ||
| 155 | |||
| 156 | return registry.Worker != nil, registry.Worker!\run! | ||
| 157 | ]] | ||
| 158 | local exists, result = compile_and_run(code) | ||
| 159 | assert.is_true(exists) | ||
| 160 | return assert.same(result, "ok") | ||
| 161 | end) | ||
| 162 | end) | ||
diff --git a/spec/outputs/test/format_spec.lua b/spec/outputs/test/format_spec.lua index d38a0ad..1eb2fbb 100644 --- a/spec/outputs/test/format_spec.lua +++ b/spec/outputs/test/format_spec.lua | |||
| @@ -26,6 +26,8 @@ local files = { | |||
| 26 | "spec/inputs/export_default.yue", | 26 | "spec/inputs/export_default.yue", |
| 27 | "spec/inputs/with_scope_shadow.yue", | 27 | "spec/inputs/with_scope_shadow.yue", |
| 28 | "spec/inputs/assign.yue", | 28 | "spec/inputs/assign.yue", |
| 29 | "spec/inputs/annotation.yue", | ||
| 30 | "spec/inputs/annotation_before.yue", | ||
| 29 | "spec/inputs/literals.yue", | 31 | "spec/inputs/literals.yue", |
| 30 | "spec/inputs/luarocks_upload.yue", | 32 | "spec/inputs/luarocks_upload.yue", |
| 31 | "spec/inputs/comprehension_nested.yue", | 33 | "spec/inputs/comprehension_nested.yue", |
| @@ -64,6 +66,7 @@ local files = { | |||
| 64 | "spec/inputs/test/continue_spec.yue", | 66 | "spec/inputs/test/continue_spec.yue", |
| 65 | "spec/inputs/test/varargs_assignment_spec.yue", | 67 | "spec/inputs/test/varargs_assignment_spec.yue", |
| 66 | "spec/inputs/test/advanced_macro_spec.yue", | 68 | "spec/inputs/test/advanced_macro_spec.yue", |
| 69 | "spec/inputs/test/annotation_spec.yue", | ||
| 67 | "spec/inputs/test/pipe_spec.yue", | 70 | "spec/inputs/test/pipe_spec.yue", |
| 68 | "spec/inputs/test/export_spec.yue", | 71 | "spec/inputs/test/export_spec.yue", |
| 69 | "spec/inputs/test/existential_spec.yue", | 72 | "spec/inputs/test/existential_spec.yue", |
diff --git a/src/yuescript/yue_ast.cpp b/src/yuescript/yue_ast.cpp index e5f8d12..23f3820 100644 --- a/src/yuescript/yue_ast.cpp +++ b/src/yuescript/yue_ast.cpp | |||
| @@ -1530,6 +1530,9 @@ std::string MacroFunc_t::to_string(void* ud) const { | |||
| 1530 | std::string Macro_t::to_string(void* ud) const { | 1530 | std::string Macro_t::to_string(void* ud) const { |
| 1531 | return "macro "s + name->to_string(ud) + " = "s + decl->to_string(ud); | 1531 | return "macro "s + name->to_string(ud) + " = "s + decl->to_string(ud); |
| 1532 | } | 1532 | } |
| 1533 | std::string Annotation_t::to_string(void* ud) const { | ||
| 1534 | return "$["s + name->to_string(ud) + (invoke ? invoke->to_string(ud) : ""s) + "]"s; | ||
| 1535 | } | ||
| 1533 | std::string MacroInPlace_t::to_string(void* ud) const { | 1536 | std::string MacroInPlace_t::to_string(void* ud) const { |
| 1534 | auto info = reinterpret_cast<YueFormat*>(ud); | 1537 | auto info = reinterpret_cast<YueFormat*>(ud); |
| 1535 | auto line = "$ ->"s; | 1538 | auto line = "$ ->"s; |
diff --git a/src/yuescript/yue_ast.h b/src/yuescript/yue_ast.h index 7d9a4a1..65568af 100644 --- a/src/yuescript/yue_ast.h +++ b/src/yuescript/yue_ast.h | |||
| @@ -853,6 +853,12 @@ AST_NODE(Macro) | |||
| 853 | AST_MEMBER(Macro, &name, &decl) | 853 | AST_MEMBER(Macro, &name, &decl) |
| 854 | AST_END(Macro) | 854 | AST_END(Macro) |
| 855 | 855 | ||
| 856 | AST_NODE(Annotation) | ||
| 857 | ast_ptr<true, UnicodeName_t> name; | ||
| 858 | ast_sel<false, Invoke_t, InvokeArgs_t> invoke; | ||
| 859 | AST_MEMBER(Annotation, &name, &invoke) | ||
| 860 | AST_END(Annotation) | ||
| 861 | |||
| 856 | AST_NODE(NameOrDestructure) | 862 | AST_NODE(NameOrDestructure) |
| 857 | ast_sel<true, Variable_t, SimpleTable_t, TableLit_t, Comprehension_t> item; | 863 | ast_sel<true, Variable_t, SimpleTable_t, TableLit_t, Comprehension_t> item; |
| 858 | AST_MEMBER(NameOrDestructure, &item) | 864 | AST_MEMBER(NameOrDestructure, &item) |
| @@ -961,7 +967,8 @@ AST_END(ChainAssign) | |||
| 961 | AST_NODE(Statement) | 967 | AST_NODE(Statement) |
| 962 | ast_sel<true, | 968 | ast_sel<true, |
| 963 | Import_t, While_t, Repeat_t, For_t, | 969 | Import_t, While_t, Repeat_t, For_t, |
| 964 | Return_t, Local_t, Global_t, Export_t, Macro_t, MacroInPlace_t, | 970 | Return_t, Local_t, Global_t, Export_t, |
| 971 | Macro_t, MacroInPlace_t, Annotation_t, | ||
| 965 | BreakLoop_t, Label_t, Goto_t, ShortTabAppending_t, | 972 | BreakLoop_t, Label_t, Goto_t, ShortTabAppending_t, |
| 966 | Backcall_t, LocalAttrib_t, PipeBody_t, ExpListAssign_t, ChainAssign_t | 973 | Backcall_t, LocalAttrib_t, PipeBody_t, ExpListAssign_t, ChainAssign_t |
| 967 | > content; | 974 | > content; |
diff --git a/src/yuescript/yue_compiler.cpp b/src/yuescript/yue_compiler.cpp index 76d6b0f..844d61e 100644 --- a/src/yuescript/yue_compiler.cpp +++ b/src/yuescript/yue_compiler.cpp | |||
| @@ -78,7 +78,7 @@ static std::unordered_set<std::string> Metamethods = { | |||
| 78 | "close"s // Lua 5.4 | 78 | "close"s // Lua 5.4 |
| 79 | }; | 79 | }; |
| 80 | 80 | ||
| 81 | const std::string_view version = "0.33.10"sv; | 81 | const std::string_view version = "0.34.0"sv; |
| 82 | const std::string_view extension = "yue"sv; | 82 | const std::string_view extension = "yue"sv; |
| 83 | 83 | ||
| 84 | class CompileError : public std::logic_error { | 84 | class CompileError : public std::logic_error { |
| @@ -5386,7 +5386,8 @@ private: | |||
| 5386 | if (!nodes.empty()) { | 5386 | if (!nodes.empty()) { |
| 5387 | str_list temp; | 5387 | str_list temp; |
| 5388 | auto lastStmt = lastStatementFrom(nodes); | 5388 | auto lastStmt = lastStatementFrom(nodes); |
| 5389 | for (auto node : nodes) { | 5389 | for (auto nodeIt = nodes.begin(); nodeIt != nodes.end(); ++nodeIt) { |
| 5390 | auto node = *nodeIt; | ||
| 5390 | if (auto comment = ast_cast<YueComment_t>(node)) { | 5391 | if (auto comment = ast_cast<YueComment_t>(node)) { |
| 5391 | transformComment(comment, temp); | 5392 | transformComment(comment, temp); |
| 5392 | continue; | 5393 | continue; |
| @@ -5398,7 +5399,8 @@ private: | |||
| 5398 | } else if (!ast_is<Statement_t>(node)) { | 5399 | } else if (!ast_is<Statement_t>(node)) { |
| 5399 | continue; | 5400 | continue; |
| 5400 | } | 5401 | } |
| 5401 | auto transformNode = [&]() { | 5402 | std::function<void()> transformNode; |
| 5403 | transformNode = [&]() { | ||
| 5402 | currentScope().lastStatement = (node == lastStmt) && currentScope().mode == GlobalMode::None; | 5404 | currentScope().lastStatement = (node == lastStmt) && currentScope().mode == GlobalMode::None; |
| 5403 | auto stmt = static_cast<Statement_t*>(node); | 5405 | auto stmt = static_cast<Statement_t*>(node); |
| 5404 | if (auto importNode = stmt->content.as<Import_t>(); | 5406 | if (auto importNode = stmt->content.as<Import_t>(); |
| @@ -5418,6 +5420,56 @@ private: | |||
| 5418 | _importedGlobal->importContent = importNode->content.get(); | 5420 | _importedGlobal->importContent = importNode->content.get(); |
| 5419 | } | 5421 | } |
| 5420 | } | 5422 | } |
| 5423 | } else if (auto annotation = stmt->content.as<Annotation_t>()) { | ||
| 5424 | #ifndef YUE_NO_MACRO | ||
| 5425 | auto next = nodeIt; | ||
| 5426 | ++next; | ||
| 5427 | if (next == nodes.end() || !ast_is<Statement_t>(*next)) { | ||
| 5428 | throw CompileError("annotation must be followed by a statement"sv, node); | ||
| 5429 | } | ||
| 5430 | if (static_cast<Statement_t*>(*next)->content.is<Return_t>()) { | ||
| 5431 | throw CompileError("annotation can not be applied to a return statement"sv, node); | ||
| 5432 | } | ||
| 5433 | auto callable = annotation->new_ptr<Callable_t>(); | ||
| 5434 | auto macroName = annotation->new_ptr<MacroName_t>(); | ||
| 5435 | macroName->name.set(annotation->name); | ||
| 5436 | callable->item.set(macroName); | ||
| 5437 | auto chainValue = annotation->new_ptr<ChainValue_t>(); | ||
| 5438 | chainValue->items.push_back(callable); | ||
| 5439 | if (annotation->invoke) { | ||
| 5440 | chainValue->items.push_back(annotation->invoke); | ||
| 5441 | } | ||
| 5442 | auto stmtCode = YueFormat{}.toString(*next); | ||
| 5443 | ast_ptr<false, ast_node> macroNode; | ||
| 5444 | std::unique_ptr<input> codes; | ||
| 5445 | std::string luaCodes; | ||
| 5446 | str_list localVars; | ||
| 5447 | bool before = false; | ||
| 5448 | std::tie(macroNode, codes, luaCodes, localVars, before) = expandMacro(chainValue, ExpUsage::Common, false, stmtCode); | ||
| 5449 | if (!before) { | ||
| 5450 | ++nodeIt; | ||
| 5451 | node = *nodeIt; | ||
| 5452 | transformNode(); | ||
| 5453 | } | ||
| 5454 | if (!macroNode) { | ||
| 5455 | temp.push_back(luaCodes); | ||
| 5456 | if (!localVars.empty()) { | ||
| 5457 | for (const auto& var : localVars) { | ||
| 5458 | addToScope(var); | ||
| 5459 | } | ||
| 5460 | } | ||
| 5461 | } else { | ||
| 5462 | if (!macroNode.to<Block_t>()->statementOrComments.empty()) { | ||
| 5463 | auto doBody = macroNode->new_ptr<Body_t>(); | ||
| 5464 | doBody->content.set(macroNode); | ||
| 5465 | auto doNode = macroNode->new_ptr<Do_t>(); | ||
| 5466 | doNode->body.set(doBody); | ||
| 5467 | transformDo(doNode, temp, ExpUsage::Common); | ||
| 5468 | } | ||
| 5469 | } | ||
| 5470 | #else // YUE_NO_MACRO | ||
| 5471 | throw CompileError("macro feature not supported"sv, annotation); | ||
| 5472 | #endif // YUE_NO_MACRO | ||
| 5421 | } else { | 5473 | } else { |
| 5422 | transformStatement(stmt, temp); | 5474 | transformStatement(stmt, temp); |
| 5423 | } | 5475 | } |
| @@ -5659,7 +5711,7 @@ private: | |||
| 5659 | pushCurrentModule(); // cur | 5711 | pushCurrentModule(); // cur |
| 5660 | int top = lua_gettop(L) - 1; | 5712 | int top = lua_gettop(L) - 1; |
| 5661 | DEFER(lua_settop(L, top)); | 5713 | DEFER(lua_settop(L, top)); |
| 5662 | if (auto builtinCode = expandMacroChain(chainValue)) { | 5714 | if (auto builtinCode = expandMacroChain(chainValue, Empty)) { |
| 5663 | throw CompileError("macro generating function must return a function"sv, chainValue); | 5715 | throw CompileError("macro generating function must return a function"sv, chainValue); |
| 5664 | } // cur res | 5716 | } // cur res |
| 5665 | if (lua_isfunction(L, -1) == 0) { | 5717 | if (lua_isfunction(L, -1) == 0) { |
| @@ -7039,7 +7091,7 @@ private: | |||
| 7039 | return Empty; | 7091 | return Empty; |
| 7040 | } | 7092 | } |
| 7041 | 7093 | ||
| 7042 | std::optional<std::string> expandMacroChain(ChainValue_t* chainValue) { | 7094 | std::optional<std::string> expandMacroChain(ChainValue_t* chainValue, const std::string& extraCode) { |
| 7043 | const auto& chainList = chainValue->items.objects(); | 7095 | const auto& chainList = chainValue->items.objects(); |
| 7044 | auto x = ast_to<Callable_t>(chainList.front())->item.to<MacroName_t>(); | 7096 | auto x = ast_to<Callable_t>(chainList.front())->item.to<MacroName_t>(); |
| 7045 | auto macroName = _parser.toString(x->name); | 7097 | auto macroName = _parser.toString(x->name); |
| @@ -7088,7 +7140,7 @@ private: | |||
| 7088 | auto chainValue = exp->get_by_path<UnaryExp_t, Value_t, ChainValue_t>(); | 7140 | auto chainValue = exp->get_by_path<UnaryExp_t, Value_t, ChainValue_t>(); |
| 7089 | BREAK_IF(!chainValue); | 7141 | BREAK_IF(!chainValue); |
| 7090 | BREAK_IF(!isMacroChain(chainValue)); | 7142 | BREAK_IF(!isMacroChain(chainValue)); |
| 7091 | str = std::get<1>(expandMacroStr(chainValue)); | 7143 | str = std::get<1>(expandMacroStr(chainValue, Empty)); |
| 7092 | BLOCK_END | 7144 | BLOCK_END |
| 7093 | } | 7145 | } |
| 7094 | } | 7146 | } |
| @@ -7184,7 +7236,11 @@ private: | |||
| 7184 | if (args) { | 7236 | if (args) { |
| 7185 | argIt = args->begin(); | 7237 | argIt = args->begin(); |
| 7186 | } | 7238 | } |
| 7239 | if (!extraCode.empty()) { | ||
| 7240 | argStrs.push_back(extraCode); | ||
| 7241 | } | ||
| 7187 | for (const auto& arg : argStrs) { | 7242 | for (const auto& arg : argStrs) { |
| 7243 | ast_node* currentArg = args && argIt != args->end() ? *argIt : chainValue; | ||
| 7188 | if (checkIt != checks.end()) { | 7244 | if (checkIt != checks.end()) { |
| 7189 | if (checkIt->empty()) { | 7245 | if (checkIt->empty()) { |
| 7190 | ++checkIt; | 7246 | ++checkIt; |
| @@ -7192,18 +7248,20 @@ private: | |||
| 7192 | if ((*checkIt)[0] == '.') { | 7248 | if ((*checkIt)[0] == '.') { |
| 7193 | auto astName = checkIt->substr(3); | 7249 | auto astName = checkIt->substr(3); |
| 7194 | if (!_parser.match(astName, arg)) { | 7250 | if (!_parser.match(astName, arg)) { |
| 7195 | throw CompileError("expecting \""s + astName + "\", AST mismatch"s, *argIt); | 7251 | throw CompileError("expecting \""s + astName + "\", AST mismatch"s, currentArg); |
| 7196 | } | 7252 | } |
| 7197 | } else { | 7253 | } else { |
| 7198 | if (!_parser.match(*checkIt, arg)) { | 7254 | if (!_parser.match(*checkIt, arg)) { |
| 7199 | throw CompileError("expecting \""s + *checkIt + "\", AST mismatch"s, *argIt); | 7255 | throw CompileError("expecting \""s + *checkIt + "\", AST mismatch"s, currentArg); |
| 7200 | } | 7256 | } |
| 7201 | ++checkIt; | 7257 | ++checkIt; |
| 7202 | } | 7258 | } |
| 7203 | } | 7259 | } |
| 7204 | } | 7260 | } |
| 7205 | lua_pushlstring(L, arg.c_str(), arg.size()); | 7261 | lua_pushlstring(L, arg.c_str(), arg.size()); |
| 7206 | ++argIt; | 7262 | if (args && argIt != args->end()) { |
| 7263 | ++argIt; | ||
| 7264 | } | ||
| 7207 | } // cur pcall macroFunc args... | 7265 | } // cur pcall macroFunc args... |
| 7208 | bool success = lua_pcall(L, static_cast<int>(argStrs.size()), 1, 0) == 0; | 7266 | bool success = lua_pcall(L, static_cast<int>(argStrs.size()), 1, 0) == 0; |
| 7209 | if (!success) { // cur err | 7267 | if (!success) { // cur err |
| @@ -7218,21 +7276,22 @@ private: | |||
| 7218 | return std::nullopt; | 7276 | return std::nullopt; |
| 7219 | } | 7277 | } |
| 7220 | 7278 | ||
| 7221 | std::tuple<std::string, std::string, str_list> expandMacroStr(ChainValue_t* chainValue) { | 7279 | std::tuple<std::string, std::string, str_list, bool> expandMacroStr(ChainValue_t* chainValue, const std::string& extraCode) { |
| 7222 | auto x = chainValue->items.front(); | 7280 | auto x = chainValue->items.front(); |
| 7223 | pushCurrentModule(); // cur | 7281 | pushCurrentModule(); // cur |
| 7224 | int top = lua_gettop(L) - 1; | 7282 | int top = lua_gettop(L) - 1; |
| 7225 | DEFER(lua_settop(L, top)); | 7283 | DEFER(lua_settop(L, top)); |
| 7226 | auto builtinCode = expandMacroChain(chainValue); | 7284 | auto builtinCode = expandMacroChain(chainValue, extraCode); |
| 7227 | if (builtinCode) { | 7285 | if (builtinCode) { |
| 7228 | return {Empty, builtinCode.value(), {}}; | 7286 | return {Empty, builtinCode.value(), {}, false}; |
| 7229 | } // cur res | 7287 | } // cur res |
| 7230 | if (lua_isstring(L, -1) == 0 && lua_istable(L, -1) == 0) { | 7288 | if (lua_isstring(L, -1) == 0 && lua_istable(L, -1) == 0) { |
| 7231 | throw CompileError("macro function must return a string or a table"sv, x); | 7289 | throw CompileError("macro function must return a string or a table"sv, x); |
| 7232 | } // cur res | 7290 | } // cur res |
| 7233 | std::string codes; | 7291 | std::string codes; |
| 7234 | std::string type; | 7292 | std::string type = "yue"s; |
| 7235 | str_list localVars; | 7293 | str_list localVars; |
| 7294 | bool before = false; | ||
| 7236 | if (lua_istable(L, -1) != 0) { // cur tab | 7295 | if (lua_istable(L, -1) != 0) { // cur tab |
| 7237 | lua_getfield(L, -1, "code"); // cur tab code | 7296 | lua_getfield(L, -1, "code"); // cur tab code |
| 7238 | if (lua_isstring(L, -1) != 0) { | 7297 | if (lua_isstring(L, -1) != 0) { |
| @@ -7245,8 +7304,8 @@ private: | |||
| 7245 | if (lua_isstring(L, -1) != 0) { | 7304 | if (lua_isstring(L, -1) != 0) { |
| 7246 | type = lua_tostring(L, -1); | 7305 | type = lua_tostring(L, -1); |
| 7247 | } | 7306 | } |
| 7248 | if (type != "lua"sv && type != "text"sv) { | 7307 | if (type != "yue"sv && type != "lua"sv && type != "text"sv) { |
| 7249 | throw CompileError("macro table must contain field \"type\" of value \"lua\" or \"text\""sv, x); | 7308 | throw CompileError("macro table must contain field \"type\" of value \"yue\", \"lua\" or \"text\""sv, x); |
| 7250 | } | 7309 | } |
| 7251 | lua_pop(L, 1); // cur tab | 7310 | lua_pop(L, 1); // cur tab |
| 7252 | if (type == "text"sv) { | 7311 | if (type == "text"sv) { |
| @@ -7269,20 +7328,26 @@ private: | |||
| 7269 | } | 7328 | } |
| 7270 | lua_pop(L, 1); // cur tab | 7329 | lua_pop(L, 1); // cur tab |
| 7271 | } | 7330 | } |
| 7331 | lua_getfield(L, -1, "before"); // cur tab before | ||
| 7332 | if (lua_toboolean(L, -1) != 0) { | ||
| 7333 | before = true; | ||
| 7334 | } | ||
| 7335 | lua_pop(L, 1); | ||
| 7272 | } else { // cur code | 7336 | } else { // cur code |
| 7273 | codes = lua_tostring(L, -1); | 7337 | codes = lua_tostring(L, -1); |
| 7274 | } | 7338 | } |
| 7275 | Utils::trim(codes); | 7339 | Utils::trim(codes); |
| 7276 | Utils::replace(codes, "\r\n"sv, "\n"sv); | 7340 | Utils::replace(codes, "\r\n"sv, "\n"sv); |
| 7277 | return {type, codes, std::move(localVars)}; | 7341 | return {type, codes, std::move(localVars), before}; |
| 7278 | } | 7342 | } |
| 7279 | 7343 | ||
| 7280 | std::tuple<ast_ptr<false, ast_node>, std::unique_ptr<input>, std::string, str_list> expandMacro(ChainValue_t* chainValue, ExpUsage usage, bool allowBlockMacroReturn) { | 7344 | std::tuple<ast_ptr<false, ast_node>, std::unique_ptr<input>, std::string, str_list, bool> expandMacro(ChainValue_t* chainValue, ExpUsage usage, bool allowBlockMacroReturn, const std::string& extraCode) { |
| 7281 | auto x = ast_to<Callable_t>(chainValue->items.front())->item.to<MacroName_t>(); | 7345 | auto x = ast_to<Callable_t>(chainValue->items.front())->item.to<MacroName_t>(); |
| 7282 | const auto& chainList = chainValue->items.objects(); | 7346 | const auto& chainList = chainValue->items.objects(); |
| 7283 | std::string type, codes; | 7347 | std::string type, codes; |
| 7284 | str_list localVars; | 7348 | str_list localVars; |
| 7285 | std::tie(type, codes, localVars) = expandMacroStr(chainValue); | 7349 | bool before = false; |
| 7350 | std::tie(type, codes, localVars, before) = expandMacroStr(chainValue, extraCode); | ||
| 7286 | bool isBlock = (usage == ExpUsage::Common) && (chainList.size() < 2 || (chainList.size() == 2 && ast_is<Invoke_t, InvokeArgs_t>(chainList.back()))); | 7351 | bool isBlock = (usage == ExpUsage::Common) && (chainList.size() < 2 || (chainList.size() == 2 && ast_is<Invoke_t, InvokeArgs_t>(chainList.back()))); |
| 7287 | ParseInfo info; | 7352 | ParseInfo info; |
| 7288 | if (type == "lua"sv) { | 7353 | if (type == "lua"sv) { |
| @@ -7298,7 +7363,7 @@ private: | |||
| 7298 | codes.insert(0, indent() + "do"s + nl(chainValue)); | 7363 | codes.insert(0, indent() + "do"s + nl(chainValue)); |
| 7299 | codes.append(_newLine + indent() + "end"s + nl(chainValue)); | 7364 | codes.append(_newLine + indent() + "end"s + nl(chainValue)); |
| 7300 | } | 7365 | } |
| 7301 | return {nullptr, nullptr, std::move(codes), std::move(localVars)}; | 7366 | return {nullptr, nullptr, std::move(codes), std::move(localVars), before}; |
| 7302 | } else { | 7367 | } else { |
| 7303 | auto expCode = "return ("s + codes + ')'; | 7368 | auto expCode = "return ("s + codes + ')'; |
| 7304 | if (luaL_loadbuffer(L, expCode.c_str(), expCode.size(), macroChunk.c_str()) != 0) { | 7369 | if (luaL_loadbuffer(L, expCode.c_str(), expCode.size(), macroChunk.c_str()) != 0) { |
| @@ -7320,7 +7385,7 @@ private: | |||
| 7320 | newChain->items.push_back(*it); | 7385 | newChain->items.push_back(*it); |
| 7321 | } | 7386 | } |
| 7322 | } | 7387 | } |
| 7323 | return {exp, nullptr, Empty, std::move(localVars)}; | 7388 | return {exp, nullptr, Empty, std::move(localVars), before}; |
| 7324 | } | 7389 | } |
| 7325 | } else if (type == "text"sv) { | 7390 | } else if (type == "text"sv) { |
| 7326 | if (!isBlock) { | 7391 | if (!isBlock) { |
| @@ -7329,7 +7394,7 @@ private: | |||
| 7329 | if (!codes.empty()) { | 7394 | if (!codes.empty()) { |
| 7330 | codes.append(_newLine); | 7395 | codes.append(_newLine); |
| 7331 | } | 7396 | } |
| 7332 | return {nullptr, nullptr, std::move(codes), std::move(localVars)}; | 7397 | return {nullptr, nullptr, std::move(codes), std::move(localVars), before}; |
| 7333 | } else { | 7398 | } else { |
| 7334 | if (!codes.empty()) { | 7399 | if (!codes.empty()) { |
| 7335 | if (isBlock) { | 7400 | if (isBlock) { |
| @@ -7395,10 +7460,10 @@ private: | |||
| 7395 | auto block = blockEnd->block.get(); | 7460 | auto block = blockEnd->block.get(); |
| 7396 | info.node.set(block); | 7461 | info.node.set(block); |
| 7397 | } | 7462 | } |
| 7398 | return {info.node, std::move(info.codes), Empty, std::move(localVars)}; | 7463 | return {info.node, std::move(info.codes), Empty, std::move(localVars), before}; |
| 7399 | } else { | 7464 | } else { |
| 7400 | if (!isBlock) throw CompileError("failed to expand empty macro as expr"sv, x); | 7465 | if (!isBlock) throw CompileError("failed to expand empty macro as expr"sv, x); |
| 7401 | return {x->new_ptr<Block_t>().get(), std::move(info.codes), Empty, std::move(localVars)}; | 7466 | return {x->new_ptr<Block_t>().get(), std::move(info.codes), Empty, std::move(localVars), before}; |
| 7402 | } | 7467 | } |
| 7403 | } | 7468 | } |
| 7404 | } | 7469 | } |
| @@ -7411,7 +7476,8 @@ private: | |||
| 7411 | std::unique_ptr<input> codes; | 7476 | std::unique_ptr<input> codes; |
| 7412 | std::string luaCodes; | 7477 | std::string luaCodes; |
| 7413 | str_list localVars; | 7478 | str_list localVars; |
| 7414 | std::tie(node, codes, luaCodes, localVars) = expandMacro(chainValue, usage, allowBlockMacroReturn); | 7479 | bool before = false; |
| 7480 | std::tie(node, codes, luaCodes, localVars, before) = expandMacro(chainValue, usage, allowBlockMacroReturn, Empty); | ||
| 7415 | if (!node) { | 7481 | if (!node) { |
| 7416 | out.push_back(luaCodes); | 7482 | out.push_back(luaCodes); |
| 7417 | if (!localVars.empty()) { | 7483 | if (!localVars.empty()) { |
diff --git a/src/yuescript/yue_parser.cpp b/src/yuescript/yue_parser.cpp index 7ea2c97..8ab667d 100644 --- a/src/yuescript/yue_parser.cpp +++ b/src/yuescript/yue_parser.cpp | |||
| @@ -1072,6 +1072,8 @@ YueParser::YueParser() { | |||
| 1072 | ); | 1072 | ); |
| 1073 | MacroInPlace = '$' >> space >> "->" >> space >> Body; | 1073 | MacroInPlace = '$' >> space >> "->" >> space >> Body; |
| 1074 | 1074 | ||
| 1075 | Annotation = "$[" >> space >> UnicodeName >> -(Invoke | InvokeArgs) >> space >> ']'; | ||
| 1076 | |||
| 1075 | must_variable = Variable | and_(LuaKeyword >> not_alpha_num) >> keyword_as_identifier_syntax_error | expected_indentifier_error; | 1077 | must_variable = Variable | and_(LuaKeyword >> not_alpha_num) >> keyword_as_identifier_syntax_error | expected_indentifier_error; |
| 1076 | 1078 | ||
| 1077 | NameList = Seperator >> must_variable >> *(space >> ',' >> space >> must_variable); | 1079 | NameList = Seperator >> must_variable >> *(space >> ',' >> space >> must_variable); |
| @@ -1128,7 +1130,7 @@ YueParser::YueParser() { | |||
| 1128 | StatementAppendix = IfLine | WhileLine | CompFor; | 1130 | StatementAppendix = IfLine | WhileLine | CompFor; |
| 1129 | Statement = ( | 1131 | Statement = ( |
| 1130 | ( | 1132 | ( |
| 1131 | Import | Export | Global | Macro | MacroInPlace | Label | 1133 | Import | Export | Global | Macro | MacroInPlace | Annotation | Label |
| 1132 | ) | ( | 1134 | ) | ( |
| 1133 | Local | While | Repeat | For | Return | | 1135 | Local | While | Repeat | For | Return | |
| 1134 | BreakLoop | Goto | ShortTabAppending | | 1136 | BreakLoop | Goto | ShortTabAppending | |
diff --git a/src/yuescript/yue_parser.h b/src/yuescript/yue_parser.h index 07153fb..cfcbb48 100644 --- a/src/yuescript/yue_parser.h +++ b/src/yuescript/yue_parser.h | |||
| @@ -464,6 +464,7 @@ private: | |||
| 464 | AST_RULE(MacroFunc); | 464 | AST_RULE(MacroFunc); |
| 465 | AST_RULE(Macro); | 465 | AST_RULE(Macro); |
| 466 | AST_RULE(MacroInPlace); | 466 | AST_RULE(MacroInPlace); |
| 467 | AST_RULE(Annotation); | ||
| 467 | AST_RULE(NameOrDestructure); | 468 | AST_RULE(NameOrDestructure); |
| 468 | AST_RULE(AssignableNameList); | 469 | AST_RULE(AssignableNameList); |
| 469 | AST_RULE(InvokeArgs); | 470 | AST_RULE(InvokeArgs); |
