fix function calls

This commit is contained in:
Irmen de Jong 2018-08-13 04:12:42 +02:00
parent 13b4afdc35
commit 4d60506d0a
9 changed files with 635 additions and 468 deletions

View File

@ -71,9 +71,7 @@ statement :
labeldef : identifier ':' ;
call_location : integerliteral | identifier | scoped_identifier ;
unconditionaljump : 'goto' call_location ;
unconditionaljump : 'goto' (integerliteral | identifier | scoped_identifier) ;
directive :
directivename=('%output' | '%launcher' | '%zp' | '%address' | '%import' |
@ -137,7 +135,7 @@ expression :
functioncall :
call_location '(' expression_list? ')'
(identifier | scoped_identifier) '(' expression_list? ')'
;
expression_list :
@ -174,11 +172,17 @@ inlineasm : '%asm' INLINEASMBLOCK;
subroutine :
'sub' identifier '(' sub_params? ')' '->' '(' sub_returns? ')' '{' EOL
'sub' identifier '(' sub_params? ')' '->' '(' sub_returns? ')' (sub_address | sub_body)
;
sub_body :
'{' EOL
(statement | EOL) *
'}' EOL
;
sub_address : '=' integerliteral ;
sub_params : sub_param (',' sub_param)* ;
sub_param: identifier ':' register ;

View File

@ -7,6 +7,12 @@
A = "derp" * %000100
sub foo () -> () {
A=99
return 33
X =33
}
return 1+999
%breakpoint
%asminclude "derp", hopsa

View File

@ -97,7 +97,7 @@ fun discoverImportedModule(name: String, importedFrom: Path): Path {
fun executeImportDirective(import: Directive, importedFrom: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if(importedModules.containsKey(moduleName))
return null

View File

@ -34,9 +34,9 @@ enum class Register {
open class AstException(override var message: String) : Exception(message)
class ExpressionException(override var message: String) : AstException(message)
class SyntaxError(override var message: String, val node: Node?) : AstException(message) {
class SyntaxError(override var message: String, val position: Position?) : AstException(message) {
fun printError() {
val location = if(node?.position == null) "" else node.position.toString()
val location = if(position == null) "" else position.toString()
System.err.println("$location $message")
}
}
@ -55,6 +55,8 @@ interface IAstProcessor {
fun process(block: Block): IStatement
fun process(decl: VarDecl): IStatement
fun process(subroutine: Subroutine): IStatement
fun process(jump: Jump): IStatement
fun process(functionCall: FunctionCall): IExpression
}
@ -193,6 +195,14 @@ data class VarDecl(val type: VarDeclType,
}
override fun process(processor: IAstProcessor) = processor.process(this)
val isScalar = arrayspec==null
val isArray = arrayspec!=null && arrayspec.y==null
val isMatrix = arrayspec?.y != null
val arraySizeX : Int?
get() = arrayspec?.x?.constValue()?.intvalue
val arraySizeY : Int?
get() = arrayspec?.y?.constValue()?.intvalue
}
@ -275,24 +285,24 @@ data class LiteralValue(val intvalue: Int? = null,
override var position: Position? = null
override var parent: Node? = null
fun asInt(): Int? {
fun asInt(errorIfNotNumeric: Boolean=true): Int? {
return when {
intvalue!=null -> intvalue
floatvalue!=null -> floatvalue.toInt()
else -> {
if(strvalue!=null || arrayvalue!=null)
if((strvalue!=null || arrayvalue!=null) && errorIfNotNumeric)
throw AstException("attempt to get int value from non-integer $this")
else null
}
}
}
fun asFloat(): Double? {
fun asFloat(errorIfNotNumeric: Boolean=true): Double? {
return when {
floatvalue!=null -> floatvalue
intvalue!=null -> intvalue.toDouble()
else -> {
if(strvalue!=null || arrayvalue!=null)
if((strvalue!=null || arrayvalue!=null) && errorIfNotNumeric)
throw AstException("attempt to get float value from non-integer $this")
else null
}
@ -350,7 +360,7 @@ data class Identifier(val name: String, val scope: List<String>) : IExpression {
}
override fun constValue(): LiteralValue? {
// @todo should look up the identifier and return its value if that is a compile time const
// @todo should look up the location and return its value if that is a compile time const
return null
}
@ -358,19 +368,6 @@ data class Identifier(val name: String, val scope: List<String>) : IExpression {
}
data class CallTarget(val address: Int?, val identifier: Identifier?) : Node {
override var position: Position? = null
override var parent: Node? = null
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
}
fun process(processor: IAstProcessor) = this
}
data class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement {
override var position: Position? = null
override var parent: Node? = null
@ -387,29 +384,26 @@ data class PostIncrDecr(var target: AssignTarget, val operator: String) : IState
}
data class Jump(var target: CallTarget) : IStatement {
data class Jump(val address: Int?, val identifier: Identifier?) : IStatement {
override var position: Position? = null
override var parent: Node? = null
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
identifier?.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement {
target = target.process(processor)
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
data class FunctionCall(var target: CallTarget, var arglist: List<IExpression>) : IExpression {
data class FunctionCall(var location: Identifier, var arglist: List<IExpression>) : IExpression {
override var position: Position? = null
override var parent: Node? = null
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
location.linkParents(this)
arglist.forEach { it.linkParents(this) }
}
@ -418,11 +412,7 @@ data class FunctionCall(var target: CallTarget, var arglist: List<IExpression>)
return null
}
override fun process(processor: IAstProcessor): IExpression {
target = target.process(processor)
arglist = arglist.map{it.process(processor)}
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
@ -441,6 +431,7 @@ data class InlineAssembly(val assembly: String) : IStatement {
data class Subroutine(val name: String,
val parameters: List<SubroutineParameter>,
val returnvalues: List<SubroutineReturnvalue>,
val address: Int?,
var statements: List<IStatement>) : IStatement {
override var position: Position? = null
override var parent: Node? = null
@ -619,7 +610,13 @@ private fun il65Parser.InlineasmContext.toAst(withPosition: Boolean): IStatement
private fun il65Parser.UnconditionaljumpContext.toAst(withPosition: Boolean): IStatement {
val jump = Jump(call_location().toAst(withPosition))
val address = integerliteral()?.toAst()
val identifier =
if(identifier()!=null) identifier()?.toAst(withPosition)
else scoped_identifier()?.toAst(withPosition)
val jump = Jump(address, identifier)
jump.position = toPosition(withPosition)
return jump
}
@ -636,7 +633,8 @@ private fun il65Parser.SubroutineContext.toAst(withPosition: Boolean) : Subrouti
val sub = Subroutine(identifier().text,
if(sub_params()==null) emptyList() else sub_params().toAst(withPosition),
if(sub_returns()==null) emptyList() else sub_returns().toAst(withPosition),
statement().map{ it.toAst(withPosition) })
sub_address()?.integerliteral()?.toAst(),
if(sub_body()==null) emptyList() else sub_body().statement().map {it.toAst(withPosition)})
sub.position = toPosition(withPosition)
return sub
}
@ -653,17 +651,6 @@ private fun il65Parser.Sub_returnsContext.toAst(withPosition: Boolean): List<Sub
}
private fun il65Parser.Call_locationContext.toAst(withPosition: Boolean) : CallTarget {
val address = integerliteral()?.toAst()
val identifier = identifier()
val result =
if(identifier!=null) CallTarget(address, identifier.toAst(withPosition))
else CallTarget(address, scoped_identifier().toAst(withPosition))
result.position = toPosition(withPosition)
return result
}
private fun il65Parser.Assign_targetContext.toAst(withPosition: Boolean) : AssignTarget {
val register = register()?.toAst()
val identifier = identifier()
@ -764,7 +751,7 @@ private fun il65Parser.ExpressionContext.toAst(withPosition: Boolean) : IExpress
val funcall = functioncall()
if(funcall!=null) {
val location = funcall.call_location().toAst(withPosition)
val location = funcall.identifier().toAst(withPosition)
val fcall = if(funcall.expression_list()==null)
FunctionCall(location, emptyList())
else

View File

@ -1,6 +1,7 @@
package il65.ast
import il65.ParsingFailedError
import javax.xml.crypto.Data
fun Module.checkValid() {
@ -16,7 +17,6 @@ fun Module.checkValid() {
class AstChecker : IAstProcessor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
private val blockNames: HashMap<String, Position?> = hashMapOf()
@ -36,13 +36,24 @@ class AstChecker : IAstProcessor {
return expr
}
override fun process(functionCall: FunctionCall): IExpression {
functionCall.arglist.map{it.process(this)}
return functionCall
}
override fun process(jump: Jump): IStatement {
if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
checkResult.add(SyntaxError("jump address must be valid 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))
checkResult.add(SyntaxError("block memory address must be valid 0..\$ffff", block.position))
}
val existing = blockNames[block.name]
if(existing!=null) {
checkResult.add(SyntaxError("block name conflict, first defined in ${existing.file} line ${existing.line}", block))
checkResult.add(SyntaxError("block name conflict, first defined in ${existing.file} line ${existing.line}", block.position))
} else {
blockNames[block.name] = block.position
}
@ -55,7 +66,7 @@ class AstChecker : IAstProcessor {
*/
override fun process(subroutine: Subroutine): IStatement {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, subroutine))
checkResult.add(SyntaxError(msg, subroutine.position))
}
val uniqueNames = subroutine.parameters.map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size)
@ -72,12 +83,15 @@ class AstChecker : IAstProcessor {
// 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) {
if(subroutine.address==null) {
val amount = subroutine.statements
.map {(it as InlineAssembly)?.assembly}
.filter { it is InlineAssembly }
.map {(it as InlineAssembly).assembly}
.count { it.contains(" rts") || it.contains("\trts") ||
it.contains(" jmp") || it.contains("\tjmp")}
if(amount==0)
if(amount==0 )
err("subroutine must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
}
}
return subroutine
@ -88,7 +102,7 @@ class AstChecker : IAstProcessor {
*/
override fun process(decl: VarDecl): IStatement {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, decl))
checkResult.add(SyntaxError(msg, decl.position))
}
when(decl.type) {
VarDeclType.VAR, VarDeclType.CONST -> {
@ -97,38 +111,12 @@ class AstChecker : IAstProcessor {
err("need a compile-time constant initializer value")
decl.value !is LiteralValue ->
err("need a compile-time constant initializer value, found: ${decl.value!!::class.simpleName}")
else -> {
val value = decl.value as LiteralValue
when (decl.datatype) {
DataType.FLOAT -> {
val number = value.asFloat()
if (number == null)
err("need a const float initializer value")
else if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
err("floating point value out of range for MFLPT format")
}
DataType.BYTE -> {
val number = value.asInt()
if (number == null)
err("need a const integer initializer value")
else if (number < 0 || number > 255)
err("value out of range for unsigned byte")
}
DataType.WORD -> {
val number = value.asInt()
if (number == null)
err("need a const integer initializer value")
else if (number < 0 || number > 65535)
err("value out of range for unsigned word")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue
if (str == null)
err("need a const string initializer value")
else if (str.isEmpty() || str.length > 65535)
err("string length must be 1..65535")
}
}
decl.isScalar -> {
checkConstInitializerValueScalar(decl)
checkValueRange(decl.datatype, decl.value as LiteralValue, decl.position)
}
decl.isArray || decl.isMatrix -> {
checkConstInitializerValueArray(decl)
}
}
}
@ -145,12 +133,107 @@ class AstChecker : IAstProcessor {
return decl
}
private fun checkConstInitializerValueArray(decl: VarDecl) {
val value = decl.value as LiteralValue
// init value should either be a scalar or an array with the same dimensions as the arrayspec.
if(decl.isArray) {
if(value.arrayvalue==null) {
checkValueRange(decl.datatype, value.constValue()!!, value.position)
}
else {
if (value.arrayvalue.size != decl.arraySizeX)
checkResult.add(SyntaxError("initializer array size mismatch (expecting ${decl.arraySizeX})", decl.position))
else {
value.arrayvalue.forEach {
checkValueRange(decl.datatype, it.constValue()!!, it.position)
}
}
}
}
if(decl.isMatrix) {
if(value.arrayvalue==null) {
checkValueRange(decl.datatype, value.constValue()!!, value.position)
}
else {
if (value.arrayvalue.size != decl.arraySizeX!! * decl.arraySizeY!!)
checkResult.add(SyntaxError("initializer array size mismatch (expecting ${decl.arraySizeX!! * decl.arraySizeY!!}", decl.position))
else {
value.arrayvalue.forEach {
checkValueRange(decl.datatype, it.constValue()!!, it.position)
}
}
}
}
}
private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, position))
}
when (datatype) {
DataType.FLOAT -> {
val number = value.asFloat(false)
if (number!=null && (number > 1.7014118345e+38 || number < -1.7014118345e+38))
err("floating point value out of range for MFLPT format")
}
DataType.BYTE -> {
val number = value.asInt(false)
if (number!=null && (number < 0 || number > 255))
err("value out of range for unsigned byte")
}
DataType.WORD -> {
val number = value.asInt(false)
if (number!=null && (number < 0 || number > 65535))
err("value out of range for unsigned word")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue
if (str!=null && (str.isEmpty() || str.length > 65535))
err("string length must be 1..65535")
}
}
}
private fun checkConstInitializerValueScalar(decl: VarDecl) {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, decl.position))
}
val value = decl.value as LiteralValue
when (decl.datatype) {
DataType.FLOAT -> {
val number = value.asFloat(false)
if (number == null)
err("need a const float initializer value")
}
DataType.BYTE -> {
val number = value.asInt(false)
if (number == null)
err("need a const integer initializer value")
}
DataType.WORD -> {
val number = value.asInt(false)
if (number == null)
err("need a const integer initializer value")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue
if (str == null)
err("need a const string initializer value")
}
}
}
/**
* check the arguments of the directive
*/
override fun process(directive: Directive): IStatement {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, directive))
checkResult.add(SyntaxError(msg, directive.position))
}
when(directive.directive) {
"%output" -> {

View File

@ -39,6 +39,15 @@ class AstOptimizer : IAstProcessor {
return subroutine
}
override fun process(functionCall: FunctionCall): IExpression {
functionCall.arglist = functionCall.arglist.map{it.process(this)}
return functionCall
}
override fun process(jump: Jump): IStatement {
return jump
}
override fun process(decl: VarDecl): IStatement {
decl.value = decl.value?.process(this)

View File

@ -65,4 +65,12 @@ class ImportedAstChecker : IAstProcessor {
return directive
}
override fun process(functionCall: FunctionCall): IExpression {
return functionCall
}
override fun process(jump: Jump): IStatement {
return jump
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
; ;
; indent format: TABS, size=8
%output enable_floats
; todo %option enable_floats
~ c64 {
@ -172,7 +172,7 @@ sub SQR () -> (?) = $bf71 ; fac1 = SQRT(fac1)
sub EXP () -> (?) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
sub NEGOP () -> (A?) = $bfb4 ; switch the sign of fac1
sub RND () -> (?) = $e097 ; fac1 = RND() (use RNDA instead)
sub RNDA (A) -> (?) = $e09a ; fac1 = RND(A)
sub RNDA (acc: A) -> (?) = $e09a ; fac1 = RND(A)
sub COS () -> (?) = $e264 ; fac1 = COS(fac1)
sub SIN () -> (?) = $e26b ; fac1 = SIN(fac1)
sub TAN () -> (?) = $e2b4 ; fac1 = TAN(fac1)
@ -206,7 +206,7 @@ sub MEMBOT (dir: SC, address: XY) -> (XY) = $FF9C ; read/set bottom of memory
sub SCNKEY () -> (?) = $FF9F ; scan the keyboard
sub SETTMO (timeout: A) -> () = $FFA2 ; set time-out flag for IEEE bus
sub ACPTR () -> (A) = $FFA5 ; (alias: IECIN) input byte from serial bus
sub CIOUT (byte: A) -> () = $FFA8 ; (alias: IECOUT) output byte to serial bus
sub CIOUT (databyte: A) -> () = $FFA8 ; (alias: IECOUT) output byte to serial bus
sub UNTLK () -> (A?) = $FFAB ; command serial bus device to UNTALK
sub UNLSN () -> (A?) = $FFAE ; command serial bus device to UNLISTEN
sub LISTEN (device: A) -> (A?) = $FFB1 ; command serial bus device to LISTEN
@ -269,7 +269,6 @@ sub init_system () -> (?) {
}}
}
%noreturn
} ; ------ end of block c64
@ -452,8 +451,6 @@ sub float_sub_SW1_from_XY (mflt: XY) -> (?) {
}}
}
%noreturn
} ; ------ end of block c64flt
@ -743,8 +740,8 @@ hex_digits .str "0123456789abcdef" ; can probably be reused for other stuff as w
}
var str word2hex_output = "1234" ; 0-terminated, to make printing easier
sub word2hex (word: XY) -> (?) {
str word2hex_output = "1234" ; 0-terminated, to make printing easier
sub word2hex (dataword: XY) -> (?) {
; ---- convert 16 bit word in X/Y into 4-character hexadecimal string into memory 'word2hex_output'
%asm {{
stx c64.SCRATCH_ZP2
@ -760,9 +757,8 @@ sub word2hex (word: XY) -> (?) {
}}
}
var array(3) word2bcd_bcdbuff
sub word2bcd (word: XY) -> (A?, X?) {
byte[3] word2bcd_bcdbuff = [0, 0, 0]
sub word2bcd (dataword: XY) -> (A?, X?) {
; Convert an 16 bit binary value to BCD
;
; This function converts a 16 bit binary value in X/Y into a 24 bit BCD. It
@ -799,8 +795,8 @@ sub word2bcd (word: XY) -> (A?, X?) {
}
var array(5) word2decimal_output
sub word2decimal (word: XY) -> (?) {
byte[5] word2decimal_output = 0
sub word2decimal (dataword: XY) -> (?) {
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
%asm {{
jsr word2bcd
@ -955,7 +951,7 @@ sub print_byte_hex (prefix: SC, ubyte: A) -> (?) {
}
sub print_word_hex (prefix: SC, word: XY) -> (?) {
sub print_word_hex (prefix: SC, dataword: XY) -> (?) {
; ---- print the (unsigned) word in X/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
@ -969,7 +965,7 @@ sub print_word_hex (prefix: SC, word: XY) -> (?) {
}
sub print_word_decimal0 (word: XY) -> (?) {
sub print_word_decimal0 (dataword: XY) -> (?) {
; ---- print the (unsigned) word in X/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr word2decimal
@ -987,7 +983,7 @@ sub print_word_decimal0 (word: XY) -> (?) {
}
sub print_word_decimal (word: XY) -> (A?, X?, Y?) {
sub print_word_decimal (dataword: XY) -> (A?, X?, Y?) {
; ---- print the word in X/Y in decimal form, without left padding 0s
%asm {{
jsr word2decimal
@ -1041,8 +1037,6 @@ sub input_chars (buffer: AX) -> (A?, Y) {
}}
}
%noreturn
} ; ---- end block c64scr