Skip to content

Commit 4bcb926

Browse files
committed
feat(parser): add support for Lua 5.4 attributes and androlua 5.3 syntax, close #1
- Implement initial support for parsing attributes in Lua 5.4, including handling of the 'attrib' production rule. - Introduce special syntax handling for androlua 5.3, specifically for 'when' and 'switch' statements and local variables with '$' prefix. - Adjust string building in AST2Lua for consistent spacing around the 'attributeName' field.
1 parent b8c9862 commit 4bcb926

File tree

7 files changed

+153
-66
lines changed

7 files changed

+153
-66
lines changed

src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/LuaParser.kt

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import kotlin.properties.Delegates
1818
* @description:
1919
**/
2020
class LuaParser(
21-
private val luaVersion: LuaVersion = LuaVersion.LUA_5_4,
21+
private val luaVersion: LuaVersion = LuaVersion.ANDROLUA_5_3,
2222
private val errorRecovery: Boolean = true,
2323
) {
2424
private var lexer by Delegates.notNull<WrapperLuaLexer>()
@@ -77,7 +77,7 @@ class LuaParser(
7777
private fun ignoreToken(advanceToken: LuaTokenTypes): Boolean {
7878
return when (advanceToken) {
7979
LuaTokenTypes.WHITE_SPACE, LuaTokenTypes.NEW_LINE
80-
-> true
80+
-> true
8181

8282
else -> false
8383
}
@@ -146,6 +146,12 @@ class LuaParser(
146146
} else error(message)
147147
}
148148

149+
private inline fun assertVersion(version: LuaVersion, crossinline messageBuilder: () -> String) {
150+
if (luaVersion != version) {
151+
error(messageBuilder())
152+
}
153+
}
154+
149155
private fun markLocation() {
150156
locations.addFirst(
151157
Position(
@@ -234,20 +240,36 @@ class LuaParser(
234240
}
235241
}
236242

237-
peekToken(LuaTokenTypes.WHEN) -> parseWhenStatement(blockNode)
243+
peekToken(LuaTokenTypes.WHEN) -> {
244+
assertVersion(LuaVersion.ANDROLUA_5_3) {
245+
"when statement is only supported in androlua 5.3"
246+
}
247+
parseWhenStatement(blockNode)
248+
}
249+
238250
peekToken(LuaTokenTypes.IF) -> parseIfStatement(blockNode)
239-
peekToken(LuaTokenTypes.SWITCH) -> parseSwitchStatement(blockNode)
251+
peekToken(LuaTokenTypes.SWITCH) -> {
252+
assertVersion(LuaVersion.ANDROLUA_5_3) {
253+
"switch statement is only supported in androlua 5.3"
254+
}
255+
parseSwitchStatement(blockNode)
256+
}
257+
240258
consumeToken(LuaTokenTypes.GOTO) -> parseGotoStatement(blockNode)
241259
consumeToken(LuaTokenTypes.DOUBLE_COLON) -> parseLabelStatement(blockNode)
242260
consumeToken(LuaTokenTypes.SEMI) -> continue
243261
consumeToken(LuaTokenTypes.DO) -> parseDoStatement(blockNode)
244262
// function call, varlist = explist, $(localvarlist)
245263
peekToken(LuaTokenTypes.NAME) -> {
246264
val name = peek { lexerText() }
247-
if (name.startsWith('$'))
265+
if (name.startsWith('$')) {
266+
assertVersion(LuaVersion.ANDROLUA_5_3) {
267+
"local variables with prefix $ are not supported in this version"
268+
}
248269
parseLocalVarList(blockNode)
249-
else
270+
} else {
250271
parseExpStatement(blockNode)
272+
}
251273
}
252274

253275
peekToken(LuaTokenTypes.RETURN) -> parseReturnStatement(blockNode)
@@ -759,6 +781,52 @@ class LuaParser(
759781
return finishNode(identifier)
760782
}
761783

784+
// attrib ::= [‘<’ Name ‘>’]
785+
// Name attrib
786+
private fun parseAttribute(parent: BaseASTNode): AttributeIdentifier {
787+
val result = AttributeIdentifier()
788+
result.parent = parent
789+
790+
// parse name
791+
val name = parseName(result)
792+
793+
result.name = name.name
794+
795+
// check <
796+
if (!consumeToken(LuaTokenTypes.LT)) {
797+
return result
798+
}
799+
800+
val attributeName = parseName(result)
801+
802+
result.attributeName = attributeName.name
803+
804+
expectToken(LuaTokenTypes.GT) { "'>' expected near ${lexerText()}" }
805+
806+
return result
807+
}
808+
809+
// attnamelist ::= Name attrib {‘,’ Name attrib}
810+
private fun parseAttrNameList(parent: BaseASTNode): List<AttributeIdentifier> {
811+
val result = mutableListOf<AttributeIdentifier>()
812+
813+
result.add(parseAttribute(parent))
814+
815+
val hasComma = consumeToken(LuaTokenTypes.COMMA)
816+
817+
if (!hasComma) {
818+
return result
819+
}
820+
var nameNode = parseAttribute(parent)
821+
while (true) {
822+
result.add(nameNode)
823+
if (!consumeToken(LuaTokenTypes.COMMA)) break
824+
nameNode = parseAttribute(parent)
825+
}
826+
827+
return result
828+
}
829+
762830
// namelist ::= Name {‘,’ Name}
763831
private fun parseNameList(parent: BaseASTNode, supportDollarSymbol: Boolean = false): List<Identifier> {
764832
val result = mutableListOf<Identifier>()
@@ -1280,12 +1348,18 @@ class LuaParser(
12801348
return result
12811349
}
12821350

1283-
// local namelist [‘=’ explist]
1351+
// local namelist [‘=’ explist] (lua 5.3)
1352+
// local attnamelist [‘=’ explist] (lua 5.4)
12841353
private fun parseLocalVarList(parent: BaseASTNode): LocalStatement {
12851354
val localStatement = LocalStatement()
12861355
localStatement.parent = parent
12871356
markLocation()
1288-
localStatement.init.addAll(parseNameList(localStatement, true))
1357+
1358+
localStatement.init.addAll(
1359+
if (luaVersion === LuaVersion.LUA_5_4)
1360+
parseAttrNameList(localStatement)
1361+
else parseNameList(localStatement, luaVersion === LuaVersion.ANDROLUA_5_3)
1362+
)
12891363
localStatement.init.forEach {
12901364
it.isLocal = true
12911365
}

src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/ast/node/expressionNode.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import kotlin.properties.Delegates
1111
* @date: 2021/10/7 10:48
1212
* @description:
1313
**/
14-
class Identifier(var name: String = "") : ExpressionNode, ASTNode() {
15-
var isLocal = false
14+
open class Identifier(open var name: String = "") : ExpressionNode, ASTNode() {
15+
open var isLocal = false
1616

1717
override fun toString(): String {
1818
return "Identifier(name='$name')"
@@ -30,6 +30,26 @@ class Identifier(var name: String = "") : ExpressionNode, ASTNode() {
3030

3131
}
3232

33+
/**
34+
* @author: dingyi
35+
* @date: 2024/9/19 18:04
36+
* @description:
37+
**/
38+
class AttributeIdentifier(
39+
override var name: String = "",
40+
var attributeName: String? = null
41+
) : Identifier(name) {
42+
override var isLocal = true
43+
override fun <T> accept(visitor: ASTVisitor<T>, value: T) {
44+
visitor.visitAttributeIdentifier(this, value)
45+
}
46+
47+
override fun clone(): AttributeIdentifier {
48+
return AttributeIdentifier(name = name, attributeName = attributeName).also {
49+
it.isLocal = true
50+
}
51+
}
52+
}
3353

3454
/**
3555
* @author: dingyi

src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/ast/visitor/visitor.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ interface ASTVisitor<T> {
182182
is CallExpression -> visitCallExpression(node, value)
183183
is BinaryExpression -> visitBinaryExpression(node, value)
184184
is UnaryExpression -> visitUnaryExpression(node, value)
185+
is AttributeIdentifier -> visitAttributeIdentifier(node, value)
185186
is Identifier -> visitIdentifier(node, value)
186187
is ConstantNode -> visitConstantNode(node, value)
187188
is LambdaDeclaration -> visitLambdaDeclaration(node, value)
@@ -198,7 +199,9 @@ interface ASTVisitor<T> {
198199

199200
fun visitIdentifiers(list: List<Identifier>, value: T) {
200201
list.forEach {
201-
visitIdentifier(it, value)
202+
if (it is AttributeIdentifier)
203+
visitAttributeIdentifier(it, value)
204+
else visitIdentifier(it, value)
202205
}
203206
}
204207

@@ -301,6 +304,8 @@ interface ASTVisitor<T> {
301304
fun visitCommentStatement(commentStatement: CommentStatement, value: T) {
302305

303306
}
307+
308+
fun visitAttributeIdentifier(identifier: AttributeIdentifier, value: T)
304309
}
305310

306311
interface ASTModifier<T> {
@@ -473,7 +478,11 @@ interface ASTModifier<T> {
473478

474479
fun visitLocalStatement(node: LocalStatement, value: T): LocalStatement {
475480
node.init.forEachIndexed { index, identifier ->
476-
node.init[index] = visitIdentifier(identifier, value).also {
481+
node.init[index] = run {
482+
if (identifier is AttributeIdentifier)
483+
visitIdentifier(identifier, value)
484+
else visitIdentifier(identifier, value)
485+
}.also {
477486
it.parent = node
478487
}
479488
}
@@ -592,6 +601,7 @@ interface ASTModifier<T> {
592601
is CallExpression -> visitCallExpression(node, value)
593602
is BinaryExpression -> visitBinaryExpression(node, value)
594603
is UnaryExpression -> visitUnaryExpression(node, value)
604+
is AttributeIdentifier -> visitAttributeIdentifier(node, value)
595605
is Identifier -> visitIdentifier(node, value)
596606
is ConstantNode -> visitConstantNode(node, value)
597607
is LambdaDeclaration -> visitLambdaDeclaration(node, value)
@@ -776,4 +786,8 @@ interface ASTModifier<T> {
776786

777787
return node
778788
}
789+
790+
fun visitAttributeIdentifier(node: AttributeIdentifier, value: T): AttributeIdentifier {
791+
return node
792+
}
779793
}

src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/SemanticAnalyzer.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ class SemanticAnalyzer : ASTVisitor<BaseASTNode> {
6161
destroyScope()
6262
}
6363

64+
override fun visitAttributeIdentifier(
65+
identifier: AttributeIdentifier,
66+
value: BaseASTNode
67+
) {
68+
TODO("Lua 5.4 attribute")
69+
}
70+
6471
override fun visitDoStatement(node: DoStatement, value: BaseASTNode) {
6572
createFunctionScope(node)
6673
super.visitDoStatement(node, value)

src/commonMain/kotlin/io/github/dingyi222666/luaparser/source/AST2Lua.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class AST2Lua : ASTVisitor<StringBuilder> {
1616
var indentSize = 4
1717

1818
private fun indent(): String {
19-
return " ".repeat(indentSize).repeat(max(currentDepth,0))
19+
return " ".repeat(indentSize).repeat(max(currentDepth, 0))
2020
}
2121

2222
private fun indent(sb: StringBuilder) {
@@ -91,7 +91,6 @@ class AST2Lua : ASTVisitor<StringBuilder> {
9191
}
9292

9393

94-
9594
override fun visitFunctionDeclaration(node: FunctionDeclaration, value: StringBuilder) {
9695
value.append("function ")
9796
node.identifier?.let { visitExpressionNode(it, value) }
@@ -414,4 +413,17 @@ class AST2Lua : ASTVisitor<StringBuilder> {
414413
ExpressionOperator.GETLEN -> "#"
415414
}
416415
}
416+
417+
override fun visitAttributeIdentifier(identifier: AttributeIdentifier, value: StringBuilder) {
418+
value.append(identifier.name)
419+
420+
val attributeName = identifier.attributeName
421+
422+
if (attributeName != null) {
423+
value.append(" ")
424+
value.append("<")
425+
value.append(attributeName)
426+
value.append(">")
427+
}
428+
}
417429
}

src/commonTest/kotlin/parser.common.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io.github.dingyi222666.luaparser.parser.LuaParser
2+
import io.github.dingyi222666.luaparser.parser.LuaVersion
23
import io.github.dingyi222666.luaparser.source.AST2Lua
34
import io.github.dingyi222666.luaparser.util.parseLuaString
45
import kotlin.test.Test
@@ -18,6 +19,17 @@ import kotlin.test.Test
1819

1920
println(AST2Lua().asCode(root))
2021
}
22+
23+
@Test
24+
fun parseLua54() {
25+
val parser = LuaParser(LuaVersion.LUA_5_4)
26+
27+
val root = parser.parse("""
28+
local apple <const>, carrot = 'fruit', 'vegetable'
29+
""".trimIndent())
30+
31+
println(AST2Lua().asCode(root))
32+
}
2133
}
2234

2335
const val source = """

src/jvmTest/kotlin/benchmark.jvm.kt

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)