various syntax checks added

first steps code generation
This commit is contained in:
Irmen de Jong 2018-09-08 00:07:25 +02:00
parent e928997193
commit 8ef61ffc88
10 changed files with 314 additions and 67 deletions

View File

@ -101,8 +101,7 @@ The name of a block must be unique in your entire program.
Also be careful when importing other modules; blocks in your own code cannot have
the same name as a block defined in an imported module or library.
It's possible to omit this name, but then you can only refer to the contents of the block via its absolute address,
which is required in this case. If you omit *both* name and address, the block is *ignored* by the compiler (and a warning is displayed).
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not fully parsed either.

View File

@ -15,15 +15,18 @@
const byte snerp2 = snerp+22
X = 42+snerp
return 44+snerp
;return 44+snerp
return
sub foo234234() -> () {
A=99+snerp
return A+snerp2
;return A+snerp2
return
}
sub thingy()->() {
return 99
;return 99
return
}
}
@ -32,6 +35,7 @@
; this is imported
X = 42
return 44
;return 44
return
}

View File

@ -67,6 +67,12 @@
const byte equal = 4==4
const byte equal2 = (4+hopla)>0
goto 64738
; A++
derp++
cc2--
goto mega
if_eq goto mega
@ -81,6 +87,7 @@
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
sin(1) ; @todo error statement has no effect (returnvalue of builtin function not used)
P_carry(1)
P_irqd(0)
@ -145,21 +152,24 @@ cool:
sub foo () -> () {
byte blerp = 3
A=99
return 33
; return 33
return
ultrafoo()
X =33
mega:
cool:
sub ultrafoo() -> () {
X= extra233.thingy()
return 33
;return 33
return
goto main.mega
}
}
some_label_def: A=44
return 1+999
;return 1+999
return
%breakpoint
%asminclude "derp", hopsa
%asmbinary "derp", 0, 200

View File

