From 74fd5d29b87818afa5c274224ffe846786339187 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 13 Aug 2018 23:28:04 +0200 Subject: [PATCH] evals --- il65/examples/test.ill | 15 ++- il65/src/il65/Main.kt | 22 ++-- il65/src/il65/ast/AST.kt | 118 ++++++++++++++------ il65/src/il65/ast/AstChecker.kt | 48 +++++++- il65/src/il65/ast/AstOptimizer.kt | 12 +- il65/src/il65/ast/ImportedAstChecker.kt | 10 +- il65/src/il65/functions/BuiltinFunctions.kt | 62 ++++++++++ 7 files changed, 228 insertions(+), 59 deletions(-) create mode 100644 il65/src/il65/functions/BuiltinFunctions.kt diff --git a/il65/examples/test.ill b/il65/examples/test.ill index 7e3495f91..9abe0aad5 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -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 diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index d28626c2b..721179713 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -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) { 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) { diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 5de7e620c..ab9e096f1 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -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 - fun subScopes(): List = 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 = 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) : IStatement? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + fun lookup(scopedName: List, 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) : Node { +data class Module(override val name: String, + override var statements: List) : 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, override val position: Position?) : INameScope - return GlobalNamespace("<<>>", lines, position) + return GlobalNamespace("<<>>", 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) : 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 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) diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index 189ff0bb8..401acc99b 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -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 = mutableListOf() private val blockNames: HashMap = 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() + 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() + 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() + 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") } } } diff --git a/il65/src/il65/ast/AstOptimizer.kt b/il65/src/il65/ast/AstOptimizer.kt index 8dca4bff3..81d3474e9 100644 --- a/il65/src/il65/ast/AstOptimizer.kt +++ b/il65/src/il65/ast/AstOptimizer.kt @@ -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++ diff --git a/il65/src/il65/ast/ImportedAstChecker.kt b/il65/src/il65/ast/ImportedAstChecker.kt index 5a69255b7..c985c6a60 100644 --- a/il65/src/il65/ast/ImportedAstChecker.kt +++ b/il65/src/il65/ast/ImportedAstChecker.kt @@ -24,8 +24,8 @@ class ImportedAstChecker : IAstProcessor { override fun process(module: Module) { super.process(module) - val newLines : MutableList = mutableListOf() - module.lines.forEach { + val newStatements : MutableList = 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 } } diff --git a/il65/src/il65/functions/BuiltinFunctions.kt b/il65/src/il65/functions/BuiltinFunctions.kt new file mode 100644 index 000000000..988f77c89 --- /dev/null +++ b/il65/src/il65/functions/BuiltinFunctions.kt @@ -0,0 +1,62 @@ +package il65.functions + +import il65.ast.IExpression +import il65.ast.INameScope +import il65.ast.LiteralValue + +private fun oneDoubleArg(args: List, 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, 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, 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, namespace: INameScope): LiteralValue = oneDoubleArgOutputInt(args, namespace) { it -> Math.round(it).toInt() } +fun builtin_sin(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::sin) +fun builtin_cos(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::cos) +fun builtin_abs(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::abs) +fun builtin_acos(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::acos) +fun builtin_asin(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::asin) +fun builtin_tan(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::tan) +fun builtin_atan(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::atan) +fun builtin_log(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::log) +fun builtin_log10(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::log10) +fun builtin_sqrt(args: List, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::sqrt) +fun builtin_max(args: List, namespace: INameScope): LiteralValue = twoDoubleArg(args, namespace, Math::max) +fun builtin_min(args: List, namespace: INameScope): LiteralValue = twoDoubleArg(args, namespace, Math::min)