1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
import to_lua from require "yue"
compile_and_run = (code, config = {}) ->
lua_code, err = to_lua code, config
assert.is_nil err
assert.is_not_nil lua_code
chunk, load_err = load lua_code
assert.is_nil load_err
assert.is_not_nil chunk
chunk!
describe "annotation", ->
it "should append generated text after annotated class by default", ->
code = [[
macro ClsDef = (code`ClassDecl) ->
className = code\match "^class%s+(%w+)"
return
type: "text"
before: false
code: "-- after:" .. className
$[ClsDef]
class A
getName: => "A"
return
]]
result, err = to_lua code
assert.is_nil err
assert.is_not_nil result
assert.is_true result\find("__name = \"A\"") != nil
assert.is_true result\find("%-%- after:A") != nil
assert.is_true result\find("__name = \"A\"") < result\find("%-%- after:A")
it "should place generated text before the annotated statement when before is true", ->
code = [[
macro Before = (code`ClassDecl) ->
className = code\match "^class%s+(%w+)"
return
type: "text"
before: true
code: "-- before:" .. className
$[Before]
class B
getName: => "B"
return
]]
result, err = to_lua code
assert.is_nil err
assert.is_not_nil result
assert.is_true result\find("%-%- before:B") != nil
assert.is_true result\find("local B") != nil
assert.is_true result\find("%-%- before:B") < result\find("local B")
it "should support annotation invocation arguments", ->
code = [[
macro Tag = (tag, code`ClassDecl) ->
className = code\match "^class%s+(%w+)"
return
type: "text"
before: false
code: "-- " .. tag .. ":" .. className
$[Tag("entity")]
class C
getName: => "C"
return
]]
result, err = to_lua code
assert.is_nil err
assert.is_not_nil result
assert.is_true result\find("%-%- \"entity\":C") != nil
it "should report an error when annotation is not followed by a statement", ->
code = [[
macro Invalid = (code) -> ""
$[Invalid]
]]
result, err = to_lua code
assert.is_nil result
assert.is_true err\match("annotation must be followed by a statement") != nil
it "should wrap annotated function to validate numeric arguments", ->
code = [[
macro ValidateNumberArgs = (code) ->
funcName = code\match "^(%w+)%s*="
return
type: "text"
before: false
code: table.concat {
"local __orig_#{funcName} = #{funcName}"
"#{funcName} = function(a, b)"
"\tassert(type(a) == \"number\", \"expected number for a\")"
"\tassert(type(b) == \"number\", \"expected number for b\")"
"\treturn __orig_#{funcName}(a, b)"
"end"
}, "\n"
$[ValidateNumberArgs]
add = (a, b) -> a + b
ok, value = pcall -> add 3, 4
bad_ok, bad_err = pcall -> add "3", 4
return ok, value, bad_ok, bad_err
]]
ok, value, bad_ok, bad_err = compile_and_run code
assert.is_true ok
assert.same value, 7
assert.is_false bad_ok
assert.is_true bad_err\match("expected number for a") != nil
it "should wrap annotated function to validate return value", ->
code = [[
macro ValidateNumberReturn = (code) ->
funcName = code\match "^(%w+)%s*="
return
type: "text"
before: false
code: table.concat {
"local __orig_#{funcName} = #{funcName}"
"#{funcName} = function(...)"
"\tlocal result = __orig_#{funcName}(...)"
"\tassert(type(result) == \"number\", \"expected numeric return\")"
"\treturn result"
"end"
}, "\n"
$[ValidateNumberReturn]
toText = (value) -> tostring value
ok, err = pcall -> toText 42
return ok, err
]]
ok, err = compile_and_run code
assert.is_false ok
assert.is_true err\match("expected numeric return") != nil
it "should use annotation arguments to register annotated classes", ->
code = [[
macro Register = (registry, code`ClassDecl) ->
className = code\match "^class%s+(%w+)"
return
type: "text"
before: false
code: "#{registry}[\"#{className}\"] = #{className}"
registry = {}
$[Register(registry)]
class Worker
run: => "ok"
return registry.Worker != nil, registry.Worker!\run!
]]
exists, result = compile_and_run code
assert.is_true exists
assert.same result, "ok"
|