aboutsummaryrefslogtreecommitdiff
path: root/spec/outputs/test/annotation_spec.lua
blob: 866b9dc0cbbf3a270703e9845982a30f88d3db3d (plain)
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
161
162
local to_lua
do
	local _obj_0 = require("yue")
	to_lua = _obj_0.to_lua
end
local compile_and_run
compile_and_run = function(code, config)
	if config == nil then
		config = { }
	end
	local lua_code, err = to_lua(code, config)
	assert.is_nil(err)
	assert.is_not_nil(lua_code)
	local chunk, load_err = load(lua_code)
	assert.is_nil(load_err)
	assert.is_not_nil(chunk)
	return chunk()
end
return describe("annotation", function()
	it("should append generated text after annotated class by default", function()
		local 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
]]
		local 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)
		return assert.is_true(result:find("__name = \"A\"") < result:find("%-%- after:A"))
	end)
	it("should place generated text before the annotated statement when before is true", function()
		local 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
]]
		local 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)
		return assert.is_true(result:find("%-%- before:B") < result:find("local B"))
	end)
	it("should support annotation invocation arguments", function()
		local 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
]]
		local result, err = to_lua(code)
		assert.is_nil(err)
		assert.is_not_nil(result)
		return assert.is_true(result:find("%-%- \"entity\":C") ~= nil)
	end)
	it("should report an error when annotation is not followed by a statement", function()
		local code = [[macro Invalid = (code) -> ""
$[Invalid]
]]
		local result, err = to_lua(code)
		assert.is_nil(result)
		return assert.is_true(err:match("annotation must be followed by a statement") ~= nil)
	end)
	it("should wrap annotated function to validate numeric arguments", function()
		local 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
]]
		local ok, value, bad_ok, bad_err = compile_and_run(code)
		assert.is_true(ok)
		assert.same(value, 7)
		assert.is_false(bad_ok)
		return assert.is_true(bad_err:match("expected number for a") ~= nil)
	end)
	it("should wrap annotated function to validate return value", function()
		local 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
]]
		local ok, err = compile_and_run(code)
		assert.is_false(ok)
		return assert.is_true(err:match("expected numeric return") ~= nil)
	end)
	return it("should use annotation arguments to register annotated classes", function()
		local 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!
]]
		local exists, result = compile_and_run(code)
		assert.is_true(exists)
		return assert.same(result, "ok")
	end)
end)