This commit is contained in:
Irmen de Jong 2018-08-13 23:28:04 +02:00
parent 4c250fdc17
commit 74fd5d29b8
7 changed files with 228 additions and 59 deletions

View File

@ -1,10 +1,18 @@
~ main $c003 { ~ main $c003 {
memory byte derp = $ffdd ;memory byte derp = $ffdd
;memory byte derp2 = 2+$ffdd+sin(3)
memory byte derp3 = round(sin(3))
const byte hopla=55-33 const byte hopla=55-33
const byte hopla2=55-hopla const byte hopla2=100+(-main.hopla)
const byte hopla3=100+(-hopla)
const byte hopla4 = 100-hopla
const byte hopla1=main.hopla
const float blerp1 = zwop / 2.22
const float zwop = -1.7014118345e+38 const float zwop = -1.7014118345e+38
const float zwop = -1.7014118345e+38 + derp.foo.bar const float zwop2 = -1.7014118345e+38
const float blerp2 = zwop / 2.22
XY = hopla*2+hopla1
A = "derp" * %000100 A = "derp" * %000100
mega: mega:
@ -13,6 +21,7 @@ cool:
Y=2 Y=2
sub foo () -> () { sub foo () -> () {
byte blerp = 3
A=99 A=99
return 33 return 33
X =33 X =33

View File

@ -58,7 +58,7 @@ fun loadModule(filePath: Path) : Module {
importedModules[moduleAst.name] = moduleAst importedModules[moduleAst.name] = moduleAst
// process imports // process imports
val lines = moduleAst.lines.toMutableList() val lines = moduleAst.statements.toMutableList()
val imports = lines val imports = lines
.mapIndexed { i, it -> Pair(i, it) } .mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" } .filter { (it.second as? Directive)?.directive == "%import" }
@ -70,11 +70,11 @@ fun loadModule(filePath: Path) : Module {
lines.removeAt(it.first) lines.removeAt(it.first)
} else { } else {
// merge imported lines at this spot // merge imported lines at this spot
lines.addAll(it.first, it.second!!.lines) lines.addAll(it.first, it.second!!.statements)
} }
} }
moduleAst.lines = lines moduleAst.statements = lines
return moduleAst return moduleAst
} }
@ -124,16 +124,16 @@ fun main(args: Array<String>) {
val filepath = Paths.get(args[0]).normalize() val filepath = Paths.get(args[0]).normalize()
val moduleAst = loadModule(filepath) val moduleAst = loadModule(filepath)
moduleAst.linkParents() moduleAst.linkParents()
var globalNamespace = moduleAst.namespace() val globalNamespace = moduleAst.namespace()
globalNamespace.debugPrint() globalNamespace.debugPrint()
// moduleAst.optimize(namespace) moduleAst.optimize(globalNamespace)
// moduleAst.checkValid() // check if final tree is valid moduleAst.checkValid(globalNamespace) // check if final tree is valid
//
// // todo compile to asm... // todo compile to asm...
// moduleAst.lines.forEach { moduleAst.statements.forEach {
// println(it) println(it)
// } }
} catch(sx: SyntaxError) { } catch(sx: SyntaxError) {
sx.printError() sx.printError()
} catch (px: ParsingFailedError) { } catch (px: ParsingFailedError) {

View File

@ -1,5 +1,6 @@
package il65.ast package il65.ast
import il65.functions.*
import il65.parser.il65Parser import il65.parser.il65Parser
import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode import org.antlr.v4.runtime.tree.TerminalNode
@ -36,7 +37,7 @@ class ExpressionException(override var message: String) : AstException(message)
class SyntaxError(override var message: String, val position: Position?) : AstException(message) { class SyntaxError(override var message: String, val position: Position?) : AstException(message) {
fun printError() { fun printError() {
val location = if(position == null) "" else position.toString() val location = position?.toString() ?: ""
System.err.println("$location $message") System.err.println("$location $message")
} }
} }
@ -49,7 +50,7 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
interface IAstProcessor { interface IAstProcessor {
fun process(module: Module) { fun process(module: Module) {
module.lines = module.lines.map { it.process(this) } module.statements = module.statements.map { it.process(this) }
} }
fun process(expr: PrefixExpression): IExpression { fun process(expr: PrefixExpression): IExpression {
expr.expression = expr.expression.process(this) expr.expression = expr.expression.process(this)
@ -80,6 +81,9 @@ interface IAstProcessor {
functionCall.arglist = functionCall.arglist.map { it.process(this) } functionCall.arglist = functionCall.arglist.map { it.process(this) }
return functionCall return functionCall
} }
fun process(identifier: Identifier): IExpression {
return identifier
}
fun process(jump: Jump): IStatement { fun process(jump: Jump): IStatement {
return jump return jump
} }
@ -103,28 +107,59 @@ interface INameScope {
val position: Position? val position: Position?
var statements: List<IStatement> var statements: List<IStatement>
fun subScopes(): List<INameScope> = statements.filter { it is INameScope }.map { it as INameScope } fun subScopes() = statements.filter { it is INameScope } .map { it as INameScope }.associate { it.name to it }
fun definedNames(): List<IStatement> = statements.filter { it is Label || it is VarDecl } fun definedNames() = statements.filter { it is Label || it is VarDecl }
.associate {
when(it) {
is Label -> it.name to it
is VarDecl -> it.name to it
else -> throw AstException("expected label or vardecl")
}
}
fun lookup(scopedName: List<String>) : IStatement? { fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. if(scopedName.size>1) {
// it's a qualified name, look it up from the namespace root
var scope: INameScope? = this
scopedName.dropLast(1).forEach {
scope = scope?.subScopes()?.get(it)
if(scope==null)
return null
}
val foundScope : INameScope = scope!!
return foundScope.definedNames()[scopedName.last()]
?:
foundScope.subScopes()[scopedName.last()] as IStatement?
} else {
// unqualified name, find the scope the statement is in, look in that first
var statementScope: Node? = statement
while(true) {
while (statementScope !is INameScope && statementScope?.parent != null)
statementScope = statementScope.parent
if (statementScope == null)
return null
val localScope = statementScope as INameScope
val result = localScope.definedNames()[scopedName[0]]
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
}
} }
fun debugPrint() { fun debugPrint() {
fun printNames(indent: Int, namespace: INameScope) { fun printNames(indent: Int, namespace: INameScope) {
println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}") println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}")
namespace.definedNames().forEach { namespace.definedNames().forEach {
val name = println(" ".repeat(4 * (1 + indent)) + "${it.key} -> ${it.value::class.simpleName} at ${it.value.position}")
when(it) {
is Label -> it.name
is VarDecl -> it.name
else -> throw AstException("expected label or vardecl")
}
println(" ".repeat(4 * (1 + indent)) + "$name -> ${it::class.simpleName} at ${it.position}")
} }
namespace.subScopes().forEach { namespace.subScopes().forEach {
printNames(indent+1, it) printNames(indent+1, it.value)
} }
} }
printNames(0, this) printNames(0, this)
@ -132,8 +167,8 @@ interface INameScope {
} }
data class Module(val name: String, data class Module(override val name: String,
var lines: List<IStatement>) : Node { override var statements: List<IStatement>) : Node, INameScope {
override var position: Position? = null override var position: Position? = null
override var parent: Node? = null override var parent: Node? = null
@ -142,7 +177,7 @@ data class Module(val name: String,
} }
fun linkParents() { fun linkParents() {
parent = null parent = null
lines.forEach {it.linkParents(this)} statements.forEach {it.linkParents(this)}
} }
fun process(processor: IAstProcessor) { fun process(processor: IAstProcessor) {
@ -154,7 +189,7 @@ data class Module(val name: String,
override var statements: List<IStatement>, override var statements: List<IStatement>,
override val position: Position?) : INameScope override val position: Position?) : INameScope
return GlobalNamespace("<<<global>>>", lines, position) return GlobalNamespace("<<<global>>>", statements, position)
} }
} }
@ -327,10 +362,7 @@ data class PrefixExpression(val operator: String, var expression: IExpression) :
expression.linkParents(this) expression.linkParents(this)
} }
override fun constValue(namespace: INameScope): LiteralValue? { override fun constValue(namespace: INameScope): LiteralValue? = null
throw ExpressionException("should have been optimized away before const value was asked")
}
override fun process(processor: IAstProcessor) = processor.process(this) override fun process(processor: IAstProcessor) = processor.process(this)
} }
@ -434,20 +466,21 @@ data class Identifier(val scopedName: List<String>) : IExpression {
} }
override fun constValue(namespace: INameScope): LiteralValue? { override fun constValue(namespace: INameScope): LiteralValue? {
val node = namespace.lookup(scopedName) val node = namespace.lookup(scopedName, this)
return if(node==null) null ?:
else { throw SyntaxError("undefined symbol: ${scopedName.joinToString(".")}", position) // todo add to a list of errors instead
var vardecl = node as VarDecl val vardecl = node as? VarDecl
if(vardecl!=null){ if(vardecl==null) {
if(vardecl.type!=VarDeclType.CONST) // todo add to a list of errors instead
throw SyntaxError("constant expected", position) throw SyntaxError("name should be a constant, instead of: ${node::class.simpleName}", position)
} else if(vardecl.type!=VarDeclType.CONST) {
// todo add to a list of errors instead
throw SyntaxError("name should be a constant, instead of: ${vardecl.type}", position)
}
return vardecl.value?.constValue(namespace) return vardecl.value?.constValue(namespace)
} }
throw SyntaxError("expected a literal value", position)
}
}
override fun process(processor: IAstProcessor) = this override fun process(processor: IAstProcessor) = processor.process(this)
} }
@ -492,7 +525,24 @@ data class FunctionCall(var location: Identifier, var arglist: List<IExpression>
override fun constValue(namespace: INameScope): LiteralValue? { override fun constValue(namespace: INameScope): LiteralValue? {
// if the function is a built-in function and the args are consts, should evaluate! // if the function is a built-in function and the args are consts, should evaluate!
return null println("CONSTVALUE of Function call $location") // todo
if(location.scopedName.size>1) return null
return when(location.scopedName[0]){
"sin" -> builtin_sin(arglist, namespace)
"cos" -> builtin_cos(arglist, namespace)
"abs" -> builtin_abs(arglist, namespace)
"acos" -> builtin_acos(arglist, namespace)
"asin" -> builtin_asin(arglist, namespace)
"tan" -> builtin_tan(arglist, namespace)
"atan" -> builtin_atan(arglist, namespace)
"log" -> builtin_log(arglist, namespace)
"log10" -> builtin_log10(arglist, namespace)
"sqrt" -> builtin_sqrt(arglist, namespace)
"max" -> builtin_max(arglist, namespace)
"min" -> builtin_min(arglist, namespace)
"round" -> builtin_round(arglist, namespace)
else -> null
}
} }
override fun process(processor: IAstProcessor) = processor.process(this) override fun process(processor: IAstProcessor) = processor.process(this)

View File

@ -15,7 +15,7 @@ fun Module.checkValid(globalNamespace: INameScope) {
} }
class AstChecker(val globalNamespace: INameScope) : IAstProcessor { class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
private val checkResult: MutableList<SyntaxError> = mutableListOf() private val checkResult: MutableList<SyntaxError> = mutableListOf()
private val blockNames: HashMap<String, Position?> = hashMapOf() private val blockNames: HashMap<String, Position?> = hashMapOf()
@ -26,13 +26,13 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
override fun process(jump: Jump): IStatement { override fun process(jump: Jump): IStatement {
super.process(jump) super.process(jump)
if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
checkResult.add(SyntaxError("jump address must be valid 0..\$ffff", jump.position)) checkResult.add(SyntaxError("jump address must be valid integer 0..\$ffff", jump.position))
return jump return jump
} }
override fun process(block: Block): IStatement { override fun process(block: Block): IStatement {
if(block.address!=null && (block.address<0 || block.address>65535)) { if(block.address!=null && (block.address<0 || block.address>65535)) {
checkResult.add(SyntaxError("block memory address must be valid 0..\$ffff", block.position)) checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position))
} }
val existing = blockNames[block.name] val existing = blockNames[block.name]
if(existing!=null) { if(existing!=null) {
@ -54,6 +54,31 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
labelnames[it.name] = it.position labelnames[it.name] = it.position
} }
} }
// check if var names are unique
val variables = block.statements.filter { it is VarDecl }.map{ it as VarDecl }
val varnames= mutableMapOf<String, Position?>()
variables.forEach {
val existing = varnames[it.name]
if(existing!=null) {
checkResult.add(SyntaxError("variable name conflict, first defined on line ${existing.line}", it.position))
} else {
varnames[it.name] = it.position
}
}
// check if subroutine names are unique
val subroutines = block.statements.filter { it is Subroutine }.map{ it as Subroutine }
val subnames = mutableMapOf<String, Position?>()
subroutines.forEach {
val existing = subnames[it.name]
if(existing!=null) {
checkResult.add(SyntaxError("subroutine name conflict, first defined on line ${existing.line}", it.position))
} else {
subnames[it.name] = it.position
}
}
return block return block
} }
@ -88,6 +113,18 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
} }
} }
// check if var names are unique
val variables = subroutine.statements.filter { it is VarDecl }.map{ it as VarDecl }
val varnames= mutableMapOf<String, Position?>()
variables.forEach {
val existing = varnames[it.name]
if(existing!=null) {
checkResult.add(SyntaxError("variable name conflict, first defined on line ${existing.line}", it.position))
} else {
varnames[it.name] = it.position
}
}
// subroutine must contain at least one 'return' or 'goto' // subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp') // (or if it has an asm block, that must contain a 'rts' or 'jmp')
if(subroutine.statements.count { it is Return || it is Jump } == 0) { if(subroutine.statements.count { it is Return || it is Jump } == 0) {
@ -129,9 +166,12 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
} }
} }
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
if(decl.value !is LiteralValue)
throw AstException("${decl.value?.position} value of memory var decl is not a literal (it is a ${decl.value!!::class.simpleName}). This is likely a bug in the AstOptimizer")
val value = decl.value as LiteralValue val value = decl.value as LiteralValue
if(value.intvalue==null || value.intvalue<0 || value.intvalue>65535) { if(value.intvalue==null || value.intvalue<0 || value.intvalue>65535) {
err("memory address must be valid 0..\$ffff") err("memory address must be valid integer 0..\$ffff")
} }
} }
} }