@ -54,16 +54,16 @@ fun main(args: Array<String>) {
StatementReorderer().process(moduleAst) // reorder statements to please the compiler later
val globalNamespaceAfterOptimize = moduleAst.definingScope() // create it again, it could have changed in the meantime
moduleAst.checkValid(globalNamespaceAfterOptimize, compilerOptions) // check if final tree is valid
moduleAst.checkRecursion() // check if there are recursive subroutine calls
moduleAst.checkRecursion(globalNamespaceAfterOptimize) // check if there are recursive subroutine calls
// globalNamespaceAfterOptimize.debugPrint()
// compile the syntax tree into intermediate form, and optimize that
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(compilerOptions)
val intermediate = compiler.compile(moduleAst)
intermediate.optimize()
// val assembly = intermediate.compileToAssembly()
// val assembly = stackvmProg.compileToAssembly()
//
// assembly.assemble(compilerOptions, "input", "output")
// val monitorfile = assembly.generateBreakpointList()

View File

@ -172,6 +172,11 @@ interface IAstProcessor {
assignment.value = assignment.value.process(this)
return assignment
}
fun process(postIncrDecr: PostIncrDecr): IStatement {
postIncrDecr.target = postIncrDecr.target.process(this)
return postIncrDecr
}
}
@ -226,9 +231,9 @@ interface IStatement : Node {
interface IFunctionCall {
var target: IdentifierReference
var arglist: List<IExpression>
var targetStatement: IStatement
}
interface INameScope {
val name: String
val position: Position?
@ -387,7 +392,7 @@ private class GlobalNamespace(override val name: String,
is Subroutine -> stmt.scopedname
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
registerUsedName(targetScopedName.joinToString("."))
registerUsedName(targetScopedName)
}
return stmt
}
@ -406,7 +411,8 @@ class Block(override val name: String,
override var statements: MutableList<IStatement>) : IStatement, INameScope {
override var position: Position? = null
override lateinit var parent: Node
val scopedname: List<String> by lazy { makeScopedName(name) }
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) {
this.parent = parent
@ -450,7 +456,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?) : No
data class Label(val name: String) : IStatement {
override var position: Position? = null
override lateinit var parent: Node
val scopedname: List<String> by lazy { makeScopedName(name) }
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) {
this.parent = parent
@ -477,6 +483,10 @@ class Return(var values: List<IExpression>) : IStatement {
values = values.map { it.process(processor) }
return this
}
override fun toString(): String {
return "Return(values: $values, pos=$position)"
}
}
@ -532,7 +542,8 @@ class VarDecl(val type: VarDeclType,
override fun process(processor: IAstProcessor) = processor.process(this)
val scopedname: List<String> by lazy { makeScopedName(name) }
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
fun arraySizeX(namespace: INameScope) : Int? {
return arrayspec?.x?.constValue(namespace)?.intvalue
@ -558,6 +569,10 @@ class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExp
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
}
}
data class AssignTarget(val register: Register?, val identifier: IdentifierReference?) : Node {
@ -700,11 +715,16 @@ data class IdentifierReference(val nameInSource: List<String>) : IExpression {
override var position: Position? = null
override lateinit var parent: Node
fun targetStatement(namespace: INameScope) =
if(nameInSource.size==1 && BuiltinFunctionNames.contains(nameInSource[0]))
BuiltinFunctionStatementPlaceholder
else
namespace.lookup(nameInSource, this)
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun constValue(namespace: INameScope): LiteralValue? {
val node = namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolException(this)
@ -735,9 +755,10 @@ class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement
target.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement {
target = target.process(processor)
return this
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
}
@ -745,7 +766,6 @@ class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement
class Jump(val address: Int?, val identifier: IdentifierReference?) : IStatement {
override var position: Position? = null
override lateinit var parent: Node
var targetStatement: IStatement? = null
override fun linkParents(parent: Node) {
this.parent = parent
@ -753,13 +773,16 @@ class Jump(val address: Int?, val identifier: IdentifierReference?) : IStatement
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Jump(addr: $address, identifier: $identifier, target: pos=$position)"
}
}
class FunctionCall(override var target: IdentifierReference, override var arglist: List<IExpression>) : IExpression, IFunctionCall {
override var position: Position? = null
override lateinit var parent: Node
override lateinit var targetStatement: IStatement
override fun linkParents(parent: Node) {
this.parent = parent
@ -825,7 +848,6 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
class FunctionCallStatement(override var target: IdentifierReference, override var arglist: List<IExpression>) : IStatement, IFunctionCall {
override var position: Position? = null
override lateinit var parent: Node
override lateinit var targetStatement: IStatement
override fun linkParents(parent: Node) {
this.parent = parent
@ -860,7 +882,8 @@ class Subroutine(override val name: String,
override var statements: MutableList<IStatement>) : IStatement, INameScope {
override var position: Position? = null
override lateinit var parent: Node
val scopedname: List<String> by lazy { makeScopedName(name) }
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) {
this.parent = parent
@ -930,6 +953,10 @@ class BranchStatement(var condition: BranchCondition,
}
override fun process(processor: IAstProcessor): IStatement = processor.process(this)
override fun toString(): String {
return "Branch(cond: $condition, ${statements.size} stmts, ${elsepart.size} else-stmts, pos=$position)"
}
}

View File

@ -53,8 +53,10 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
override fun process(jump: Jump): IStatement {
if(jump.identifier!=null) {
val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump)
if(targetStatement!=null)
jump.targetStatement = targetStatement // link to actual jump target
if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionStatementPlaceholder)
checkResult.add(SyntaxError("can't jump to a builtin function", jump.position))
}
}
if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
@ -303,28 +305,38 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
if(targetStatement!=null) {
functionCall.targetStatement = targetStatement // link to the actual target statement
if(targetStatement!=null)
checkBuiltinFunctionCall(functionCall, functionCall.position)
}
return super.process(functionCall)
}
override fun process(functionCall: FunctionCallStatement): IStatement {
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
if(targetStatement!=null) {
functionCall.targetStatement = targetStatement // link to the actual target statement
if(targetStatement!=null)
checkBuiltinFunctionCall(functionCall, functionCall.position)
}
return super.process(functionCall)
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
if(target.nameInSource.size==1 && BuiltinFunctionNames.contains(target.nameInSource[0])) {
return BuiltinFunctionStatementPlaceholder
override fun process(postIncrDecr: PostIncrDecr): IStatement {
if(postIncrDecr.target.register==null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = globalNamespace.lookup(targetName, postIncrDecr)
if(target==null) {
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
} else {
if(target !is VarDecl || target.type==VarDeclType.CONST) {
checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position))
} else if(target.datatype!=DataType.FLOAT && target.datatype!=DataType.WORD && target.datatype!=DataType.BYTE) {
checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position))
}
}
}
val targetStatement = globalNamespace.lookup(target.nameInSource, statement)
if(targetStatement is Label || targetStatement is Subroutine)
return super.process(postIncrDecr)
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
val targetStatement = target.targetStatement(globalNamespace)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
return null

View File

@ -36,7 +36,7 @@ class AstIdentifiersChecker : IAstProcessor {
}
override fun process(block: Block): IStatement {
val scopedName = block.scopedname.joinToString(".")
val scopedName = block.scopedname
val existing = symbols[scopedName]
if(existing!=null) {
nameError(block.name, block.position, existing)
@ -51,7 +51,7 @@ class AstIdentifiersChecker : IAstProcessor {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
val scopedName = decl.scopedname.joinToString(".")
val scopedName = decl.scopedname
val existing = symbols[scopedName]
if(existing!=null) {
nameError(decl.name, decl.position, existing)
@ -66,7 +66,7 @@ class AstIdentifiersChecker : IAstProcessor {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
} else {
val scopedName = subroutine.scopedname.joinToString(".")
val scopedName = subroutine.scopedname
val existing = symbols[scopedName]
if (existing != null) {
nameError(subroutine.name, subroutine.position, existing)
@ -82,7 +82,7 @@ class AstIdentifiersChecker : IAstProcessor {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else {
val scopedName = label.scopedname.joinToString(".")
val scopedName = label.scopedname
val existing = symbols[scopedName]
if (existing != null) {
nameError(label.name, label.position, existing)

View File

@ -6,8 +6,8 @@ import il65.parser.ParsingFailedError
* Checks for the occurrence of recursive subroutine calls
*/
fun Module.checkRecursion() {
val checker = AstRecursionChecker()
fun Module.checkRecursion(namespace: INameScope) {
val checker = AstRecursionChecker(namespace)
this.process(checker)
val checkResult = checker.result()
checkResult.forEach {
@ -88,7 +88,7 @@ class DirectedGraph<VT> {
}
class AstRecursionChecker : IAstProcessor {
class AstRecursionChecker(val namespace: INameScope) : IAstProcessor {
private val callGraph = DirectedGraph<INameScope>()
fun result(): List<AstException> {
@ -96,26 +96,32 @@ class AstRecursionChecker : IAstProcessor {
if(cycle.isEmpty())
return emptyList()
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n"+chain))
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) "+chain))
}
override fun process(functionCall: FunctionCallStatement): IStatement {
val scope = functionCall.definingScope()
val targetScope = when(functionCall.targetStatement) {
is Subroutine -> functionCall.targetStatement as Subroutine
else -> functionCall.targetStatement.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
callGraph.add(scope, targetScope)
return super.process(functionCall)
}
override fun process(functionCall: FunctionCall): IExpression {
val scope = functionCall.definingScope()
val targetScope = when(functionCall.targetStatement) {
is Subroutine -> functionCall.targetStatement as Subroutine
else -> functionCall.targetStatement.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
callGraph.add(scope, targetScope)
return super.process(functionCall)
}
}

View File

@ -85,38 +85,227 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va
}
}
/*
; source code for a stackvm program
; init memory bytes/words/strings
%memory
0400 01 02 03 04 05 06 07 08 09 22 33 44 55 66
0500 1111 2222 3333 4444
1000 "Hello world!\n"
%end_memory
; init global var table with bytes/words/floats/strings
%variables
main.var1 str "This is main.var1"
main.var2 byte aa
main.var3 word ea44
main.var4 float 3.1415927
input.prompt str "Enter a number: "
input.result word 0
%end_variables
; instructions and labels
%instructions
nop
syscall WRITE_MEMSTR word:1000
loop:
syscall WRITE_VAR str:"input.prompt"
syscall INPUT_VAR str:"input.result"
syscall WRITE_VAR str:"input.result"
push byte:8d
syscall WRITE_CHAR
jump loop
%end_instructions
*/
class Compiler(private val options: CompilationOptions) {
fun compile(module: Module) : IntermediateForm {
println("\nCompiling parsed source code to intermediate code...")
// todo
fun compile(module: Module) : StackVmProgram {
println("\nCompiling parsed source code to stackvmProg code...")
val namespace = module.definingScope()
// todo
val intermediate = StackVmProgram(module.name)
namespace.debugPrint()
module.statements.filter { it is Block }.map {
with(it as Block) {
"$address $scopedname"
// create the pool of all variables used in all blocks and scopes
val varGather = VarGatherer(intermediate)
varGather.process(module)
val stmtGatherer = StatementGatherer(intermediate, namespace)
stmtGatherer.process(module)
intermediate.toTextLines().forEach { System.out.println(it) }
return intermediate
}
class VarGatherer(val stackvmProg: StackVmProgram): IAstProcessor {
// collect all the VarDecls to make them into one global list
override fun process(decl: VarDecl): IStatement {
assert(decl.type==VarDeclType.VAR) {"only VAR decls should remain: CONST and MEMORY should have been processed away"}
stackvmProg.blockvar(decl.scopedname, decl)
return super.process(decl)
}
}
class StatementGatherer(val stackvmProg: StackVmProgram, val namespace: INameScope): IAstProcessor {
override fun process(subroutine: Subroutine): IStatement {
translate(subroutine.statements)
return super.process(subroutine)
}
override fun process(block: Block): IStatement {
translate(block.statements)
return super.process(block)
}
private fun translate(statements: List<IStatement>) {
for (stmt: IStatement in statements) {
when (stmt) {
is AnonymousStatementList -> translate(stmt.statements)
is BuiltinFunctionStatementPlaceholder -> translate(stmt)
is Label -> translate(stmt)
is Return -> translate(stmt)
is Assignment -> translate(stmt)
is PostIncrDecr -> translate(stmt)
is Jump -> translate(stmt)
is FunctionCallStatement -> translate(stmt)
is InlineAssembly -> translate(stmt)
is IfStatement -> translate(stmt)
is BranchStatement -> translate(stmt)
is Directive, is VarDecl, is Subroutine -> {}
else -> TODO("translate statement $stmt")
}
}
}.forEach { println(it) }
return IntermediateForm(module.name)
}
private fun translate(stmt: BranchStatement) {
println("translate: $stmt")
// todo
}
private fun translate(stmt: IfStatement) {
println("translate: $stmt")
// todo
}
private fun translate(stmt: InlineAssembly) {
println("translate: $stmt")
TODO("inline assembly not supported yet by stackvm")
}
private fun translate(stmt: FunctionCallStatement) {
println("translate: $stmt")
// todo
}
private fun translate(stmt: Jump) {
val instr =
if(stmt.address!=null) {
"jump \$${stmt.address.toString(16)}"
} else {
val target = stmt.identifier!!.targetStatement(namespace)!!
when(target) {
is Label -> "jump ${target.scopedname}"
is Subroutine -> "jump ${target.scopedname}"
else -> throw CompilerException("invalid jump target type ${target::class}")
}
}
stackvmProg.instruction(instr)
}
private fun translate(stmt: PostIncrDecr) {
if(stmt.target.register!=null) {
TODO("register++/-- not yet implemented")
} else {
val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl
when(stmt.operator) {
"++" -> stackvmProg.instruction("inc_var ${targetStatement.scopedname}")
"--" -> stackvmProg.instruction("decr_var ${targetStatement.scopedname}")
}
}
}
private fun translate(stmt: Assignment) {
println("translate: $stmt")
// todo
}
private fun translate(stmt: Return) {
if(stmt.values.isNotEmpty()) {
TODO("return with value(s) not yet supported: $stmt")
}
stackvmProg.instruction("return")
}
private fun translate(stmt: Label) {
stackvmProg.label(stmt.scopedname)
}
private fun translate(stmt: BuiltinFunctionStatementPlaceholder) {
println("translate: $stmt")
// todo
}
}
}
class IntermediateForm(val name: String) {
class StackVmProgram(val name: String) {
val variables = mutableMapOf<String, VarDecl>()
val instructions = mutableListOf<String>()
fun optimize() {
println("\nOptimizing intermediate code...")
println("\nOptimizing stackvmProg code...")
// todo
}
fun compileToAssembly(): AssemblyResult {
println("\nGenerating assembly code from intermediate code... ")
println("\nGenerating assembly code from stackvmProg code... ")
// todo
return AssemblyResult(name)
}
fun blockvar(scopedname: String, decl: VarDecl) {
println("$scopedname $decl")
variables[scopedname] = decl
}
fun toTextLines() : List<String> {
val result = mutableListOf("; stackvm program code for: $name")
result.add("%memory")
// todo memory lines for initial memory initialization
result.add("%end_memory")
result.add("%variables")
for(v in variables) {
assert(v.value.type==VarDeclType.VAR || v.value.type==VarDeclType.CONST)
val litval = v.value.value as LiteralValue
val litvalStr = when {
litval.isNumeric -> litval.asNumericValue.toString()
litval.isString -> "\"${litval.strvalue}\""
else -> TODO()
}
val line = "${v.key} ${v.value.datatype.toString().toLowerCase()} $litvalStr"
result.add(line)
}
result.add("%end_variables")
result.add("%instructions")
result.addAll(instructions)
result.add("%end_nstructions")
return result
}
fun instruction(s: String) {
instructions.add(" $s")
}
fun label(name: String) {
instructions.add("$name:")
}
}
enum class OutputType {

View File

@ -83,7 +83,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
is Subroutine -> stmt.scopedname
else -> throw AstException("invalid call target node type: ${stmt::class}")
}
globalNamespace.registerUsedName(scopedName.joinToString("."))
globalNamespace.registerUsedName(scopedName)
}
fun removeUnusedNodes(usedNames: Set<String>, allScopedSymbolDefinitions: MutableMap<String, IStatement>) {