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 {
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 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 + derp.foo.bar
const float zwop2 = -1.7014118345e+38
const float blerp2 = zwop / 2.22
XY = hopla*2+hopla1
A = "derp" * %000100
mega:
@ -13,6 +21,7 @@ cool:
Y=2
sub foo () -> () {
byte blerp = 3
A=99
return 33
X =33

View File

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

View File

@ -1,5 +1,6 @@
package il65.ast
import il65.functions.*
import il65.parser.il65Parser
import org.antlr.v4.runtime.ParserRuleContext
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) {
fun printError() {
val location = if(position == null) "" else position.toString()
val location = position?.toString() ?: ""
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 {
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 {
expr.expression = expr.expression.process(this)
@ -80,6 +81,9 @@ interface IAstProcessor {
functionCall.arglist = functionCall.arglist.map { it.process(this) }
return functionCall
}
fun process(identifier: Identifier): IExpression {
return identifier
}
fun process(jump: Jump): IStatement {
return jump
}
@ -103,28 +107,59 @@ interface INameScope {
val position: Position?
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? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
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 printNames(indent: Int, namespace: INameScope) {
println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}")
namespace.definedNames().forEach {
val name =
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}")
println(" ".repeat(4 * (1 + indent)) + "${it.key} -> ${it.value::class.simpleName} at ${it.value.position}")
}
namespace.subScopes().forEach {
printNames(indent+1, it)
printNames(indent+1, it.value)
}
}
printNames(0, this)
@ -132,8 +167,8 @@ interface INameScope {
}
data class Module(val name: String,
var lines: List<IStatement>) : Node {
data class Module(override val name: String,
override var statements: List<IStatement>) : Node, INameScope {
override var position: Position? = null
override var parent: Node? = null
@ -142,7 +177,7 @@ data class Module(val name: String,
}
fun linkParents() {
parent = null
lines.forEach {it.linkParents(this)}
statements.forEach {it.linkParents(this)}
}
fun process(processor: IAstProcessor) {
@ -154,7 +189,7 @@ data class Module(val name: String,
override var statements: List<IStatement>,
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)
}
override fun constValue(namespace: INameScope): LiteralValue? {
throw ExpressionException("should have been optimized away before const value was asked")
}
override fun constValue(namespace: INameScope): LiteralValue? = null
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? {
val node = namespace.lookup(scopedName)
return if(node==null) null
else {
var vardecl = node as VarDecl
if(vardecl!=null){
if(vardecl.type!=VarDeclType.CONST)
throw SyntaxError("constant expected", position)
return vardecl.value?.constValue(namespace)
}
throw SyntaxError("expected a literal value", position)
val node = namespace.lookup(scopedName, this)
?:
throw SyntaxError("undefined symbol: ${scopedName.joinToString(".")}", position) // todo add to a list of errors instead
val vardecl = node as? VarDecl
if(vardecl==null) {
// todo add to a list of errors instead
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)
}
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? {
// 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)

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 blockNames: HashMap<String, Position?> = hashMapOf()
@ -26,13 +26,13 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
override fun process(jump: Jump): IStatement {
super.process(jump)
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
}
override fun process(block: Block): IStatement {
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]
if(existing!=null) {
@ -54,6 +54,31 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
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
}
@ -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'
// (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) {
@ -129,9 +166,12 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
}
}
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
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
private set
@ -26,6 +26,14 @@ class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
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.
@ -43,7 +51,7 @@ class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
expr.operator == "-" -> when {
subexpr.intvalue != null -> {
optimizationsDone++
LiteralValue(intvalue = subexpr.intvalue)
LiteralValue(intvalue = -subexpr.intvalue)
}
subexpr.floatvalue != null -> {
optimizationsDone++

View File

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