Skip to content

Commit ad0cd3a

Browse files
committed
feat: add m_end position to AST nodes
- Add end line and column (m_end.m_line, m_end.m_col) to AST output - New AST format: [name, begin_line, begin_col, end_line, end_col, ...children] - Update format_spec.yue to normalize end positions for comparison - Add ast_spec.yue tests for AST end position feature Closes #251
1 parent ffdbbbd commit ad0cd3a

5 files changed

Lines changed: 198 additions & 6 deletions

File tree

spec/inputs/test/ast_spec.yue

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
yue = require "yue"
2+
3+
describe "yue.to_ast", ->
4+
it "should return AST with end position for simple expression", ->
5+
ast = yue.to_ast "x = 1"
6+
assert.is_not_nil ast
7+
-- ast format: [name, begin_line, begin_col, end_line, end_col, ...children]
8+
assert.same ast[1], "File"
9+
assert.same ast[2], 1 -- begin line
10+
assert.same ast[3], 1 -- begin col
11+
assert.is_number ast[4] -- end line
12+
assert.is_number ast[5] -- end col
13+
14+
it "should have correct end position for leaf nodes", ->
15+
ast = yue.to_ast "1"
16+
assert.is_not_nil ast
17+
-- Leaf node with no children should have format: [name, begin_line, begin_col, end_line, end_col, value]
18+
assert.same ast[1], "File"
19+
assert.same ast[2], 1
20+
assert.same ast[3], 1
21+
assert.is_number ast[4]
22+
assert.is_number ast[5]
23+
24+
it "should have end position for multi-line code", ->
25+
code = [[
26+
x = 1
27+
y = 2]]
28+
ast = yue.to_ast code
29+
assert.is_not_nil ast
30+
assert.same ast[1], "File"
31+
assert.same ast[2], 1
32+
assert.same ast[3], 1
33+
-- End position should be on line 2
34+
assert.is_true ast[4] >= 2
35+
36+
it "should have end position for function definition", ->
37+
code = [[
38+
add = (a, b) ->
39+
a + b]]
40+
ast = yue.to_ast code
41+
assert.is_not_nil ast
42+
assert.same ast[1], "File"
43+
assert.same ast[2], 1
44+
assert.same ast[3], 1
45+
assert.is_number ast[4]
46+
assert.is_number ast[5]
47+
48+
it "should have end position for table literal", ->
49+
ast = yue.to_ast "{a: 1, b: 2}"
50+
assert.is_not_nil ast
51+
assert.same ast[1], "File"
52+
assert.same ast[2], 1
53+
assert.same ast[3], 1
54+
assert.is_number ast[4]
55+
assert.is_number ast[5]
56+
57+
it "should have end position for class definition", ->
58+
code = [[
59+
class Person
60+
new: (@name) =>
61+
getName: => @name]]
62+
ast = yue.to_ast code
63+
assert.is_not_nil ast
64+
assert.same ast[1], "File"
65+
assert.same ast[2], 1
66+
assert.same ast[3], 1
67+
assert.is_true ast[4] >= 3
68+
69+
it "should return nil and error message for invalid syntax", ->
70+
ast, err = yue.to_ast "if then else"
71+
assert.is_nil ast
72+
assert.is_string err
73+
74+
it "should support flatten level parameter", ->
75+
ast = yue.to_ast "x = 1", 0
76+
assert.is_not_nil ast
77+
assert.same ast[1], "File"
78+
assert.is_number ast[4]
79+
assert.is_number ast[5]
80+
81+
it "should have end position in nested structures", ->
82+
code = "x = [i for i = 1, 10]"
83+
ast = yue.to_ast code
84+
assert.is_not_nil ast
85+
assert.same ast[1], "File"
86+
assert.same ast[2], 1
87+
assert.same ast[3], 1
88+
assert.is_number ast[4]
89+
assert.is_number ast[5]
90+
-- End column should reflect the end of the expression
91+
assert.is_true ast[5] > 1

spec/inputs/test/format_spec.yue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ import "yue"
165165
rewriteLineCol = (item)->
166166
item[2] = 0
167167
item[3] = 0
168-
for i = 4, #item
168+
item[4] = 0
169+
item[5] = 0
170+
for i = 6, #item
169171
switch type item[i] when "table"
170172
if item[i][1] == "comment"
171173
table.remove item, i

