mirror of
https://github.com/irmen/prog8.git
synced 2024-09-30 15:57:06 +00:00
improved type checking of builtin functions
This commit is contained in:
parent
28aaf38f22
commit
70fe43a6ac
@ -59,15 +59,15 @@ Code
|
|||||||
- label definition
|
- label definition
|
||||||
|
|
||||||
Subroutine
|
Subroutine
|
||||||
Defines a piece of code that can be called by its name from different locations in your code.
|
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 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.
|
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
|
||||||
To keep things simple, you can only define subroutines inside code blocks from a module.
|
Their contents is scoped accordingly.
|
||||||
|
|
||||||
Label
|
Label
|
||||||
This is a named position in your code where you can jump to from another place.
|
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
|
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).
|
subroutine call to a label (but without parameters and return value).
|
||||||
|
|
||||||
|
|
||||||
Scope
|
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).
|
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.
|
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
|
Calling a subroutine
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -22,12 +22,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ main $c003 {
|
~ main $c003 {
|
||||||
|
const byte [2,3] cmatrix1 = [1,2,3,4,5,255]
|
||||||
word lsb1 = lsb($ea31)
|
word lsb1 = lsb($ea31)
|
||||||
word msb1 = msb($ea31)
|
word msb1 = msb($ea31)
|
||||||
byte lsb2 = lsb($ea31)
|
byte lsb2 = lsb($ea31)
|
||||||
byte msb2 = msb($ea31)
|
byte msb2 = msb($ea31)
|
||||||
word lsb3 = lsb($ea)
|
word lsb3 = lsb($ea)
|
||||||
word msb3 = msb($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 len1 = len([1,2,3,wa1, wa2, ws1, all1])
|
||||||
const word wa1 = ceil(abs(-999.22))
|
const word wa1 = ceil(abs(-999.22))
|
||||||
@ -140,7 +143,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(6==6) {
|
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=max([1,233,Y])
|
||||||
X=min([1,2,Y])
|
X=min([1,2,Y])
|
||||||
X=lsl(12)
|
X=lsl(12)
|
||||||
|
@ -735,7 +735,7 @@ data class LiteralValue(val type: DataType,
|
|||||||
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
|
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.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.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)
|
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null)
|
||||||
throw FatalAstException("literal value without actual value")
|
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") {
|
if(target.nameInSource[0] == "P_carry" || target.nameInSource[0]=="P_irqd") {
|
||||||
return null // these have no return value
|
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) {
|
else if(stmt is Subroutine) {
|
||||||
if(stmt.returnvalues.isEmpty()) {
|
if(stmt.returnvalues.isEmpty()) {
|
||||||
@ -1004,7 +1004,6 @@ class FunctionCall(override var target: IdentifierReference,
|
|||||||
}
|
}
|
||||||
TODO("datatype of functioncall to $stmt")
|
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.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.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")
|
else -> throw FatalAstException("invalid parsed literal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,10 +81,6 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
|||||||
checkResult.add(SyntaxError(msg, subroutine.position))
|
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))
|
if(BuiltinFunctionNames.contains(subroutine.name))
|
||||||
err("cannot redefine a built-in function")
|
err("cannot redefine a built-in function")
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ class Compiler(private val options: CompilationOptions) {
|
|||||||
is BranchStatement -> translate(stmt)
|
is BranchStatement -> translate(stmt)
|
||||||
is Directive, is VarDecl, is Subroutine -> {} // skip this, already processed these.
|
is Directive, is VarDecl, is Subroutine -> {} // skip this, already processed these.
|
||||||
is InlineAssembly -> throw CompilerException("inline assembly is not supported by the StackVM")
|
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 {
|
} else {
|
||||||
when(target) {
|
when(target) {
|
||||||
is Subroutine -> stackvmProg.instruction("call ${target.scopedname}")
|
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 {
|
fun generateBreakpointList(): String {
|
||||||
// todo build breakpoint list!
|
// todo build breakpoint list
|
||||||
/*
|
/*
|
||||||
def generate_breakpoint_list(self, program_filename: str) -> str:
|
def generate_breakpoint_list(self, program_filename: str) -> str:
|
||||||
breakpoints = []
|
breakpoints = []
|
||||||
|
@ -53,9 +53,7 @@ class Zeropage(private val options: CompilationOptions) {
|
|||||||
|
|
||||||
val size =
|
val size =
|
||||||
if(vardecl.arrayspec!=null) {
|
if(vardecl.arrayspec!=null) {
|
||||||
if(vardecl.position!=null)
|
println("${vardecl.position} warning: allocating a large value (array) in zeropage")
|
||||||
print(vardecl.position)
|
|
||||||
println(" warning: allocating a large value (array) in zeropage")
|
|
||||||
val y = (vardecl.arrayspec.y as? LiteralValue)?.asIntegerValue
|
val y = (vardecl.arrayspec.y as? LiteralValue)?.asIntegerValue
|
||||||
if(y==null) {
|
if(y==null) {
|
||||||
// 1 dimensional array
|
// 1 dimensional array
|
||||||
@ -78,9 +76,7 @@ class Zeropage(private val options: CompilationOptions) {
|
|||||||
DataType.WORD -> 2
|
DataType.WORD -> 2
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if (options.floats) {
|
if (options.floats) {
|
||||||
if(vardecl.position!=null)
|
println("${vardecl.position} warning: allocating a large value (float) in zeropage")
|
||||||
print(vardecl.position)
|
|
||||||
println(" warning: allocating a large value (float) in zeropage")
|
|
||||||
5
|
5
|
||||||
} else throw CompilerException("floating point option not enabled")
|
} else throw CompilerException("floating point option not enabled")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package il65.functions
|
package il65.functions
|
||||||
|
|
||||||
import il65.ast.*
|
import il65.ast.*
|
||||||
|
import javax.xml.crypto.Data
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
@ -13,6 +14,43 @@ val BuiltinFunctionNames = setOf(
|
|||||||
|
|
||||||
val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf("P_carry", "P_irqd")
|
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")
|
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
|
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||||
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }
|
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ fun Module.optimizeExpressions(globalNamespace: INameScope) {
|
|||||||
x & 0 -> 0
|
x & 0 -> 0
|
||||||
X ^ 0 -> X
|
X ^ 0 -> X
|
||||||
|
|
||||||
|
todo expression optimization: remove redundant builtin function calls
|
||||||
todo expression optimization: reduce expression nesting / flattening of parenthesis
|
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: 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)
|
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"
|
val error = "cannot calculate $left ** $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.asIntegerValue!=null -> when {
|
||||||
// @todo BYTE?
|
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue.toDouble().pow(right.asIntegerValue), left.position)
|
||||||
right.asIntegerValue!=null -> LiteralValue(DataType.WORD, wordvalue = left.asIntegerValue.toDouble().pow(right.asIntegerValue).toInt(), position = left.position)
|
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position)
|
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
|
@ -754,7 +754,7 @@ class Program (prog: MutableList<Instruction>,
|
|||||||
class StackVm(val traceOutputFile: String?) {
|
class StackVm(val traceOutputFile: String?) {
|
||||||
private val mem = Memory()
|
private val mem = Memory()
|
||||||
private val evalstack = MyStack<Value>() // evaluation stack
|
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 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 carry: Boolean = false
|
||||||
private var program = listOf<Instruction>()
|
private var program = listOf<Instruction>()
|
||||||
|
Loading…
Reference in New Issue
Block a user