Skip to content

Commit

Permalink
feat(parser): add support for Lua 5.4 attributes and androlua 5.3 syn…
Browse files Browse the repository at this point in the history
…tax, 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.
  • Loading branch information
dingyi222666 committed Sep 19, 2024
1 parent b8c9862 commit 4bcb926
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import kotlin.properties.Delegates
* @description:
**/
class LuaParser(
private val luaVersion: LuaVersion = LuaVersion.LUA_5_4,
private val luaVersion: LuaVersion = LuaVersion.ANDROLUA_5_3,
private val errorRecovery: Boolean = true,
) {
private var lexer by Delegates.notNull<WrapperLuaLexer>()
Expand Down Expand Up @@ -77,7 +77,7 @@ class LuaParser(
private fun ignoreToken(advanceToken: LuaTokenTypes): Boolean {
return when (advanceToken) {
LuaTokenTypes.WHITE_SPACE, LuaTokenTypes.NEW_LINE
-> true
-> true

else -> false
}
Expand Down Expand Up @@ -146,6 +146,12 @@ class LuaParser(
} else error(message)
}

private inline fun assertVersion(version: LuaVersion, crossinline messageBuilder: () -> String) {
if (luaVersion != version) {
error(messageBuilder())
}
}

private fun markLocation() {
locations.addFirst(
Position(
Expand Down Expand Up @@ -234,20 +240,36 @@ class LuaParser(
}
}

peekToken(LuaTokenTypes.WHEN) -> parseWhenStatement(blockNode)
peekToken(LuaTokenTypes.WHEN) -> {
assertVersion(LuaVersion.ANDROLUA_5_3) {
"when statement is only supported in androlua 5.3"
}
parseWhenStatement(blockNode)
}

peekToken(LuaTokenTypes.IF) -> parseIfStatement(blockNode)
peekToken(LuaTokenTypes.SWITCH) -> parseSwitchStatement(blockNode)
peekToken(LuaTokenTypes.SWITCH) -> {
assertVersion(LuaVersion.ANDROLUA_5_3) {
"switch statement is only supported in androlua 5.3"
}
parseSwitchStatement(blockNode)
}

consumeToken(LuaTokenTypes.GOTO) -> parseGotoStatement(blockNode)
consumeToken(LuaTokenTypes.DOUBLE_COLON) -> parseLabelStatement(blockNode)
consumeToken(LuaTokenTypes.SEMI) -> continue
consumeToken(LuaTokenTypes.DO) -> parseDoStatement(blockNode)
// function call, varlist = explist, $(localvarlist)
peekToken(LuaTokenTypes.NAME) -> {
val name = peek { lexerText() }
if (name.startsWith('$'))
if (name.startsWith('$')) {
assertVersion(LuaVersion.ANDROLUA_5_3) {
"local variables with prefix $ are not supported in this version"
}
parseLocalVarList(blockNode)
else
} else {
parseExpStatement(blockNode)
}
}

peekToken(LuaTokenTypes.RETURN) -> parseReturnStatement(blockNode)
Expand Down Expand Up @@ -759,6 +781,52 @@ class LuaParser(
return finishNode(identifier)
}

// attrib ::= [‘<’ Name ‘>’]
// Name attrib
private fun parseAttribute(parent: BaseASTNode): AttributeIdentifier {
val result = AttributeIdentifier()
result.parent = parent

// parse name
val name = parseName(result)

result.name = name.name

// check <
if (!consumeToken(LuaTokenTypes.LT)) {
return result
}

val attributeName = parseName(result)

result.attributeName = attributeName.name

expectToken(LuaTokenTypes.GT) { "'>' expected near ${lexerText()}" }

return result
}

// attnamelist ::= Name attrib {‘,’ Name attrib}
private fun parseAttrNameList(parent: BaseASTNode): List<AttributeIdentifier> {
val result = mutableListOf<AttributeIdentifier>()

result.add(parseAttribute(parent))

val hasComma = consumeToken(LuaTokenTypes.COMMA)

if (!hasComma) {
return result
}
var nameNode = parseAttribute(parent)
while (true) {
result.add(nameNode)
if (!consumeToken(LuaTokenTypes.COMMA)) break
nameNode = parseAttribute(parent)
}

return result
}

// namelist ::= Name {‘,’ Name}
private fun parseNameList(parent: BaseASTNode, supportDollarSymbol: Boolean = false): List<Identifier> {
val result = mutableListOf<Identifier>()
Expand Down Expand Up @@ -1280,12 +1348,18 @@ class LuaParser(
return result
}

// local namelist [‘=’ explist]
// local namelist [‘=’ explist] (lua 5.3)
// local attnamelist [‘=’ explist] (lua 5.4)
private fun parseLocalVarList(parent: BaseASTNode): LocalStatement {
val localStatement = LocalStatement()
localStatement.parent = parent
markLocation()
localStatement.init.addAll(parseNameList(localStatement, true))

localStatement.init.addAll(
if (luaVersion === LuaVersion.LUA_5_4)
parseAttrNameList(localStatement)
else parseNameList(localStatement, luaVersion === LuaVersion.ANDROLUA_5_3)
)
localStatement.init.forEach {
it.isLocal = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import kotlin.properties.Delegates
* @date: 2021/10/7 10:48
* @description:
**/
class Identifier(var name: String = "") : ExpressionNode, ASTNode() {
var isLocal = false
open class Identifier(open var name: String = "") : ExpressionNode, ASTNode() {
open var isLocal = false

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

}

/**
* @author: dingyi
* @date: 2024/9/19 18:04
* @description:
**/
class AttributeIdentifier(
override var name: String = "",
var attributeName: String? = null
) : Identifier(name) {
override var isLocal = true
override fun <T> accept(visitor: ASTVisitor<T>, value: T) {
visitor.visitAttributeIdentifier(this, value)
}

override fun clone(): AttributeIdentifier {
return AttributeIdentifier(name = name, attributeName = attributeName).also {
it.isLocal = true
}
}
}

/**
* @author: dingyi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ interface ASTVisitor<T> {
is CallExpression -> visitCallExpression(node, value)
is BinaryExpression -> visitBinaryExpression(node, value)
is UnaryExpression -> visitUnaryExpression(node, value)
is AttributeIdentifier -> visitAttributeIdentifier(node, value)
is Identifier -> visitIdentifier(node, value)
is ConstantNode -> visitConstantNode(node, value)
is LambdaDeclaration -> visitLambdaDeclaration(node, value)
Expand All @@ -198,7 +199,9 @@ interface ASTVisitor<T> {

fun visitIdentifiers(list: List<Identifier>, value: T) {
list.forEach {
visitIdentifier(it, value)
if (it is AttributeIdentifier)
visitAttributeIdentifier(it, value)
else visitIdentifier(it, value)
}
}

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

}

fun visitAttributeIdentifier(identifier: AttributeIdentifier, value: T)
}

interface ASTModifier<T> {
Expand Down Expand Up @@ -473,7 +478,11 @@ interface ASTModifier<T> {

fun visitLocalStatement(node: LocalStatement, value: T): LocalStatement {
node.init.forEachIndexed { index, identifier ->
node.init[index] = visitIdentifier(identifier, value).also {
node.init[index] = run {
if (identifier is AttributeIdentifier)
visitIdentifier(identifier, value)
else visitIdentifier(identifier, value)
}.also {
it.parent = node
}
}
Expand Down Expand Up @@ -592,6 +601,7 @@ interface ASTModifier<T> {
is CallExpression -> visitCallExpression(node, value)
is BinaryExpression -> visitBinaryExpression(node, value)
is UnaryExpression -> visitUnaryExpression(node, value)
is AttributeIdentifier -> visitAttributeIdentifier(node, value)
is Identifier -> visitIdentifier(node, value)
is ConstantNode -> visitConstantNode(node, value)
is LambdaDeclaration -> visitLambdaDeclaration(node, value)
Expand Down Expand Up @@ -776,4 +786,8 @@ interface ASTModifier<T> {

return node
}

fun visitAttributeIdentifier(node: AttributeIdentifier, value: T): AttributeIdentifier {
return node
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ class SemanticAnalyzer : ASTVisitor<BaseASTNode> {
destroyScope()
}

override fun visitAttributeIdentifier(
identifier: AttributeIdentifier,
value: BaseASTNode
) {
TODO("Lua 5.4 attribute")
}

override fun visitDoStatement(node: DoStatement, value: BaseASTNode) {
createFunctionScope(node)
super.visitDoStatement(node, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AST2Lua : ASTVisitor<StringBuilder> {
var indentSize = 4

private fun indent(): String {
return " ".repeat(indentSize).repeat(max(currentDepth,0))
return " ".repeat(indentSize).repeat(max(currentDepth, 0))
}

private fun indent(sb: StringBuilder) {
Expand Down Expand Up @@ -91,7 +91,6 @@ class AST2Lua : ASTVisitor<StringBuilder> {
}



override fun visitFunctionDeclaration(node: FunctionDeclaration, value: StringBuilder) {
value.append("function ")
node.identifier?.let { visitExpressionNode(it, value) }
Expand Down Expand Up @@ -414,4 +413,17 @@ class AST2Lua : ASTVisitor<StringBuilder> {
ExpressionOperator.GETLEN -> "#"
}
}

override fun visitAttributeIdentifier(identifier: AttributeIdentifier, value: StringBuilder) {
value.append(identifier.name)

val attributeName = identifier.attributeName

if (attributeName != null) {
value.append(" ")
value.append("<")
value.append(attributeName)
value.append(">")
}
}
}
12 changes: 12 additions & 0 deletions src/commonTest/kotlin/parser.common.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io.github.dingyi222666.luaparser.parser.LuaParser
import io.github.dingyi222666.luaparser.parser.LuaVersion
import io.github.dingyi222666.luaparser.source.AST2Lua
import io.github.dingyi222666.luaparser.util.parseLuaString
import kotlin.test.Test
Expand All @@ -18,6 +19,17 @@ import kotlin.test.Test

println(AST2Lua().asCode(root))
}

@Test
fun parseLua54() {
val parser = LuaParser(LuaVersion.LUA_5_4)

val root = parser.parse("""
local apple <const>, carrot = 'fruit', 'vegetable'
""".trimIndent())

println(AST2Lua().asCode(root))
}
}

const val source = """
Expand Down
52 changes: 0 additions & 52 deletions src/jvmTest/kotlin/benchmark.jvm.kt

This file was deleted.

0 comments on commit 4bcb926

Please sign in to comment.