spec/outputs/test/ast_spec.lua

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
local yue = require("yue")
2+
return describe("yue.to_ast", function()
3+
it("should return AST with end position for simple expression", function()
4+
local ast = yue.to_ast("x = 1")
5+
assert.is_not_nil(ast)
6+
assert.same(ast[1], "File")
7+
assert.same(ast[2], 1)
8+
assert.same(ast[3], 1)
9+
assert.is_number(ast[4])
10+
return assert.is_number(ast[5])
11+
end)
12+
it("should have correct end position for leaf nodes", function()
13+
local ast = yue.to_ast("1")
14+
assert.is_not_nil(ast)
15+
assert.same(ast[1], "File")
16+
assert.same(ast[2], 1)
17+
assert.same(ast[3], 1)
18+
assert.is_number(ast[4])
19+
return assert.is_number(ast[5])
20+
end)
21+
it("should have end position for multi-line code", function()
22+
local code = [[x = 1
23+
y = 2]]
24+
local ast = yue.to_ast(code)
25+
assert.is_not_nil(ast)
26+
assert.same(ast[1], "File")
27+
assert.same(ast[2], 1)
28+
assert.same(ast[3], 1)
29+
return assert.is_true(ast[4] >= 2)
30+
end)
31+
it("should have end position for function definition", function()
32+
local code = [[add = (a, b) ->
33+
a + b]]
34+
local ast = yue.to_ast(code)
35+
assert.is_not_nil(ast)
36+
assert.same(ast[1], "File")
37+
assert.same(ast[2], 1)
38+
assert.same(ast[3], 1)
39+
assert.is_number(ast[4])
40+
return assert.is_number(ast[5])
41+
end)
42+
it("should have end position for table literal", function()
43+
local ast = yue.to_ast("{a: 1, b: 2}")
44+
assert.is_not_nil(ast)
45+
assert.same(ast[1], "File")
46+
assert.same(ast[2], 1)
47+
assert.same(ast[3], 1)
48+
assert.is_number(ast[4])
49+
return assert.is_number(ast[5])
50+
end)
51+
it("should have end position for class definition", function()
52+
local code = [[class Person
53+
new: (@name) =>
54+
getName: => @name]]
55+
local ast = yue.to_ast(code)
56+
assert.is_not_nil(ast)
57+
assert.same(ast[1], "File")
58+
assert.same(ast[2], 1)
59+
assert.same(ast[3], 1)
60+
return assert.is_true(ast[4] >= 3)
61+
end)
62+
it("should return nil and error message for invalid syntax", function()
63+
local ast, err = yue.to_ast("if then else")
64+
assert.is_nil(ast)
65+
return assert.is_string(err)
66+
end)
67+
it("should support flatten level parameter", function()
68+
local ast = yue.to_ast("x = 1", 0)
69+
assert.is_not_nil(ast)
70+
assert.same(ast[1], "File")
71+
assert.is_number(ast[4])
72+
return assert.is_number(ast[5])
73+
end)
74+
return it("should have end position in nested structures", function()
75+
local code = "x = [i for i = 1, 10]"
76+
local ast = yue.to_ast(code)
77+
assert.is_not_nil(ast)
78+
assert.same(ast[1], "File")
79+
assert.same(ast[2], 1)
80+
assert.same(ast[3], 1)
81+
assert.is_number(ast[4])
82+
assert.is_number(ast[5])
83+
return assert.is_true(ast[5] > 1)
84+
end)
85+
end)

spec/outputs/test/format_spec.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ local rewriteLineCol
164164
rewriteLineCol = function(item)
165165
item[2] = 0
166166
item[3] = 0
167-
for i = 4, #item do
167+
item[4] = 0
168+
item[5] = 0
169+
for i = 6, #item do
168170
local _exp_0 = type(item[i])
169171
if "table" == _exp_0 then
170172
if item[i][1] == "comment" then

src/yuescript/yuescript.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,18 +380,22 @@ static int yuetoast(lua_State* L) {
380380
int count = current.children ? static_cast<int>(current.children->size()) : 0;
381381
switch (count) {
382382
case 0: {
383-
lua_createtable(L, 4, 0);
383+
lua_createtable(L, 6, 0);
384384
getName(node);
385385
lua_rawseti(L, -2, 1);
386386
lua_pushinteger(L, node->m_begin.m_line);
387387
lua_rawseti(L, -2, 2);
388388
lua_pushinteger(L, node->m_begin.m_col);
389389
lua_rawseti(L, -2, 3);
390+
lua_pushinteger(L, node->m_end.m_line);
391+
lua_rawseti(L, -2, 4);
392+
lua_pushinteger(L, node->m_end.m_col);
393+
lua_rawseti(L, -2, 5);
390394
formatter.indent = 0;
391395
auto str = node->to_string(&formatter);
392396
yue::Utils::trim(str);
393397
lua_pushlstring(L, str.c_str(), str.length());
394-
lua_rawseti(L, -2, 4);
398+
lua_rawseti(L, -2, 6);
395399
lua_rawseti(L, tableIndex, static_cast<int>(lua_objlen(L, tableIndex)) + 1);
396400
break;
397401
}
@@ -404,21 +408,29 @@ static int yuetoast(lua_State* L) {
404408
lua_rawseti(L, -2, 2);
405409
lua_pushinteger(L, node->m_begin.m_col);
406410
lua_rawseti(L, -2, 3);
411+
lua_pushinteger(L, node->m_end.m_line);
412+
lua_rawseti(L, -2, 4);
413+
lua_pushinteger(L, node->m_end.m_col);
414+
lua_rawseti(L, -2, 5);
407415
lua_pop(L, 1);
408416
break;
409417
}
410418
[[fallthrough]];
411419
}
412420
default: {
413421
auto len = static_cast<int>(lua_objlen(L, tableIndex));
414-
lua_createtable(L, count + 3, 0);
422+
lua_createtable(L, count + 5, 0);
415423
getName(node);
416424
lua_rawseti(L, -2, 1);
417425
lua_pushinteger(L, node->m_begin.m_line);
418426
lua_rawseti(L, -2, 2);
419427
lua_pushinteger(L, node->m_begin.m_col);
420428
lua_rawseti(L, -2, 3);
421-
for (int i = count, j = 4; i >= 1; i--, j++) {
429+
lua_pushinteger(L, node->m_end.m_line);
430+
lua_rawseti(L, -2, 4);
431+
lua_pushinteger(L, node->m_end.m_col);
432+
lua_rawseti(L, -2, 5);
433+
for (int i = count, j = 6; i >= 1; i--, j++) {
422434
lua_rawgeti(L, tableIndex, len - i + 1);
423435
lua_rawseti(L, -2, j);
424436
}

0 commit comments

Comments
 (0)