Merge branch 'master' into structs

# Conflicts:
#	codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
This commit is contained in:
Irmen de Jong
2025-06-02 20:52:50 +02:00
14 changed files with 855 additions and 111 deletions
+1
View File
@@ -12,6 +12,7 @@
<option name="pkg" value="" />
<option name="language" value="Java" />
<option name="generateListener" value="false" />
<option name="generateVisitor" value="true" />
</PerGrammarGenerationSettings>
</list>
</option>
@@ -658,7 +658,8 @@ class AsmGen6502Internal (
}
}
expr.type.isFloat -> {
require(options.compTarget.FLOAT_MEM_SIZE == 5) {"invalid float size ${expr.position}"}
if(options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${expr.position}")
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out("""
sta P8ZP_SCRATCH_REG
@@ -1778,6 +1778,17 @@ internal class AssignmentAsmGen(
}
}
fun requiresCmp(expr: PtExpression) =
when (expr) {
is PtFunctionCall -> {
val function = asmgen.symbolTable.lookup(expr.name)
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
}
is PtBuiltinFunctionCall -> true
is PtIfExpression -> true
else -> false
}
if(!expr.right.isSimple() && expr.operator!="xor") {
// shortcircuit evaluation into A
val shortcutLabel = asmgen.makeLabel("shortcut")
@@ -1785,15 +1796,23 @@ internal class AssignmentAsmGen(
"and" -> {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" beq $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
"or" -> {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" bne $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
else -> throw AssemblyError("invalid logical operator")
@@ -2664,10 +2683,10 @@ $endLabel""")
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
if(arrayIndexExpr!=null) {
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
if(constIndex in 1..255)
@@ -2691,15 +2710,16 @@ $endLabel""")
else {
if(constIndex>0) {
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
} else {
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return
} else {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
asmgen.saveRegisterStack(CpuRegister.A, false)
@@ -2734,10 +2754,29 @@ $endLabel""")
}
else {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
val subtype = arrayDt.sub!!
if(subtype.isByteOrBool) {
// elt size 1, we're good
} else if(subtype.isWord) {
if(!arrayDt.isSplitWordArray) {
// elt size 2
asmgen.out(" asl a")
}
} else if(subtype==BaseDataType.FLOAT) {
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${arrayIndexExpr.position}")
asmgen.out("""
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG"""
)
} else throw AssemblyError("weird type $subtype")
asmgen.out("""
ldy #>$arrayName
ldy #>$sourceName
clc
adc #<$arrayName
adc #<$sourceName
bcc +
iny
+""")
@@ -49,7 +49,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
@@ -57,7 +57,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
@@ -89,12 +89,11 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse)
)
}
@@ -113,7 +112,7 @@ class StatementOptimizer(private val program: Program,
if(ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
@@ -153,15 +152,8 @@ class StatementOptimizer(private val program: Program,
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(
forLoop.loopVar,
null,
null,
null,
false,
position = forLoop.position
), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, position=forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -175,15 +167,8 @@ class StatementOptimizer(private val program: Program,
// loop over string of length 1 -> just assign the single character
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(BaseDataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(
forLoop.loopVar,
null,
null,
null,
false,
position = forLoop.position
), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, position=forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -194,7 +179,7 @@ class StatementOptimizer(private val program: Program,
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, null, false, position = forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignmentOrigin.OPTIMIZER, forLoop.position))
@@ -487,7 +472,7 @@ class StatementOptimizer(private val program: Program,
return IfElse(
compare,
AnonymousScope(mutableListOf(assign), position),
AnonymousScope(mutableListOf(), position),
AnonymousScope.empty(),
position
)
}
+11 -9
View File
@@ -26,6 +26,7 @@ import kotlin.io.path.isRegularFile
import kotlin.io.path.nameWithoutExtension
import kotlin.system.exitProcess
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.measureTime
import kotlin.time.measureTimedValue
@@ -228,19 +229,20 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
System.err.flush()
if(!args.quietAll && args.showTimings) {
println("\n**** TIMING ****")
println("source parsing : ${parseDuration}")
println("ast processing : ${processDuration}")
println("ast optimizing : ${optimizeDuration}")
println("ast postprocess : ${postprocessDuration}")
println("code prepare : ${simplifiedAstDuration}")
println("code generation : ${createAssemblyDuration}")
println(" total : ${parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration}")
println("\n**** TIMINGS ****")
println("source parsing : ${parseDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast processing : ${processDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast optimizing : ${optimizeDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast postprocess : ${postprocessDuration.toString(DurationUnit.SECONDS, 3)}")
println("code prepare : ${simplifiedAstDuration.toString(DurationUnit.SECONDS, 3)}")
println("code generation : ${createAssemblyDuration.toString(DurationUnit.SECONDS, 3)}")
val totalDuration = parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration
println(" total : ${totalDuration.toString(DurationUnit.SECONDS, 3)}")
}
}
if(!args.quietAll) {
println("\nTotal compilation+assemble time: ${totalTime}.")
println("\nTotal compilation+assemble time: ${totalTime.toString(DurationUnit.SECONDS, 3)}.")
}
return CompilationResult(resultingProgram!!, ast, compilationOptions, importedFiles)
} catch (px: ParseError) {
@@ -116,7 +116,7 @@ if not CONDITION
untilLoop.body,
IfElse(invertCondition(untilLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(loopLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos)
), pos)
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
@@ -156,7 +156,7 @@ _after:
loopLabel,
IfElse(invertCondition(whileLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(afterLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos),
whileLoop.body,
program.jumpLabel(loopLabel),
@@ -523,13 +523,8 @@ _after:
assignIndex = Assignment(varTarget, ongoto.index, AssignmentOrigin.USERCODE, ongoto.position)
}
val callTarget = ArrayIndexedExpression(
IdentifierReference(listOf(jumplistArray.name), jumplistArray.position),
null,
ArrayIndex(indexValue.copy(), indexValue.position),
ongoto.position
)
val callIndexed = AnonymousScope(mutableListOf(), ongoto.position)
val callTarget = ArrayIndexedExpression(IdentifierReference(listOf(jumplistArray.name), jumplistArray.position), null, ArrayIndex(indexValue.copy(), indexValue.position), ongoto.position)
val callIndexed = AnonymousScope.empty(ongoto.position)
if(ongoto.isCall) {
callIndexed.statements.add(FunctionCallStatement(IdentifierReference(listOf("call"), ongoto.position), mutableListOf(callTarget), true, ongoto.position))
} else {
@@ -539,7 +534,7 @@ _after:
val ifSt = if(ongoto.elsepart==null || ongoto.elsepart!!.isEmpty()) {
// if index<numlabels call(labels[index])
val compare = BinaryExpression(indexValue.copy(), "<", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, callIndexed, AnonymousScope(mutableListOf(), ongoto.position), ongoto.position)
IfElse(compare, callIndexed, AnonymousScope.empty(), ongoto.position)
} else {
// if index>=numlabels elselabel() else call(labels[index])
val compare = BinaryExpression(indexValue.copy(), ">=", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
+2 -2
View File
@@ -213,7 +213,7 @@ class TestNumericLiteral: FunSpec({
test("cast can change value") {
fun num(dt: BaseDataType, num: Double): NumericLiteral {
val n = NumericLiteral(dt, num, Position.DUMMY)
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
n.linkParents(AnonymousScope.empty())
return n
}
val cast1 = num(BaseDataType.UBYTE, 200.0).cast(BaseDataType.BYTE, false)
@@ -233,7 +233,7 @@ class TestNumericLiteral: FunSpec({
test("convert cannot change value") {
fun num(dt: BaseDataType, num: Double): NumericLiteral {
val n = NumericLiteral(dt, num, Position.DUMMY)
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
n.linkParents(AnonymousScope.empty())
return n
}
num(BaseDataType.UBYTE, 200.0).convertTypeKeepValue(BaseDataType.BYTE).isValid shouldBe false
+3 -3
View File
@@ -288,13 +288,13 @@ class TestProg8Parser: FunSpec( {
}
"""
val module = parseModule(SourceCode.Text(srcText))
assertPositionOf(module, Regex("^string:[0-9a-f\\-]+$"), 1, 0)
assertPositionOf(module, Regex("^string:[0-9a-f\\-]+$"), 1, 1)
}
test("of Module parsed from a file") {
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
val module = parseModule(ImportFileSystem.getFile(path))
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 1)
}
test("of non-root Nodes parsed from file") {
@@ -302,7 +302,7 @@ class TestProg8Parser: FunSpec( {
val module = parseModule(ImportFileSystem.getFile(path))
val mpf = module.position.file
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 1)
val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 2, 1, 4)
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
@@ -0,0 +1,750 @@
package prog8.ast.antlr
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.FatalAstException
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.code.source.SourceCode
import prog8.parser.Prog8ANTLRParser.*
import prog8.parser.Prog8ANTLRVisitor
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node>(), Prog8ANTLRVisitor<Node> {
override fun visitModule(ctx: ModuleContext): Module {
val statements = ctx.module_element().map { it.accept(this) as Statement }
return Module(statements.toMutableList(), ctx.toPosition(), source)
}
override fun visitBlock(ctx: BlockContext): Block {
val name = getname(ctx.identifier())
val address = (ctx.integerliteral()?.accept(this) as NumericLiteral?)?.number?.toUInt()
val statements = ctx.block_statement().map { it.accept(this) as Statement }
return Block(name, address, statements.toMutableList(), source.isFromLibrary, ctx.toPosition())
}
override fun visitExpression(ctx: ExpressionContext): Expression {
if(ctx.sizeof_expression!=null) {
val sdt = ctx.sizeof_argument().datatype()
val datatype = if(sdt!=null) baseDatatypeFor(sdt) else null
val expression = ctx.sizeof_argument().expression()?.accept(this) as Expression?
val sizeof = IdentifierReference(listOf("sizeof"), ctx.toPosition())
val arg = if (expression != null) expression else {
require(datatype != null)
IdentifierReference(listOf(datatype.name.lowercase()), ctx.toPosition())
}
return FunctionCallExpression(sizeof, mutableListOf(arg), ctx.toPosition())
}
if(ctx.bop!=null) {
val operator = ctx.bop.text.trim().replace("\\s+".toRegex(), " ")
return BinaryExpression(
ctx.left.accept(this) as Expression,
operator,
ctx.right.accept(this) as Expression,
ctx.toPosition()
)
}
if(ctx.prefix!=null) {
return PrefixExpression(ctx.prefix.text, ctx.expression(0).accept(this) as Expression, ctx.toPosition())
}
if(ctx.rangefrom!=null && ctx.rangeto!=null) {
val defaultstep = if(ctx.rto.text == "to") 1 else -1
return RangeExpression(
ctx.rangefrom.accept(this) as Expression,
ctx.rangeto.accept(this) as Expression,
ctx.rangestep?.accept(this) as Expression? ?: NumericLiteral.optimalInteger(defaultstep, ctx.toPosition()),
ctx.toPosition())
}
if(ctx.typecast()!=null) {
// typecast is always to a base datatype
val baseDt = baseDatatypeFor(ctx.typecast().datatype())
return TypecastExpression(ctx.expression(0).accept(this) as Expression, baseDt, false, ctx.toPosition())
}
if(ctx.childCount==3 && ctx.children[0].text=="(" && ctx.children[2].text==")")
return ctx.expression(0).accept(this) as Expression // expression within ( )
return visitChildren(ctx) as Expression
}
override fun visitSubroutinedeclaration(ctx: SubroutinedeclarationContext): Subroutine {
if(ctx.subroutine()!=null)
return ctx.subroutine().accept(this) as Subroutine
if(ctx.asmsubroutine()!=null)
return ctx.asmsubroutine().accept(this) as Subroutine
if(ctx.extsubroutine()!=null)
return ctx.extsubroutine().accept(this) as Subroutine
throw FatalAstException("weird subroutine")
}
override fun visitAlias(ctx: AliasContext): Alias {
val identifier = getname(ctx.identifier())
val target = ctx.scoped_identifier().accept(this) as IdentifierReference
return Alias(identifier, target, ctx.toPosition())
}
override fun visitDefer(ctx: DeferContext): Defer {
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return Defer(statements, ctx.toPosition())
}
override fun visitLabeldef(ctx: LabeldefContext): Label {
return Label(getname(ctx.identifier()), ctx.toPosition())
}
override fun visitUnconditionaljump(ctx: UnconditionaljumpContext): Jump {
return Jump(ctx.expression().accept(this) as Expression, ctx.toPosition())
}
override fun visitDirective(ctx: DirectiveContext): Directive {
if(ctx.directivenamelist() != null) {
val namelist = ctx.directivenamelist().scoped_identifier().map { it.accept(this) as IdentifierReference }
val identifiers = namelist.map { DirectiveArg(it.nameInSource.joinToString("."), null, ctx.toPosition()) }
return Directive(ctx.directivename.text, identifiers, ctx.toPosition())
}
else
return Directive(ctx.directivename.text, ctx.directivearg().map { it.accept(this) as DirectiveArg }, ctx.toPosition())
}
override fun visitDirectivearg(ctx: DirectiveargContext): DirectiveArg {
val integer = (ctx.integerliteral()?.accept(this) as NumericLiteral?)?.number?.toUInt()
val str = ctx.stringliteral()
if(str!=null) {
if (str.encoding?.text != null)
throw SyntaxError("don't use a string encoding for directive arguments", ctx.toPosition())
return DirectiveArg(str.text.substring(1, str.text.length-1), integer, ctx.toPosition())
}
val identifier = ctx.identifier()?.accept(this) as IdentifierReference?
return DirectiveArg(identifier?.nameInSource?.single(), integer, ctx.toPosition())
}
override fun visitVardecl(ctx: VardeclContext): VarDecl {
val tags = ctx.TAG().map { it.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid variable tag '$tag'", ctx.toPosition())
}
val zp = getZpOption(tags)
val split = getSplitOption(tags)
val alignword = "@alignword" in tags
val align64 = "@align64" in tags
val alignpage = "@alignpage" in tags
if(alignpage && alignword)
throw SyntaxError("choose a single alignment option", ctx.toPosition())
val identifiers = ctx.identifier().map { getname(it) }
val identifiername = identifiers[0]
val name = if(identifiers.size==1) identifiername else "<multiple>"
val arrayIndex = ctx.arrayindex()?.accept(this) as ArrayIndex?
val isArray = ctx.ARRAYSIG() != null || arrayIndex != null
val baseDt = baseDatatypeFor(ctx.datatype())
val dt = if(isArray) DataType.arrayFor(baseDt, split!=SplitWish.NOSPLIT) else DataType.forDt(baseDt)
return VarDecl(
VarDeclType.VAR, // can be changed to MEMORY or CONST as required
VarDeclOrigin.USERCODE,
dt,
zp,
split,
arrayIndex,
name,
if(identifiers.size==1) emptyList() else identifiers,
null,
"@shared" in tags,
if(alignword) 2u else if(align64) 64u else if(alignpage) 256u else 0u,
"@dirty" in tags,
ctx.toPosition()
)
}
override fun visitVarinitializer(ctx: VarinitializerContext): VarDecl {
val vardecl = ctx.vardecl().accept(this) as VarDecl
vardecl.value = ctx.expression().accept(this) as Expression
return vardecl
}
override fun visitConstdecl(ctx: ConstdeclContext): VarDecl {
val vardecl = ctx.varinitializer().accept(this) as VarDecl
vardecl.type = VarDeclType.CONST
return vardecl
}
override fun visitMemoryvardecl(ctx: MemoryvardeclContext): VarDecl {
val vardecl = ctx.varinitializer().accept(this) as VarDecl
vardecl.type = VarDeclType.MEMORY
return vardecl
}
override fun visitArrayindex(ctx: ArrayindexContext): ArrayIndex {
return ArrayIndex(ctx.expression().accept(this) as Expression, ctx.toPosition())
}
override fun visitAssignment(ctx: AssignmentContext): Statement {
val multiAssign = ctx.multi_assign_target()
if(multiAssign!=null) {
return Assignment(multiAssign.accept(this) as AssignTarget, ctx.expression().accept(this) as Expression, AssignmentOrigin.USERCODE, ctx.toPosition())
}
val nestedAssign = ctx.assignment()
return if(nestedAssign==null)
Assignment(ctx.assign_target().accept(this) as AssignTarget, ctx.expression().accept(this) as Expression, AssignmentOrigin.USERCODE, ctx.toPosition())
else
ChainedAssignment(ctx.assign_target().accept(this) as AssignTarget, nestedAssign.accept(this) as Statement, ctx.toPosition())
}
override fun visitAugassignment(ctx: AugassignmentContext): Assignment {
// replace A += X with A = A + X
val target = ctx.assign_target().accept(this) as AssignTarget
val oper = ctx.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, ctx.expression().accept(this) as Expression, ctx.toPosition())
return Assignment(target, expression, AssignmentOrigin.USERCODE, ctx.toPosition())
}
override fun visitIdentifierTarget(ctx: IdentifierTargetContext): AssignTarget {
val identifier = ctx.scoped_identifier().accept(this) as IdentifierReference
return AssignTarget(identifier, null, null, null, false, ctx.toPosition())
}
override fun visitArrayindexedTarget(ctx: ArrayindexedTargetContext): AssignTarget {
val ax = ctx.arrayindexed()
val arrayvar = ax.scoped_identifier().accept(this) as IdentifierReference
val index = ax.arrayindex().accept(this) as ArrayIndex
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
return AssignTarget(null, arrayindexed, null, null, false, ctx.toPosition())
}
override fun visitMemoryTarget(ctx: MemoryTargetContext): AssignTarget {
return AssignTarget(null, null,
DirectMemoryWrite(ctx.directmemory().expression().accept(this) as Expression, ctx.toPosition()),
null, false, ctx.toPosition())
}
override fun visitVoidTarget(ctx: VoidTargetContext): AssignTarget {
return AssignTarget(null, null, null, null, true, ctx.toPosition())
}
override fun visitMulti_assign_target(ctx: Multi_assign_targetContext): AssignTarget {
val targets = ctx.assign_target().map { it.accept(this) as AssignTarget }
return AssignTarget(null, null, null, targets, false, ctx.toPosition())
}
override fun visitPostincrdecr(ctx: PostincrdecrContext): Assignment {
val tgt = ctx.assign_target().accept(this) as AssignTarget
val operator = ctx.operator.text
val pos = ctx.toPosition()
val addSubOne = BinaryExpression(tgt.toExpression(), if(operator=="++") "+" else "-", NumericLiteral.optimalInteger(1, pos), pos)
return Assignment(tgt, addSubOne, AssignmentOrigin.USERCODE, pos)
}
override fun visitArrayindexed(ctx: ArrayindexedContext): ArrayIndexedExpression {
val identifier = ctx.scoped_identifier().accept(this) as IdentifierReference
val index = ctx.arrayindex().accept(this) as ArrayIndex
return ArrayIndexedExpression(identifier, index, ctx.toPosition())
}
override fun visitDirectmemory(ctx: DirectmemoryContext): DirectMemoryRead {
return DirectMemoryRead(ctx.expression().accept(this) as Expression, ctx.toPosition())
}
override fun visitAddressof(ctx: AddressofContext): AddressOf {
val identifier = ctx.scoped_identifier()?.accept(this) as IdentifierReference?
val msb = ctx.ADDRESS_OF_MSB()!=null
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
return if (identifier != null)
AddressOf(identifier, null, msb, ctx.toPosition())
else {
val array = ctx.arrayindexed()
AddressOf(array.scoped_identifier().accept(this) as IdentifierReference,
array.arrayindex().accept(this) as ArrayIndex,
msb, ctx.toPosition())
}
}
override fun visitFunctioncall(ctx: FunctioncallContext): FunctionCallExpression {
val name = ctx.scoped_identifier().accept(this) as IdentifierReference
val args = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression } ?: emptyList()
return FunctionCallExpression(name, args.toMutableList(), ctx.toPosition())
}
override fun visitFunctioncall_stmt(ctx: Functioncall_stmtContext): FunctionCallStatement {
val void = ctx.VOID() != null
val name = ctx.scoped_identifier().accept(this) as IdentifierReference
val args = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression } ?: emptyList()
return FunctionCallStatement(name, args.toMutableList(), void, ctx.toPosition())
}
override fun visitReturnstmt(ctx: ReturnstmtContext): Return {
val cvalues = ctx.returnvalues()
val values = if(cvalues==null || cvalues.expression().isEmpty()) arrayOf() else cvalues.expression().map { it.accept(this) as Expression }.toTypedArray()
return Return(values, ctx.toPosition())
}
override fun visitBreakstmt(ctx: BreakstmtContext): Break {
return Break(ctx.toPosition())
}
override fun visitContinuestmt(ctx: ContinuestmtContext): Continue {
return Continue(ctx.toPosition())
}
override fun visitIdentifier(ctx: IdentifierContext): IdentifierReference {
return IdentifierReference(listOf(getname(ctx)), ctx.toPosition())
}
override fun visitScoped_identifier(ctx: Scoped_identifierContext): IdentifierReference {
val children = ctx.identifier().map { it.text }
return IdentifierReference(children, ctx.toPosition())
}
override fun visitIntegerliteral(ctx: IntegerliteralContext): NumericLiteral {
fun makeLiteral(literalTextWithGrouping: String, radix: Int): Pair<Double, BaseDataType> {
val literalText = literalTextWithGrouping.replace("_", "")
val integer: Int
var datatype = BaseDataType.UBYTE
when (radix) {
10 -> {
integer = try {
literalText.toInt()
} catch(x: NumberFormatException) {
throw SyntaxError("invalid decimal literal ${x.message}", ctx.toPosition())
}
datatype = when(integer) {
in 0..255 -> BaseDataType.UBYTE
in -128..127 -> BaseDataType.BYTE
in 0..65535 -> BaseDataType.UWORD
in -32768..32767 -> BaseDataType.WORD
in -2147483647..2147483647 -> BaseDataType.LONG
else -> BaseDataType.FLOAT
}
}
2 -> {
if(literalText.length>16)
datatype = BaseDataType.LONG
else if(literalText.length>8)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(2)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid binary literal ${x.message}", ctx.toPosition())
}
}
16 -> {
if(literalText.length>4)
datatype = BaseDataType.LONG
else if(literalText.length>2)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(16)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid hexadecimal literal ${x.message}", ctx.toPosition())
}
}
else -> throw FatalAstException("invalid radix")
}
return integer.toDouble() to datatype
}
val terminal: TerminalNode = ctx.children[0] as TerminalNode
val integerPart = ctx.intpart.text
val integer = when (terminal.symbol.type) {
DEC_INTEGER -> makeLiteral(integerPart, 10)
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
return NumericLiteral(integer.second, integer.first, ctx.toPosition())
}
override fun visitBooleanliteral(ctx: BooleanliteralContext): NumericLiteral {
val boolean = when(ctx.text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(ctx.text)
}
return NumericLiteral.fromBoolean(boolean, ctx.toPosition())
}
override fun visitArrayliteral(ctx: ArrayliteralContext): ArrayLiteral {
val array = ctx.expression().map { it.accept(this) as Expression }.toTypedArray()
// the actual type of the arraysize can not yet be determined here
// the ConstantFold takes care of that and converts the type if needed.
return ArrayLiteral(InferredTypes.InferredType.unknown(), array, position = ctx.toPosition())
}
override fun visitStringliteral(ctx: StringliteralContext): StringLiteral {
val text = ctx.STRING().text
val enc = ctx.encoding?.text
val encoding =
if(enc!=null)
Encoding.entries.singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", ctx.toPosition())
else
Encoding.DEFAULT
val raw = text.substring(1, text.length-1)
try {
return StringLiteral.fromEscaped(raw, encoding, ctx.toPosition())
} catch(ex: IllegalArgumentException) {
throw SyntaxError(ex.message!!, ctx.toPosition())
}
}
override fun visitCharliteral(ctx: CharliteralContext): CharLiteral {
val text = ctx.SINGLECHAR().text
val enc = ctx.encoding?.text
val encoding =
if(enc!=null)
Encoding.entries.singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", ctx.toPosition())
else
Encoding.DEFAULT
val raw = text.substring(1, text.length - 1)
try {
return CharLiteral.fromEscaped(raw, encoding, ctx.toPosition())
} catch(ex: IllegalArgumentException) {
throw SyntaxError(ex.message!!, ctx.toPosition())
}
}
override fun visitFloatliteral(ctx: FloatliteralContext): NumericLiteral {
val floatvalue = ctx.text.replace("_","").toDouble()
return NumericLiteral(BaseDataType.FLOAT, floatvalue, ctx.toPosition())
}
override fun visitInlineasm(ctx: InlineasmContext): InlineAssembly {
val text = ctx.INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), false, ctx.toPosition())
}
override fun visitInlineir(ctx: InlineirContext): InlineAssembly {
val text = ctx.INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), true, ctx.toPosition())
}
override fun visitSubroutine(ctx: SubroutineContext): Subroutine {
val name = getname(ctx.identifier())
val parameters = ctx.sub_params()?.sub_param()?.map { it.accept(this) as SubroutineParameter } ?: emptyList()
val returntypes = ctx.sub_return_part()?.datatype()?. map { dataTypeFor(it) } ?: emptyList()
val statements = ctx.statement_block().accept(this) as AnonymousScope
return Subroutine(
name,
parameters.toMutableList(),
returntypes.toMutableList(),
emptyList(),
emptyList(),
emptySet(),
asmAddress = null,
isAsmSubroutine = false,
inline = false,
statements = statements.statements,
position = ctx.toPosition()
)
}
override fun visitStatement_block(ctx: Statement_blockContext): AnonymousScope {
val statements = ctx.statement().map { it.accept(this) as Statement }
return AnonymousScope(statements.toMutableList(), ctx.toPosition())
}
override fun visitSub_param(pctx: Sub_paramContext): SubroutineParameter {
val decl = pctx.vardecl()
val tags = decl.TAG().map { t -> t.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid parameter tag '$tag'", pctx.toPosition())
}
val zp = getZpOption(tags)
val decldt = decl.datatype()
var datatype = if(decldt!=null) dataTypeFor(decldt) else DataType.UNDEFINED
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
datatype = datatype.elementToArray()
val identifiers = decl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = getname(identifiers[0])
val (registerorpair, statusregister) = parseParamRegister(pctx.register, pctx.toPosition())
if(statusregister!=null) {
throw SyntaxError("can't use status register as param for normal subroutines", Position(pctx.toPosition().file, pctx.register.line, pctx.register.charPositionInLine, pctx.register.charPositionInLine+1))
}
return SubroutineParameter(identifiername, datatype, zp, registerorpair, pctx.toPosition())
}
override fun visitAsmsubroutine(ctx: AsmsubroutineContext): Subroutine {
val inline = ctx.INLINE()!=null
val ad = asmSubDecl(ctx.asmsub_decl())
val statements = ctx.statement_block().accept(this) as AnonymousScope
return Subroutine(ad.name,
ad.parameters.toMutableList(),
ad.returntypes.toMutableList(),
ad.asmParameterRegisters,
ad.asmReturnvaluesRegisters,
ad.asmClobbers, null, true, inline,
statements = statements.statements, position = ctx.toPosition()
)
}
override fun visitExtsubroutine(ctx: ExtsubroutineContext): Subroutine {
val subdecl = asmSubDecl(ctx.asmsub_decl())
val constbank = (ctx.constbank?.accept(this) as NumericLiteral?)?.number?.toUInt()?.toUByte()
val varbank = ctx.varbank?.accept(this) as IdentifierReference?
val addr = ctx.address.accept(this) as Expression
val address = Subroutine.Address(constbank, varbank, addr)
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = ctx.toPosition()
)
}
override fun visitIf_stmt(ctx: If_stmtContext): IfElse {
val condition = ctx.expression().accept(this) as Expression
val truepart = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
return IfElse(condition, truepart, elsepart, ctx.toPosition())
}
override fun visitElse_part(ctx: Else_partContext): AnonymousScope {
return stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
}
override fun visitIf_expression(ctx: If_expressionContext): IfExpression {
val (condition, truevalue, falsevalue) = ctx.expression().map { it.accept(this) as Expression }
return IfExpression(condition, truevalue, falsevalue, ctx.toPosition())
}
override fun visitBranch_stmt(ctx: Branch_stmtContext): ConditionalBranch {
val branchcondition = branchCondition(ctx.branchcondition())
val truepart = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
return ConditionalBranch(branchcondition, truepart, elsepart, ctx.toPosition())
}
override fun visitForloop(ctx: ForloopContext): ForLoop {
val loopvar = ctx.scoped_identifier().accept(this) as IdentifierReference
val iterable = ctx.expression().accept(this) as Expression
val scope = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return ForLoop(loopvar, iterable, scope, ctx.toPosition())
}
override fun visitWhileloop(ctx: WhileloopContext): WhileLoop {
val condition = ctx.expression().accept(this) as Expression
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return WhileLoop(condition, statements, ctx.toPosition())
}
override fun visitUntilloop(ctx: UntilloopContext): UntilLoop {
val condition = ctx.expression().accept(this) as Expression
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return UntilLoop(statements, condition, ctx.toPosition())
}
override fun visitRepeatloop(ctx: RepeatloopContext): RepeatLoop {
val iterations = ctx.expression()?.accept(this) as Expression?
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return RepeatLoop(iterations, statements, ctx.toPosition())
}
override fun visitUnrollloop(ctx: UnrollloopContext): UnrollLoop {
val iterations = ctx.expression().accept(this) as Expression
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return UnrollLoop(iterations, statements, ctx.toPosition())
}
override fun visitWhenstmt(ctx: WhenstmtContext): When {
val condition = ctx.expression().accept(this) as Expression
val choices = ctx.when_choice()?.map { it.accept(this) as WhenChoice }?.toMutableList() ?: mutableListOf()
return When(condition, choices, ctx.toPosition())
}
override fun visitWhen_choice(ctx: When_choiceContext): WhenChoice {
val values = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression }
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return WhenChoice(values?.toMutableList(), statements, ctx.toPosition())
}
override fun visitOngoto(ctx: OngotoContext): OnGoto {
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
val isCall = ctx.kind.text == "call"
val index = ctx.expression().accept(this) as Expression
val labels = ctx.directivenamelist().scoped_identifier().map { it.accept(this) as IdentifierReference }
return OnGoto(isCall, index, labels, elsepart, ctx.toPosition())
}
override fun visitModule_element(ctx: Module_elementContext): Node = visitChildren(ctx)
override fun visitBlock_statement(ctx: Block_statementContext): Statement = visitChildren(ctx) as Statement
override fun visitStatement(ctx: StatementContext): Statement = visitChildren(ctx) as Statement
override fun visitVariabledeclaration(ctx: VariabledeclarationContext): VarDecl = visitChildren(ctx) as VarDecl
override fun visitLiteralvalue(ctx: LiteralvalueContext): Expression = visitChildren(ctx) as Expression
override fun visitDirectivenamelist(ctx: DirectivenamelistContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_decl(ctx: Asmsub_declContext?) = throw FatalAstException("should not be called")
override fun visitAsmsub_params(ctx: Asmsub_paramsContext) = throw FatalAstException("should not be called")
override fun visitExpression_list(ctx: Expression_listContext) = throw FatalAstException("should not be called")
override fun visitBranchcondition(ctx: BranchconditionContext) = throw FatalAstException("should not be called")
override fun visitDatatype(ctx: DatatypeContext) = throw FatalAstException("should not be called")
override fun visitSizeof_argument(ctx: Sizeof_argumentContext) = throw FatalAstException("should not be called")
override fun visitReturnvalues(ctx: ReturnvaluesContext) = throw FatalAstException("should not be called")
override fun visitTypecast(ctx: TypecastContext) = throw FatalAstException("should not be called")
override fun visitSub_params(ctx: Sub_paramsContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_param(ctx: Asmsub_paramContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_clobbers(ctx: Asmsub_clobbersContext) = throw FatalAstException("should not be called")
override fun visitClobber(ctx: ClobberContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_returns(ctx: Asmsub_returnsContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_return(ctx: Asmsub_returnContext) = throw FatalAstException("should not be called")
override fun visitSub_return_part(ctx: Sub_return_partContext) = throw FatalAstException("should not be called")
private fun getname(identifier: IdentifierContext): String = identifier.children[0].text
private fun ParserRuleContext.toPosition() : Position {
val pathString = start.inputStream.sourceName
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
val path = Path(pathString)
if(path.isRegularFile()) {
SourceCode.relative(path).toString()
} else {
path.toString()
}
} else {
pathString
}
// note: beware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
}
private fun getZpOption(tags: List<String>): ZeropageWish = when {
"@requirezp" in tags -> ZeropageWish.REQUIRE_ZEROPAGE
"@zp" in tags -> ZeropageWish.PREFER_ZEROPAGE
"@nozp" in tags -> ZeropageWish.NOT_IN_ZEROPAGE
else -> ZeropageWish.DONTCARE
}
private fun getSplitOption(tags: List<String>): SplitWish {
return when {
"@nosplit" in tags -> SplitWish.NOSPLIT
"@split" in tags -> SplitWish.SPLIT
else -> SplitWish.DONTCARE
}
}
private fun asmSubroutineParam(pctx: Asmsub_paramContext): AsmSubroutineParameter {
val vardecl = pctx.vardecl()
val decldt = vardecl.datatype()
var datatype = if(decldt!=null) dataTypeFor(decldt) else DataType.UNDEFINED
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
datatype = datatype.elementToArray()
val (registerorpair, statusregister) = parseParamRegister(pctx.register, pctx.toPosition())
val identifiers = vardecl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = getname(identifiers[0])
return AsmSubroutineParameter(identifiername, datatype, registerorpair, statusregister, pctx.toPosition())
}
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
if(registerTok==null)
return Pair(null, null)
val register = registerTok.text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> {
throw SyntaxError("invalid register or status flag", Position(pos.file, registerTok.line, registerTok.charPositionInLine, registerTok.charPositionInLine+1))
}
}
}
return Pair(registerorpair, statusregister)
}
private fun asmReturn(rctx: Asmsub_returnContext): AsmSubroutineReturn {
val register = rctx.register.text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> throw SyntaxError("invalid register or status flag", rctx.toPosition())
}
}
return AsmSubroutineReturn(
dataTypeFor(rctx.datatype()),
registerorpair,
statusregister)
}
private fun cpuRegister(text: String, pos: Position): CpuRegister {
try {
return CpuRegister.valueOf(text)
} catch(_: IllegalArgumentException) {
throw SyntaxError("invalid cpu register", pos)
}
}
private fun dataTypeFor(it: DatatypeContext) = DataType.forDt(baseDatatypeFor(it))
private fun baseDatatypeFor(it: DatatypeContext) = BaseDataType.valueOf(it.text.uppercase())
private fun stmtBlockOrSingle(statementBlock: Statement_blockContext?, statement: StatementContext?): AnonymousScope {
return if(statementBlock!=null)
statementBlock.accept(this) as AnonymousScope
else if(statement!=null)
AnonymousScope(mutableListOf(statement.accept(this) as Statement), statement.toPosition())
else
AnonymousScope.empty()
}
private fun branchCondition(ctx: BranchconditionContext) = BranchCondition.valueOf(ctx.text.substringAfter('_').uppercase())
private fun asmSubDecl(ad: Asmsub_declContext): AsmsubDecl {
val name = getname(ad.identifier())
val params = ad.asmsub_params()?.asmsub_param()?.map { asmSubroutineParam(it) } ?: emptyList()
val returns = ad.asmsub_returns()?.asmsub_return()?.map { asmReturn(it) } ?: emptyList()
val clobbers = ad.asmsub_clobbers()?.clobber()?.NAME()?.map { cpuRegister(it.text, ad.toPosition()) } ?: emptyList()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.registerOrPair, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers.toSet())
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>)
private class AsmSubroutineParameter(name: String,
type: DataType,
registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, registerOrPair, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?)
}
@@ -148,8 +148,7 @@ class BinaryExpression(
var left: Expression,
var operator: String,
var right: Expression,
override val position: Position,
private val insideParentheses: Boolean = false
override val position: Position
) : Expression() {
override lateinit var parent: Node
@@ -169,7 +168,7 @@ class BinaryExpression(
replacement.parent = this
}
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position, insideParentheses)
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
override fun toString() = "[$left $operator $right]"
override val isSimple = false
@@ -239,19 +239,20 @@ enum class VarDeclType {
MEMORY
}
class VarDecl(val type: VarDeclType,
val origin: VarDeclOrigin,
val datatype: DataType,
val zeropage: ZeropageWish,
val splitwordarray: SplitWish,
var arraysize: ArrayIndex?,
override val name: String,
val names: List<String>,
var value: Expression?,
val sharedWithAsm: Boolean,
val alignment: UInt,
val dirty: Boolean,
override val position: Position) : Statement(), INamedStatement {
class VarDecl(
var type: VarDeclType,
val origin: VarDeclOrigin,
val datatype: DataType,
val zeropage: ZeropageWish,
val splitwordarray: SplitWish,
var arraysize: ArrayIndex?,
override val name: String,
val names: List<String>,
var value: Expression?,
val sharedWithAsm: Boolean,
val alignment: UInt,
val dirty: Boolean,
override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@@ -921,6 +922,10 @@ class AnonymousScope(override val statements: MutableList<Statement>,
statements.forEach { it.linkParents(this) }
}
companion object {
fun empty(pos: Position?=null): AnonymousScope = AnonymousScope(mutableListOf(), pos ?: Position.DUMMY)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
+4 -35
View File
@@ -2,9 +2,7 @@ package prog8.parser
import org.antlr.v4.runtime.*
import prog8.ast.Module
import prog8.ast.antlr.toAst
import prog8.ast.statements.Block
import prog8.ast.statements.Directive
import prog8.ast.antlr.Antlr2KotlinVisitor
import prog8.code.core.Position
import prog8.code.source.SourceCode
@@ -25,41 +23,12 @@ object Prog8Parser {
parser.addErrorListener(antlrErrorListener)
val parseTree = parser.module()
val module = ParsedModule(src)
parseTree.module_element().forEach {
val block = it.block()?.toAst(module.isLibrary)
val directive = it.directive()?.toAst()
if(directive != null) module.add(directive)
if(block != null) module.add(block)
}
return module
val visitor = Antlr2KotlinVisitor(src)
val visitorResult = visitor.visit(parseTree)
return visitorResult as Module
}
private class ParsedModule(source: SourceCode) :
Module(mutableListOf(), Position(source.origin, 1, 0, 0), source)
{
/**
* Adds a [Directive] to [statements] and
* sets this Module as its [parent].
* Note: you can only add [Directive]s or [Block]s to a Module.
*/
fun add(child: Directive) {
child.linkParents(this)
statements.add(child)
}
/**
* Adds a [Block] to [statements] and
* sets this Module as its [parent].
* Note: you can only add [Directive]s or [Block]s to a Module.
*/
fun add(child: Block) {
child.linkParents(this)
statements.add(child)
}
}
private object Prog8ErrorStrategy: BailErrorStrategy() {
private fun fillIn(e: RecognitionException?, ctx: ParserRuleContext?) {
+1 -3
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("antlr")
id("java")
@@ -16,7 +14,7 @@ configurations.all {
tasks.generateGrammarSource {
outputDirectory = file("src/prog8/parser")
arguments.addAll(listOf("-no-listener", "-no-visitor"))
arguments.addAll(listOf("-no-listener", "-visitor"))
}
tasks.compileJava {