From 70fe43a6ac80a1474b0d4d67bc3c05f03504620b Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 15 Sep 2018 02:12:37 +0200 Subject: [PATCH] improved type checking of builtin functions --- docs/source/programming.rst | 16 ++++---- il65/examples/test.ill | 5 ++- il65/src/il65/ast/AST.kt | 8 ++-- il65/src/il65/ast/AstChecker.kt | 4 -- il65/src/il65/compiler/Compiler.kt | 6 +-- il65/src/il65/compiler/Zeropage.kt | 8 +--- il65/src/il65/functions/BuiltinFunctions.kt | 39 ++++++++++++++++++- .../il65/optimizing/ExpressionOptimizer.kt | 4 +- il65/src/il65/stackvm/StackVm.kt | 2 +- 9 files changed, 63 insertions(+), 29 deletions(-) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 4de36b319..cddd9c537 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -59,15 +59,15 @@ Code - label definition Subroutine - Defines a piece of code that can be called by its name from different locations in your code. - It accepts parameters and can return result values. - It can define its own variables but it's not possible to define subroutines nested in other subroutines. - To keep things simple, you can only define subroutines inside code blocks from a module. + Defines a piece of code that can be called by its name from different locations in your code. + It accepts parameters and can return result values. + It can define its own variables, and it is even possible to define subroutines nested inside other subroutines. + Their contents is scoped accordingly. Label - This is a named position in your code where you can jump to from another place. - You can jump to it with a jump statement elsewhere. It is also possible to use a - subroutine call to a label (but without parameters and return value). + This is a named position in your code where you can jump to from another place. + You can jump to it with a jump statement elsewhere. It is also possible to use a + subroutine call to a label (but without parameters and return value). Scope @@ -371,6 +371,8 @@ For now, only register based parameters are supported (A, X, Y and paired regist the carry status bit SC and the interrupt disable bit SI as specials). For subroutine return values, the special SZ register is also available, it means the zero status bit. +Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly. + Calling a subroutine ^^^^^^^^^^^^^^^^^^^^ diff --git a/il65/examples/test.ill b/il65/examples/test.ill index 0edc4e97e..173f6d124 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -22,12 +22,15 @@ } ~ main $c003 { + const byte [2,3] cmatrix1 = [1,2,3,4,5,255] word lsb1 = lsb($ea31) word msb1 = msb($ea31) byte lsb2 = lsb($ea31) byte msb2 = msb($ea31) word lsb3 = lsb($ea) word msb3 = msb($ea) + const byte matrixsize = len(cmatrix1) + const byte matrixsize2 = len(not_main.cmatrix1) const word len1 = len([1,2,3,wa1, wa2, ws1, all1]) const word wa1 = ceil(abs(-999.22)) @@ -140,7 +143,7 @@ } if(6==6) { - A=sin(X) ; @todo should give error of float loss of precision + A=lsb(sin(X)) X=max([1,233,Y]) X=min([1,2,Y]) X=lsl(12) diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index c74788e31..08e452bdb 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -735,7 +735,7 @@ data class LiteralValue(val type: DataType, DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> if(strvalue==null) throw FatalAstException("literal value missing strvalue") DataType.ARRAY, DataType.ARRAY_W -> if(arrayvalue==null) throw FatalAstException("literal value missing arrayvalue") - DataType.MATRIX -> TODO("matrix literalvalue") + DataType.MATRIX -> TODO("matrix literalvalue? for now, arrays are good enough for this") } if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null) throw FatalAstException("literal value without actual value") @@ -987,7 +987,7 @@ class FunctionCall(override var target: IdentifierReference, if(target.nameInSource[0] == "P_carry" || target.nameInSource[0]=="P_irqd") { return null // these have no return value } - return DataType.BYTE // @todo table lookup to determine result type of builtin function call + return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace) } else if(stmt is Subroutine) { if(stmt.returnvalues.isEmpty()) { @@ -1004,7 +1004,6 @@ class FunctionCall(override var target: IdentifierReference, } TODO("datatype of functioncall to $stmt") } - } @@ -1393,7 +1392,8 @@ private fun il65Parser.ExpressionContext.toAst() : IExpression { } litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition()) litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = litval.stringliteral().text, position = litval.toPosition()) - litval.arrayliteral()!=null -> LiteralValue(DataType.ARRAY, arrayvalue = litval.arrayliteral()?.toAst(), position = litval.toPosition()) // @todo byte/word array difference? + litval.arrayliteral()!=null -> LiteralValue(DataType.ARRAY, arrayvalue = litval.arrayliteral()?.toAst(), position = litval.toPosition()) + // @todo byte/word array difference needed for literal array values? else -> throw FatalAstException("invalid parsed literal") } } diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index c62056ee8..ce9a39b28 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -81,10 +81,6 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: checkResult.add(SyntaxError(msg, subroutine.position)) } -// // subroutines may only be defined directly inside a block @todo NAH, why should we restrict that? -// if(subroutine.parent !is Block) -// err("subroutines can only be defined in a block (not in other scopes)") - if(BuiltinFunctionNames.contains(subroutine.name)) err("cannot redefine a built-in function") diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index 42a12f067..641d7bee7 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -162,7 +162,7 @@ class Compiler(private val options: CompilationOptions) { is BranchStatement -> translate(stmt) is Directive, is VarDecl, is Subroutine -> {} // skip this, already processed these. is InlineAssembly -> throw CompilerException("inline assembly is not supported by the StackVM") - else -> TODO("translate statement $stmt") + else -> TODO("translate statement $stmt to stackvm") } } } @@ -256,7 +256,7 @@ class Compiler(private val options: CompilationOptions) { } else { when(target) { is Subroutine -> stackvmProg.instruction("call ${target.scopedname}") - else -> TODO("non-builtin function call to $target") + else -> TODO("non-builtin-function call to $target") } } } @@ -525,7 +525,7 @@ class AssemblyResult(val name: String) { } fun generateBreakpointList(): String { - // todo build breakpoint list! + // todo build breakpoint list /* def generate_breakpoint_list(self, program_filename: str) -> str: breakpoints = [] diff --git a/il65/src/il65/compiler/Zeropage.kt b/il65/src/il65/compiler/Zeropage.kt index cd5bcc445..829eeffa3 100644 --- a/il65/src/il65/compiler/Zeropage.kt +++ b/il65/src/il65/compiler/Zeropage.kt @@ -53,9 +53,7 @@ class Zeropage(private val options: CompilationOptions) { val size = if(vardecl.arrayspec!=null) { - if(vardecl.position!=null) - print(vardecl.position) - println(" warning: allocating a large value (array) in zeropage") + println("${vardecl.position} warning: allocating a large value (array) in zeropage") val y = (vardecl.arrayspec.y as? LiteralValue)?.asIntegerValue if(y==null) { // 1 dimensional array @@ -78,9 +76,7 @@ class Zeropage(private val options: CompilationOptions) { DataType.WORD -> 2 DataType.FLOAT -> { if (options.floats) { - if(vardecl.position!=null) - print(vardecl.position) - println(" warning: allocating a large value (float) in zeropage") + println("${vardecl.position} warning: allocating a large value (float) in zeropage") 5 } else throw CompilerException("floating point option not enabled") } diff --git a/il65/src/il65/functions/BuiltinFunctions.kt b/il65/src/il65/functions/BuiltinFunctions.kt index a90adc63b..2e82fabf7 100644 --- a/il65/src/il65/functions/BuiltinFunctions.kt +++ b/il65/src/il65/functions/BuiltinFunctions.kt @@ -1,6 +1,7 @@ package il65.functions import il65.ast.* +import javax.xml.crypto.Data import kotlin.math.abs import kotlin.math.floor @@ -13,6 +14,43 @@ val BuiltinFunctionNames = setOf( val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf("P_carry", "P_irqd") +fun builtinFunctionReturnType(function: String, args: List, namespace: INameScope): DataType? { + fun integerDatatypeFromArg(arg: IExpression): DataType { + val dt = arg.resultingDatatype(namespace) + return when(dt) { + DataType.BYTE -> DataType.BYTE + DataType.WORD -> DataType.WORD + DataType.FLOAT -> DataType.WORD + else -> throw FatalAstException("fuction $function can only return a numeric value") + } + } + + fun datatypeFromListArg(arglist: IExpression): DataType { + if(arglist is LiteralValue) { + if(arglist.type==DataType.ARRAY || arglist.type==DataType.ARRAY_W || arglist.type==DataType.MATRIX) { + val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace)} + if(dt.any { it!=DataType.BYTE && it!=DataType.WORD && it!=DataType.FLOAT}) { + throw FatalAstException("fuction $function only accepts array of numeric values") + } + if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT + if(dt.any { it==DataType.WORD }) return DataType.WORD + return DataType.BYTE + } + } + throw FatalAstException("function requires one argument which is an array $function") + } + + return when (function) { + "sin", "cos", "tan", "asin", "acos", "atan", "log", "log10", "sqrt", "rad", "deg", "avg" -> DataType.FLOAT + "len", "lsb", "msb", "any", "all" -> DataType.BYTE + "rol", "rol2", "ror", "ror2", "P_carry", "P_irqd" -> null // no return value so no datatype + "abs" -> args.single().resultingDatatype(namespace) + "max", "min", "sum" -> datatypeFromListArg(args.single()) + "round", "floor", "ceil", "lsl", "lsr" -> integerDatatypeFromArg(args.single()) + else -> throw FatalAstException("invalid builtin function $function") + } +} + class NotConstArgumentException: AstException("not a const argument to a built-in function") @@ -135,7 +173,6 @@ fun builtinAbs(args: List, position: Position, namespace:INameScope } -// todo different functions for byte/word params/results? fun builtinLsb(args: List, position: Position, namespace:INameScope): LiteralValue = oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 } diff --git a/il65/src/il65/optimizing/ExpressionOptimizer.kt b/il65/src/il65/optimizing/ExpressionOptimizer.kt index 0d620370b..dff6f8b68 100644 --- a/il65/src/il65/optimizing/ExpressionOptimizer.kt +++ b/il65/src/il65/optimizing/ExpressionOptimizer.kt @@ -46,6 +46,7 @@ fun Module.optimizeExpressions(globalNamespace: INameScope) { x & 0 -> 0 X ^ 0 -> X + todo expression optimization: remove redundant builtin function calls todo expression optimization: reduce expression nesting / flattening of parenthesis todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0) todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2) @@ -451,8 +452,7 @@ class ConstExprEvaluator { val error = "cannot calculate $left ** $right" return when { left.asIntegerValue!=null -> when { - // @todo BYTE? - right.asIntegerValue!=null -> LiteralValue(DataType.WORD, wordvalue = left.asIntegerValue.toDouble().pow(right.asIntegerValue).toInt(), position = left.position) + right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue.toDouble().pow(right.asIntegerValue), left.position) right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position) else -> throw ExpressionError(error, left.position) } diff --git a/il65/src/il65/stackvm/StackVm.kt b/il65/src/il65/stackvm/StackVm.kt index 6084548db..cde1c0cdc 100644 --- a/il65/src/il65/stackvm/StackVm.kt +++ b/il65/src/il65/stackvm/StackVm.kt @@ -754,7 +754,7 @@ class Program (prog: MutableList, class StackVm(val traceOutputFile: String?) { private val mem = Memory() private val evalstack = MyStack() // evaluation stack - private val callstack = MyStack() // subroutine call stack (@todo maybe use evalstack as well for this?) + private val callstack = MyStack() // subroutine call stack private var variables = mutableMapOf() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name private var carry: Boolean = false private var program = listOf()