aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt5
-rw-r--r--spec/inputs/annotation.yue27
-rw-r--r--spec/inputs/annotation_before.yue16
-rw-r--r--spec/inputs/test/annotation_spec.yue160
-rw-r--r--spec/inputs/test/format_spec.yue4
-rw-r--r--spec/outputs/annotation.lua48
-rw-r--r--spec/outputs/annotation_before.lua55
-rw-r--r--spec/outputs/test/annotation_spec.lua162
-rw-r--r--spec/outputs/test/format_spec.lua3
-rw-r--r--src/yuescript/yue_ast.cpp3
-rw-r--r--src/yuescript/yue_ast.h9
-rw-r--r--src/yuescript/yue_compiler.cpp114
-rw-r--r--src/yuescript/yue_parser.cpp4
-rw-r--r--src/yuescript/yue_parser.h1
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)
31elseif (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)
31elseif (LUA_VERSION_STRING MATCHES "Lua 5.4") 36elseif (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 @@
1macro 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]
15class 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
22a = A!
23res = a::setAdd 1, 2
24print(a.x, a.y, a.y, res)
25
26return
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 @@
1macro 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")]
9class B
10 getTag: => "before"
11
12$[Tag("after")]
13class C
14 getTag: => "after"
15
16return 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 @@
1import to_lua from require "yue"
2
3compile_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
12describe "annotation", ->
13 it "should append generated text after annotated class by default", ->
14 code = [[
15macro 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]
23class A
24 getName: => "A"
25
26return
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 = [[
37macro 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]
45class B
46 getName: => "B"
47
48return
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 = [[
59macro 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")]
67class C
68 getName: => "C"
69
70return
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 = [[
79macro 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 = [[
88macro 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]
103add = (a, b) -> a + b
104
105ok, value = pcall -> add 3, 4
106bad_ok, bad_err = pcall -> add "3", 4
107return 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 = [[
117macro 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]
132toText = (value) -> tostring value
133
134ok, err = pcall -> toText 42
135return 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 = [[
143macro 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
150registry = {}
151
152$[Register(registry)]
153class Worker
154 run: => "ok"
155
156return 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 @@
1local A
2do
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
37end
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
45local a = A()
46local res = a:setAdd(1, 2)
47print(a.x, a.y, a.y, res)
48return
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 @@
1local B
2do
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
26end
27-- "before":B
28local C
29do
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
53end
54-- "after":C
55return 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 @@
1local to_lua
2do
3 local _obj_0 = require("yue")
4 to_lua = _obj_0.to_lua
5end
6local compile_and_run
7compile_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()
18end
19return 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]
29class A
30 getName: => "A"
31
32return
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]
50class B
51 getName: => "B"
52
53return
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")]
71class C
72 getName: => "C"
73
74return
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]
105add = (a, b) -> a + b
106
107ok, value = pcall -> add 3, 4
108bad_ok, bad_err = pcall -> add "3", 4
109return 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]
133toText = (value) -> tostring value
134
135ok, err = pcall -> toText 42
136return 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
150registry = {}
151
152$[Register(registry)]
153class Worker
154 run: => "ok"
155
156return 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)
162end)
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 {
1530std::string Macro_t::to_string(void* ud) const { 1530std::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}
1533std::string Annotation_t::to_string(void* ud) const {
1534 return "$["s + name->to_string(ud) + (invoke ? invoke->to_string(ud) : ""s) + "]"s;
1535}
1533std::string MacroInPlace_t::to_string(void* ud) const { 1536std::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)
854AST_END(Macro) 854AST_END(Macro)
855 855
856AST_NODE(Annotation)
857 ast_ptr<true, UnicodeName_t> name;
858 ast_sel<false, Invoke_t, InvokeArgs_t> invoke;
859 AST_MEMBER(Annotation, &name, &invoke)
860AST_END(Annotation)
861
856AST_NODE(NameOrDestructure) 862AST_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)
961AST_NODE(Statement) 967AST_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
81const std::string_view version = "0.33.10"sv; 81const std::string_view version = "0.34.0"sv;
82const std::string_view extension = "yue"sv; 82const std::string_view extension = "yue"sv;
83 83
84class CompileError : public std::logic_error { 84class 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);