View File

@ -18,7 +18,7 @@ fun Module.optimize(globalNamespace: INameScope) {
} }
class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor { class AstOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
private set private set
@ -26,6 +26,14 @@ class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
optimizationsDone = 0 optimizationsDone = 0
} }
/**
* some identifiers can be replaced with the constant value they refer to
*/
override fun process(identifier: Identifier): IExpression {
println("PROCESS ID $identifier") // todo
val const = identifier.constValue(globalNamespace)
return const ?: identifier
}
/** /**
* Try to process a unary prefix expression. * Try to process a unary prefix expression.
@ -43,7 +51,7 @@ class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
expr.operator == "-" -> when { expr.operator == "-" -> when {
subexpr.intvalue != null -> { subexpr.intvalue != null -> {
optimizationsDone++ optimizationsDone++
LiteralValue(intvalue = subexpr.intvalue) LiteralValue(intvalue = -subexpr.intvalue)
} }
subexpr.floatvalue != null -> { subexpr.floatvalue != null -> {
optimizationsDone++ optimizationsDone++

View File

@ -24,8 +24,8 @@ class ImportedAstChecker : IAstProcessor {
override fun process(module: Module) { override fun process(module: Module) {
super.process(module) super.process(module)
val newLines : MutableList<IStatement> = mutableListOf() val newStatements : MutableList<IStatement> = mutableListOf()
module.lines.forEach { module.statements.forEach {
val stmt = it.process(this) val stmt = it.process(this)
if(stmt is Directive) { if(stmt is Directive) {
if(stmt.parent is Module) { if(stmt.parent is Module) {
@ -33,12 +33,12 @@ class ImportedAstChecker : IAstProcessor {
"%output", "%launcher", "%zp", "%address" -> "%output", "%launcher", "%zp", "%address" ->
println("${stmt.position} Warning: ignoring module directive because it was imported: ${stmt.directive}") println("${stmt.position} Warning: ignoring module directive because it was imported: ${stmt.directive}")
else -> else ->
newLines.add(stmt) newStatements.add(stmt)
} }
} }
} }
else newLines.add(stmt) else newStatements.add(stmt)
} }
module.lines = newLines module.statements = newStatements
} }
} }

View File

@ -0,0 +1,62 @@
package il65.functions
import il65.ast.IExpression
import il65.ast.INameScope
import il65.ast.LiteralValue
private fun oneDoubleArg(args: List<IExpression>, namespace: INameScope, function: (arg: Double)->Double): LiteralValue {
if(args.size!=1)
throw UnsupportedOperationException("built-in function requires one floating point argument")
val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) {
val result = LiteralValue(floatvalue = function(float))
result.position = args[0].position
return result
}
else
throw UnsupportedOperationException("built-in function requires floating point value as argument")
}
private fun oneDoubleArgOutputInt(args: List<IExpression>, namespace: INameScope, function: (arg: Double)->Int): LiteralValue {
if(args.size!=1)
throw UnsupportedOperationException("built-in function requires one floating point argument")
val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) {
val result = LiteralValue(intvalue = function(float))
result.position = args[0].position
return result
}
else
throw UnsupportedOperationException("built-in function requires floating point value as argument")
}
private fun twoDoubleArg(args: List<IExpression>, namespace: INameScope, function: (arg1: Double, arg2: Double)->Double): LiteralValue {
if(args.size!=2)
throw UnsupportedOperationException("built-in function requires two floating point arguments")
val float1 = args[0].constValue(namespace)?.asFloat()
val float2 = args[1].constValue(namespace)?.asFloat()
if(float1!=null && float2!=null) {
val result = LiteralValue(floatvalue = function(float1, float2))
result.position = args[0].position
return result
}
else
throw UnsupportedOperationException("built-in function requires two floating point values as argument")
}
fun builtin_round(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArgOutputInt(args, namespace) { it -> Math.round(it).toInt() }
fun builtin_sin(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::sin)
fun builtin_cos(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::cos)
fun builtin_abs(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::abs)
fun builtin_acos(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::acos)
fun builtin_asin(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::asin)
fun builtin_tan(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::tan)
fun builtin_atan(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::atan)
fun builtin_log(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::log)
fun builtin_log10(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::log10)
fun builtin_sqrt(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::sqrt)
fun builtin_max(args: List<IExpression>, namespace: INameScope): LiteralValue = twoDoubleArg(args, namespace, Math::max)
fun builtin_min(args: List<IExpression>, namespace: INameScope): LiteralValue = twoDoubleArg(args, namespace, Math::min)