improved type checking of builtin functions

This commit is contained in:
Irmen de Jong 2018-09-15 02:12:37 +02:00
parent 28aaf38f22
commit 70fe43a6ac
9 changed files with 63 additions and 29 deletions

View File

@ -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
^^^^^^^^^^^^^^^^^^^^

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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")

View File

@ -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 = []

View File

@ -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")
}

View File

@ -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<IExpression>, 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<IExpression>, position: Position, namespace:INameScope
}
// todo different functions for byte/word params/results?
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }

View File

@ -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)
}

View File

@ -754,7 +754,7 @@ class Program (prog: MutableList<Instruction>,
class StackVm(val traceOutputFile: String?) {
private val mem = Memory()
private val evalstack = MyStack<Value>() // evaluation stack
private val callstack = MyStack<Instruction>() // subroutine call stack (@todo maybe use evalstack as well for this?)
private val callstack = MyStack<Instruction>() // subroutine call stack
private var variables = mutableMapOf<String, Value>() // 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<Instruction>()