mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
more stackvm opcodes, and sort of finished the stackvm compiler
This commit is contained in:
parent
efd3b1f5c6
commit
949e468543
@ -115,8 +115,9 @@ Directives
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point, il65 will not process this at all.
|
||||
The scopelabel will be used as a prefix to access the labels from the included source code,
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
il65 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
|
||||
.. data:: %breakpoint
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
IL65 combined lexer and parser grammar
|
||||
IL65 combined lexer bitand parser grammar
|
||||
|
||||
NOTES:
|
||||
|
||||
- whitespace is ignored. (tabs/spaces)
|
||||
- every position can be empty, be a comment, or contain ONE statement.
|
||||
- every position can be empty, be a comment, bitor contain ONE statement.
|
||||
|
||||
*/
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
label_in_extra2:
|
||||
X = 33
|
||||
return
|
||||
|
||||
sub sub_in_extra2() -> () {
|
||||
return
|
||||
|
@ -59,13 +59,13 @@
|
||||
str ascending5 = "z" to "z"
|
||||
const byte cc = 4 + (2==9)
|
||||
byte cc2 = 4 - (2==10)
|
||||
memory byte derp = max([$ffdd])
|
||||
memory byte derpA = abs(-20000)
|
||||
memory byte derpB = max([1, 2.2, 4.4, 100])
|
||||
memory byte cderp = min([$ffdd])+ (1/1)
|
||||
memory byte cderpA = min([$ffdd, 10, 20, 30])
|
||||
memory byte cderpB = min([1, 2.2, 4.4, 100])
|
||||
memory byte derp2 = 2+$ffdd+round(10*sin(3.1))
|
||||
; memory byte derp = max([$ffdd]) ; @todo implement memory vars in stackvm
|
||||
; memory byte derpA = abs(-20000)
|
||||
; memory byte derpB = max([1, 2.2, 4.4, 100])
|
||||
; memory byte cderp = min([$ffdd])+ (1/1)
|
||||
; memory byte cderpA = min([$ffdd, 10, 20, 30])
|
||||
; memory byte cderpB = min([1, 2.2, 4.4, 100])
|
||||
; memory byte derp2 = 2+$ffdd+round(10*sin(3.1))
|
||||
const byte hopla=55-33
|
||||
const byte hopla3=100+(-hopla)
|
||||
const byte hopla4 = 100-hopla
|
||||
@ -77,10 +77,10 @@
|
||||
const byte equal = 4==4
|
||||
const byte equal2 = (4+hopla)>0
|
||||
|
||||
goto 64738
|
||||
; goto 64738
|
||||
|
||||
; A++
|
||||
derp++
|
||||
A++
|
||||
; derp++
|
||||
cc2--
|
||||
|
||||
goto mega
|
||||
@ -92,12 +92,19 @@
|
||||
A=100
|
||||
}
|
||||
|
||||
%breakpoint
|
||||
|
||||
byte equalQQ = 4==4
|
||||
const byte equalQQ2 = (4+hopla)>0
|
||||
|
||||
sin(1) ; @todo error statement has no effect (returnvalue of builtin function not used)
|
||||
equalQQ++
|
||||
cos(2) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement)
|
||||
cos(A) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement)
|
||||
AX++
|
||||
A=X + round(sin(Y))
|
||||
equalQQ= X
|
||||
equalQQ= len([X, Y, AX])
|
||||
len([X, Y, AX]) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement)
|
||||
sin(1) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement)
|
||||
P_carry(1)
|
||||
P_irqd(0)
|
||||
|
||||
@ -118,7 +125,6 @@
|
||||
|
||||
if (5==5) {
|
||||
A=99
|
||||
%breakpoint
|
||||
}
|
||||
|
||||
if(6==6) {
|
||||
@ -144,12 +150,19 @@
|
||||
}
|
||||
|
||||
main.foo(1,2,3)
|
||||
return
|
||||
|
||||
sub start () -> () {
|
||||
word dinges = 0
|
||||
word blerp1 =99
|
||||
byte blerp2 =99
|
||||
dinges=blerp1
|
||||
A=blerp1
|
||||
A=blerp1 ; @todo error can't assign word to byte
|
||||
A=blerp2
|
||||
A=9999
|
||||
XY=blerp1
|
||||
X=blerp1 ; @todo error can't assign word to byte
|
||||
X=blerp2
|
||||
return
|
||||
}
|
||||
|
||||
@ -158,6 +171,7 @@ mega:
|
||||
X += 1
|
||||
cool:
|
||||
Y=2
|
||||
goto start
|
||||
|
||||
sub foo () -> () {
|
||||
byte blerp = 3
|
||||
@ -166,8 +180,11 @@ cool:
|
||||
return
|
||||
ultrafoo()
|
||||
X =33
|
||||
|
||||
mega:
|
||||
cool:
|
||||
return
|
||||
|
||||
sub ultrafoo() -> () {
|
||||
X= extra233.thingy()
|
||||
;return 33
|
||||
@ -180,9 +197,6 @@ cool:
|
||||
some_label_def: A=44
|
||||
;return 1+999
|
||||
return
|
||||
%breakpoint
|
||||
%asminclude "derp", hopsa
|
||||
%asmbinary "derp", 0, 200
|
||||
}
|
||||
|
||||
%import imported
|
||||
|
@ -6,6 +6,7 @@ import il65.parser.*
|
||||
import il65.compiler.*
|
||||
import il65.optimizing.optimizeExpressions
|
||||
import il65.optimizing.optimizeStatements
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
@ -14,7 +15,7 @@ fun main(args: Array<String>) {
|
||||
println("\nIL65 compiler by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
|
||||
|
||||
// import main module and process additional imports
|
||||
// import main module bitand process additional imports
|
||||
|
||||
if(args.size != 1) {
|
||||
System.err.println("module filename argument missing")
|
||||
@ -45,7 +46,7 @@ fun main(args: Array<String>) {
|
||||
)
|
||||
|
||||
|
||||
// perform syntax checks and optimizations
|
||||
// perform syntax checks bitand optimizations
|
||||
moduleAst.checkIdentifiers()
|
||||
moduleAst.optimizeExpressions(globalNameSpaceBeforeOptimization)
|
||||
moduleAst.checkValid(globalNameSpaceBeforeOptimization, compilerOptions) // check if tree is valid
|
||||
@ -58,11 +59,16 @@ fun main(args: Array<String>) {
|
||||
|
||||
// globalNamespaceAfterOptimize.debugPrint()
|
||||
|
||||
// compile the syntax tree into stackvmProg form, and optimize that
|
||||
// compile the syntax tree into stackvmProg form, bitand optimize that
|
||||
val compiler = Compiler(compilerOptions)
|
||||
val intermediate = compiler.compile(moduleAst)
|
||||
intermediate.optimize()
|
||||
intermediate.toTextLines().forEach { println(it) }
|
||||
|
||||
val stackVmFilename = intermediate.name + "_stackvm.txt"
|
||||
val stackvmFile = File(stackVmFilename).printWriter()
|
||||
intermediate.toTextLines().forEach { stackvmFile.println(it) }
|
||||
stackvmFile.close()
|
||||
println("StackVM intermediary code written to $stackVmFilename")
|
||||
|
||||
// val assembly = stackvmProg.compileToAssembly()
|
||||
//
|
||||
|
@ -197,7 +197,7 @@ interface Node {
|
||||
}
|
||||
|
||||
|
||||
// find the parent node of a specific type or interface
|
||||
// find the parent node of a specific type bitor interface
|
||||
// (useful to figure out in what namespace/block something is defined, etc)
|
||||
inline fun <reified T> findParentNode(node: Node): T? {
|
||||
var candidate = node.parent
|
||||
@ -302,7 +302,7 @@ interface INameScope {
|
||||
|
||||
/**
|
||||
* Inserted into the Ast in place of modified nodes (not inserted directly as a parser result)
|
||||
* It can hold zero or more replacement statements that have to be inserted at that point.
|
||||
* It can hold zero bitor more replacement statements that have to be inserted at that point.
|
||||
*/
|
||||
class AnonymousStatementList(override var parent: Node, var statements: List<IStatement>) : IStatement {
|
||||
override var position: Position? = null
|
||||
@ -371,7 +371,7 @@ private class GlobalNamespace(override val name: String,
|
||||
override var statements: MutableList<IStatement>,
|
||||
override val position: Position?) : INameScope {
|
||||
|
||||
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main", "main.start") // main and main.start are always used
|
||||
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main", "main.start") // main bitand main.start are always used
|
||||
|
||||
override fun usedNames(): Set<String> = scopedNamesUsed
|
||||
|
||||
@ -576,7 +576,7 @@ class VarDecl(val type: VarDeclType,
|
||||
else -> when (declaredDatatype) {
|
||||
DataType.BYTE -> DataType.ARRAY
|
||||
DataType.WORD -> DataType.ARRAY_W
|
||||
else -> throw SyntaxError("array can only contain bytes or words", position)
|
||||
else -> throw SyntaxError("array can only contain bytes bitor words", position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -816,7 +816,7 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? {
|
||||
// if the function is a built-in function and the args are consts, should try to const-evaluate!
|
||||
// if the function is a built-in function bitand the args are consts, should try to const-evaluate!
|
||||
if(target.nameInSource.size>1) return null
|
||||
try {
|
||||
return when (target.nameInSource[0]) {
|
||||
|
@ -68,6 +68,8 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
if(block.address!=null && (block.address<0 || block.address>65535)) {
|
||||
checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position))
|
||||
}
|
||||
|
||||
checkSubroutinesPrecededByReturnOrJump(block.statements)
|
||||
return super.process(block)
|
||||
}
|
||||
|
||||
@ -98,9 +100,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
err("return registers should be unique")
|
||||
|
||||
super.process(subroutine)
|
||||
checkSubroutinesPrecededByReturnOrJump(subroutine.statements)
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp')
|
||||
// subroutine must contain at least one 'return' bitor 'goto'
|
||||
// (bitor if it has an asm block, that must contain a 'rts' bitor 'jmp')
|
||||
if(subroutine.statements.count { it is Return || it is Jump } == 0) {
|
||||
if(subroutine.address==null) {
|
||||
val amount = subroutine.statements
|
||||
@ -109,15 +112,31 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
.count { it.contains(" rts") || it.contains("\trts") ||
|
||||
it.contains(" jmp") || it.contains("\tjmp")}
|
||||
if(amount==0 )
|
||||
err("subroutine must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
|
||||
err("subroutine must have at least one 'return' bitor 'goto' in it (bitor 'rts' / 'jmp' in case of %asm)")
|
||||
}
|
||||
}
|
||||
|
||||
return subroutine
|
||||
}
|
||||
|
||||
private fun checkSubroutinesPrecededByReturnOrJump(statements: MutableList<IStatement>) {
|
||||
var preceding: IStatement = BuiltinFunctionStatementPlaceholder
|
||||
for (stmt in statements) {
|
||||
if(stmt is Subroutine) {
|
||||
if(preceding !is Return
|
||||
&& preceding !is Jump
|
||||
&& preceding !is Subroutine
|
||||
&& preceding !is VarDecl
|
||||
&& preceding !is BuiltinFunctionStatementPlaceholder) {
|
||||
checkResult.add(SyntaxError("subroutine definition should be preceded by a return, jump, vardecl, bitor another subroutine statement", stmt.position))
|
||||
}
|
||||
}
|
||||
preceding = stmt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignment target must be register, or a variable name
|
||||
* Assignment target must be register, bitor a variable name
|
||||
* for constant-value assignments, check the datatype as well
|
||||
*/
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
@ -130,7 +149,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
return super.process(assignment)
|
||||
}
|
||||
targetSymbol !is VarDecl -> {
|
||||
checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position))
|
||||
checkResult.add(SyntaxError("assignment LHS must be register bitor variable", assignment.position))
|
||||
return super.process(assignment)
|
||||
}
|
||||
targetSymbol.type == VarDeclType.CONST -> {
|
||||
@ -219,12 +238,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
"%output" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 || directive.args[0].name != "raw" && directive.args[0].name != "prg")
|
||||
err("invalid output directive type, expected raw or prg")
|
||||
err("invalid output directive type, expected raw bitor prg")
|
||||
}
|
||||
"%launcher" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 || directive.args[0].name != "basic" && directive.args[0].name != "none")
|
||||
err("invalid launcher directive type, expected basic or none")
|
||||
err("invalid launcher directive type, expected basic bitor none")
|
||||
}
|
||||
"%zeropage" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
@ -232,7 +251,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
directive.args[0].name != "basicsafe" &&
|
||||
directive.args[0].name != "kernalsafe" &&
|
||||
directive.args[0].name != "full")
|
||||
err("invalid zp directive style, expected basicsafe, kernalsafe, or full")
|
||||
err("invalid zp directive style, expected basicsafe, kernalsafe, bitor full")
|
||||
}
|
||||
"%address" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
@ -297,11 +316,11 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
}
|
||||
from.strvalue!=null && to.strvalue!=null -> {
|
||||
if(from.strvalue.length!=1 || to.strvalue.length!=1)
|
||||
err("range from and to must be a single character")
|
||||
err("range from bitand to must be a single character")
|
||||
if(from.strvalue[0] > to.strvalue[0])
|
||||
err("range from is larger than to value")
|
||||
}
|
||||
else -> err("range expression must be over integers or over characters")
|
||||
else -> err("range expression must be over integers bitor over characters")
|
||||
}
|
||||
}
|
||||
return range
|
||||
@ -333,9 +352,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
|
||||
} else {
|
||||
if(target !is VarDecl || target.type==VarDeclType.CONST) {
|
||||
checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position))
|
||||
checkResult.add(SyntaxError("can only increment bitor decrement a variable", postIncrDecr.position))
|
||||
} else if(target.datatype!=DataType.FLOAT && target.datatype!=DataType.WORD && target.datatype!=DataType.BYTE) {
|
||||
checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position))
|
||||
checkResult.add(SyntaxError("can only increment bitor decrement a byte/float/word variable", postIncrDecr.position))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,7 +365,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
val targetStatement = target.targetStatement(namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
checkResult.add(SyntaxError("undefined function bitor subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
return null
|
||||
}
|
||||
|
||||
@ -354,13 +373,13 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
if(call.target.nameInSource.size==1 && BuiltinFunctionNames.contains(call.target.nameInSource[0])) {
|
||||
val functionName = call.target.nameInSource[0]
|
||||
if(functionName=="P_carry" || functionName=="P_irqd") {
|
||||
// these functions allow only 0 or 1 as argument
|
||||
// these functions allow only 0 bitor 1 as argument
|
||||
if(call.arglist.size!=1 || call.arglist[0] !is LiteralValue) {
|
||||
checkResult.add(SyntaxError("$functionName requires one argument, 0 or 1", position))
|
||||
checkResult.add(SyntaxError("$functionName requires one argument, 0 bitor 1", position))
|
||||
} else {
|
||||
val value = call.arglist[0] as LiteralValue
|
||||
if(value.intvalue==null || value.intvalue < 0 || value.intvalue > 1) {
|
||||
checkResult.add(SyntaxError("$functionName requires one argument, 0 or 1", position))
|
||||
checkResult.add(SyntaxError("$functionName requires one argument, 0 bitor 1", position))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -398,7 +417,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
return err("string length must be 1..65535")
|
||||
}
|
||||
DataType.ARRAY -> {
|
||||
// value may be either a single byte, or a byte array
|
||||
// value may be either a single byte, bitor a byte array
|
||||
if(value.isArray) {
|
||||
for (av in value.arrayvalue!!) {
|
||||
val number = (av as LiteralValue).intvalue
|
||||
@ -419,7 +438,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
// value may be either a single word, or a word array
|
||||
// value may be either a single word, bitor a word array
|
||||
if(value.isArray) {
|
||||
for (av in value.arrayvalue!!) {
|
||||
val number = (av as LiteralValue).intvalue
|
||||
@ -440,7 +459,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
}
|
||||
}
|
||||
DataType.MATRIX -> {
|
||||
// value can only be a single byte, or a byte array (which represents the matrix)
|
||||
// value can only be a single byte, bitor a byte array (which represents the matrix)
|
||||
if(value.isArray) {
|
||||
for (av in value.arrayvalue!!) {
|
||||
val number = (av as LiteralValue).intvalue
|
||||
|
@ -66,7 +66,7 @@ class DirectedGraph<VT> {
|
||||
if(recStack[node]==true) return true
|
||||
if(visited[node]==true) return false
|
||||
|
||||
// mark current node as visited and add to recursion stack
|
||||
// mark current node as visited bitand add to recursion stack
|
||||
visited[node] = true
|
||||
recStack[node] = true
|
||||
|
||||
|
@ -4,7 +4,7 @@ class StatementReorderer: IAstProcessor {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement.
|
||||
// - in every scope:
|
||||
// -- the directives '%output', '%launcher', '%zeropage', '%address' and '%option' will come first.
|
||||
// -- the directives '%output', '%launcher', '%zeropage', '%address' bitand '%option' will come first.
|
||||
// -- all vardecls then follow.
|
||||
// -- the remaining statements then follow in their original order.
|
||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
||||
|
@ -34,8 +34,8 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va
|
||||
val zero = Mflpt5(0, 0,0,0,0)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
// bitand https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// bitand https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
|
||||
val flt = num.toDouble()
|
||||
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
@ -47,12 +47,12 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
// if mantissa is too large, shift right bitand adjust exponent
|
||||
while(mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent ++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
// if mantissa is too small, shift left bitand adjust exponent
|
||||
while(mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent --
|
||||
@ -95,10 +95,10 @@ class Compiler(private val options: CompilationOptions) {
|
||||
val intermediate = StackVmProgram(module.name)
|
||||
namespace.debugPrint()
|
||||
|
||||
// create the pool of all variables used in all blocks and scopes
|
||||
// create the pool of all variables used in all blocks bitand scopes
|
||||
val varGather = VarGatherer(intermediate)
|
||||
varGather.process(module)
|
||||
println("Number of allocated variables and constants: ${intermediate.variables.size} (${intermediate.variablesMemSize} bytes)")
|
||||
println("Number of allocated variables bitand constants: ${intermediate.variables.size} (${intermediate.variablesMemSize} bytes)")
|
||||
|
||||
val translator = StatementTranslator(intermediate, namespace)
|
||||
translator.process(module)
|
||||
@ -112,8 +112,12 @@ class Compiler(private val options: CompilationOptions) {
|
||||
class VarGatherer(val stackvmProg: StackVmProgram): IAstProcessor {
|
||||
// collect all the VarDecls to make them into one global list
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
assert(decl.type==VarDeclType.VAR) {"only VAR decls should remain: CONST and MEMORY should have been processed away"}
|
||||
stackvmProg.blockvar(decl.scopedname, decl)
|
||||
if(decl.type == VarDeclType.MEMORY)
|
||||
TODO("stackVm doesn't support memory vars for now")
|
||||
|
||||
if (decl.type == VarDeclType.VAR) {
|
||||
stackvmProg.blockvar(decl.scopedname, decl)
|
||||
}
|
||||
return super.process(decl)
|
||||
}
|
||||
}
|
||||
@ -123,31 +127,41 @@ class Compiler(private val options: CompilationOptions) {
|
||||
private set
|
||||
|
||||
override fun process(subroutine: Subroutine): IStatement {
|
||||
stackvmProg.label(subroutine.scopedname)
|
||||
translate(subroutine.statements)
|
||||
return super.process(subroutine)
|
||||
}
|
||||
|
||||
override fun process(block: Block): IStatement {
|
||||
stackvmProg.label(block.scopedname)
|
||||
translate(block.statements)
|
||||
return super.process(block)
|
||||
}
|
||||
|
||||
override fun process(directive: Directive): IStatement {
|
||||
when(directive.directive) {
|
||||
"%asminclude" -> throw CompilerException("can't use %asminclude in stackvm")
|
||||
"%asmbinary" -> throw CompilerException("can't use %asmbinary in stackvm")
|
||||
"%breakpoint" -> stackvmProg.instruction("break")
|
||||
}
|
||||
return super.process(directive)
|
||||
}
|
||||
|
||||
private fun translate(statements: List<IStatement>) {
|
||||
for (stmt: IStatement in statements) {
|
||||
stmtUniqueSequenceNr++
|
||||
when (stmt) {
|
||||
is AnonymousStatementList -> translate(stmt.statements)
|
||||
is BuiltinFunctionStatementPlaceholder -> translate(stmt)
|
||||
is Label -> translate(stmt)
|
||||
is Return -> translate(stmt)
|
||||
is Assignment -> translate(stmt)
|
||||
is PostIncrDecr -> translate(stmt)
|
||||
is Jump -> translate(stmt)
|
||||
is FunctionCallStatement -> translate(stmt)
|
||||
is InlineAssembly -> translate(stmt)
|
||||
is IfStatement -> translate(stmt)
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -156,7 +170,7 @@ class Compiler(private val options: CompilationOptions) {
|
||||
private fun translate(branch: BranchStatement) {
|
||||
/*
|
||||
* A branch: IF_CC { stuff } else { other_stuff }
|
||||
* Which is desugared into:
|
||||
* Which is translated into:
|
||||
* BCS _stmt_999_else
|
||||
* stuff
|
||||
* JUMP _stmt_999_continue
|
||||
@ -194,20 +208,116 @@ class Compiler(private val options: CompilationOptions) {
|
||||
private fun makeLabel(postfix: String): String = "_il65stmt_${stmtUniqueSequenceNr}_$postfix"
|
||||
|
||||
private fun translate(stmt: IfStatement) {
|
||||
println("@todo translate: #$stmtUniqueSequenceNr : $stmt")
|
||||
stackvmProg.instruction("nop") // todo translate if statement
|
||||
/*
|
||||
* An IF statement: IF (condition-expression) { stuff } else { other_stuff }
|
||||
* Which is translated into:
|
||||
* <condition-expression evaluation>
|
||||
* BEQ _stmt_999_else
|
||||
* stuff
|
||||
* JUMP _stmt_999_continue
|
||||
* _stmt_999_else:
|
||||
* other_stuff ;; optional
|
||||
* _stmt_999_continue:
|
||||
* ...
|
||||
*/
|
||||
translate(stmt.condition)
|
||||
val labelElse = makeLabel("else")
|
||||
val labelContinue = makeLabel("continue")
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
stackvmProg.instruction("beq $labelContinue")
|
||||
translate(stmt.statements)
|
||||
stackvmProg.label(labelContinue)
|
||||
} else {
|
||||
stackvmProg.instruction("beq $labelElse")
|
||||
translate(stmt.statements)
|
||||
stackvmProg.instruction("jump $labelContinue")
|
||||
stackvmProg.label(labelElse)
|
||||
translate(stmt.elsepart)
|
||||
stackvmProg.label(labelContinue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: InlineAssembly) {
|
||||
println("@todo translate: #$stmtUniqueSequenceNr : $stmt")
|
||||
TODO("inline assembly not supported yet by stackvm")
|
||||
private fun translate(expr: IExpression) {
|
||||
when(expr) {
|
||||
is RegisterExpr -> {
|
||||
stackvmProg.instruction("push_var ${expr.register}")
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
translate(expr.left)
|
||||
translate(expr.right)
|
||||
translateBinaryOperator(expr.operator)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
expr.arglist.forEach { translate(it) }
|
||||
val target = expr.target.targetStatement(namespace)
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
// call to a builtin function
|
||||
stackvmProg.instruction("syscall FUNC_${expr.target.nameInSource[0].toUpperCase()}") // call builtin function
|
||||
} else {
|
||||
when(target) {
|
||||
is Subroutine -> stackvmProg.instruction("call ${target.scopedname}")
|
||||
else -> TODO("non-builtin function call to $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val target = expr.targetStatement(namespace)
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instruction("push_var ${target.scopedname}")
|
||||
else -> throw CompilerException("expression identifierref should be a vardef, not $target")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr")
|
||||
when {
|
||||
lv.isString -> stackvmProg.instruction("push \"${lv.strvalue}\"")
|
||||
lv.isInteger -> {
|
||||
val intval = lv.intvalue!!
|
||||
if(intval<=255)
|
||||
stackvmProg.instruction("push b:${intval.toString(16)}")
|
||||
else if(intval <= 65535)
|
||||
stackvmProg.instruction("push w:${intval.toString(16)}")
|
||||
}
|
||||
lv.isNumeric -> stackvmProg.instruction("push f:${lv.asNumericValue}")
|
||||
lv.isArray -> {
|
||||
lv.arrayvalue?.forEach { translate(it) }
|
||||
stackvmProg.instruction("array w:${lv.arrayvalue!!.size.toString(16)}")
|
||||
}
|
||||
else -> throw CompilerException("expression constvalue invalid type $lv")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperator(operator: String) {
|
||||
val instruction = when(operator) {
|
||||
"+" -> "add"
|
||||
"-" -> "sub"
|
||||
"*" -> "mul"
|
||||
"/" -> "div"
|
||||
"**" -> "pow"
|
||||
"&" -> "bitand"
|
||||
"|" -> "bitor"
|
||||
"^" -> "bitxor"
|
||||
"bitand" -> "bitand"
|
||||
"bitor" -> "bitor"
|
||||
"bitxor" -> "bitxor"
|
||||
"<" -> "less"
|
||||
">" -> "greater"
|
||||
"<=" -> "lesseq"
|
||||
">=" -> "greatereq"
|
||||
"==" -> "equal"
|
||||
"!=" -> "notequal"
|
||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||
}
|
||||
stackvmProg.instruction(instruction)
|
||||
}
|
||||
|
||||
private fun translate(stmt: FunctionCallStatement) {
|
||||
val targetStmt = stmt.target.targetStatement(namespace)!!
|
||||
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
|
||||
// call to a builtin function
|
||||
TODO("BUILTIN_${stmt.target.nameInSource[0]}") // TODO
|
||||
stmt.arglist.forEach { translate(it) }
|
||||
stackvmProg.instruction("syscall FUNC_${stmt.target.nameInSource[0].toUpperCase()}") // call builtin function
|
||||
return
|
||||
}
|
||||
|
||||
@ -216,23 +326,10 @@ class Compiler(private val options: CompilationOptions) {
|
||||
is Subroutine -> targetStmt.scopedname
|
||||
else -> throw AstException("invalid call target node type: ${targetStmt::class}")
|
||||
}
|
||||
|
||||
for (arg in stmt.arglist) {
|
||||
val lv = arg.constValue(namespace)
|
||||
if(lv==null) TODO("argument must be constant for now") // TODO non-const args
|
||||
stackvmProg.instruction("push ${makeValue(lv)}")
|
||||
}
|
||||
stmt.arglist.forEach { translate(it) }
|
||||
stackvmProg.instruction("call $targetname")
|
||||
}
|
||||
|
||||
private fun makeValue(value: LiteralValue): String {
|
||||
return when {
|
||||
value.isString -> "\"${value.strvalue}\""
|
||||
value.isNumeric -> value.asNumericValue.toString()
|
||||
else -> TODO("stackvm value for $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: Jump) {
|
||||
val instr =
|
||||
if(stmt.address!=null) {
|
||||
@ -250,19 +347,60 @@ class Compiler(private val options: CompilationOptions) {
|
||||
|
||||
private fun translate(stmt: PostIncrDecr) {
|
||||
if(stmt.target.register!=null) {
|
||||
TODO("register++/-- not yet implemented")
|
||||
when(stmt.operator) {
|
||||
"++" -> stackvmProg.instruction("inc_var ${stmt.target.register}")
|
||||
"--" -> stackvmProg.instruction("dec_var ${stmt.target.register}")
|
||||
}
|
||||
} else {
|
||||
val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl
|
||||
when(stmt.operator) {
|
||||
"++" -> stackvmProg.instruction("inc_var ${targetStatement.scopedname}")
|
||||
"--" -> stackvmProg.instruction("decr_var ${targetStatement.scopedname}")
|
||||
"--" -> stackvmProg.instruction("dec_var ${targetStatement.scopedname}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: Assignment) {
|
||||
println("@todo translate: #$stmtUniqueSequenceNr : $stmt")
|
||||
stackvmProg.instruction("nop") // todo translate assignment
|
||||
translate(stmt.value)
|
||||
if(stmt.aug_op!=null) {
|
||||
// augmented assignment
|
||||
if(stmt.target.identifier!=null) {
|
||||
val target = stmt.target.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instruction("push_var ${target.scopedname}")
|
||||
else -> throw CompilerException("invalid assignment target type ${target::class}")
|
||||
}
|
||||
} else if(stmt.target.register!=null) {
|
||||
stackvmProg.instruction("push_var ${stmt.target.register}")
|
||||
}
|
||||
translateAugAssignOperator(stmt.aug_op)
|
||||
}
|
||||
|
||||
// pop the result value back into the assignment target
|
||||
if(stmt.target.identifier!=null) {
|
||||
val target = stmt.target.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instruction("pop_var ${target.scopedname}")
|
||||
else -> throw CompilerException("invalid assignment target type ${target::class}")
|
||||
}
|
||||
} else if(stmt.target.register!=null) {
|
||||
stackvmProg.instruction("pop_var ${stmt.target.register}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateAugAssignOperator(aug_op: String) {
|
||||
val instruction = when(aug_op) {
|
||||
"+=" -> "add"
|
||||
"-=" -> "sub"
|
||||
"/=" -> "div"
|
||||
"*=" -> "mul"
|
||||
"**=" -> "pow"
|
||||
"&=" -> "bitand"
|
||||
"|=" -> "bitor"
|
||||
"^=" -> "bitxor"
|
||||
else -> throw CompilerException("invalid aug assignment operator $aug_op")
|
||||
}
|
||||
stackvmProg.instruction(instruction)
|
||||
}
|
||||
|
||||
private fun translate(stmt: Return) {
|
||||
@ -275,11 +413,6 @@ class Compiler(private val options: CompilationOptions) {
|
||||
private fun translate(stmt: Label) {
|
||||
stackvmProg.label(stmt.scopedname)
|
||||
}
|
||||
|
||||
private fun translate(stmt: BuiltinFunctionStatementPlaceholder) {
|
||||
println("@todo translate: #$stmtUniqueSequenceNr : $stmt")
|
||||
stackvmProg.instruction("nop") // todo translate builtinfunction placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,12 +449,15 @@ class StackVmProgram(val name: String) {
|
||||
result.add("%end_memory")
|
||||
result.add("%variables")
|
||||
for(v in variables) {
|
||||
assert(v.value.type==VarDeclType.VAR || v.value.type==VarDeclType.CONST)
|
||||
if (!(v.value.type == VarDeclType.VAR || v.value.type == VarDeclType.CONST)) {
|
||||
throw AssertionError("Should be only VAR bitor CONST variables")
|
||||
}
|
||||
val litval = v.value.value as LiteralValue
|
||||
val litvalStr = when {
|
||||
litval.isNumeric -> litval.asNumericValue.toString()
|
||||
litval.isInteger -> litval.intvalue!!.toString(16)
|
||||
litval.isFloat -> litval.floatvalue.toString()
|
||||
litval.isString -> "\"${litval.strvalue}\""
|
||||
else -> TODO()
|
||||
else -> TODO("non-scalar value")
|
||||
}
|
||||
val line = "${v.key} ${v.value.datatype.toString().toLowerCase()} $litvalStr"
|
||||
result.add(line)
|
||||
|
@ -132,7 +132,7 @@ fun builtinDeg(args: List<IExpression>, position: Position?, namespace:INameScop
|
||||
= oneDoubleArg(args, position, namespace, Math::toDegrees)
|
||||
|
||||
fun builtinAbs(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
|
||||
// 1 arg, type = float or int, result type= same as argument type
|
||||
// 1 arg, type = float bitor int, result type= same as argument type
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("abs requires one numeric argument", position)
|
||||
|
||||
|
@ -47,7 +47,7 @@ fun Module.optimizeExpressions(globalNamespace: INameScope) {
|
||||
X ^ 0 -> X
|
||||
|
||||
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 bitor false (1 bitor 0)
|
||||
todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2)
|
||||
todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call)
|
||||
|
||||
@ -148,7 +148,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
|
||||
optimizationsDone++
|
||||
LiteralValue(floatvalue = -subexpr.floatvalue)
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
else -> throw ExpressionError("can only take negative of int bitor float", subexpr.position)
|
||||
}
|
||||
expr.operator == "~" -> when {
|
||||
subexpr.intvalue != null -> {
|
||||
@ -268,9 +268,9 @@ class ConstExprEvaluator {
|
||||
"&" -> bitwiseand(left, right)
|
||||
"|" -> bitwiseor(left, right)
|
||||
"^" -> bitwisexor(left, right)
|
||||
"and" -> logicaland(left, right)
|
||||
"or" -> logicalor(left, right)
|
||||
"xor" -> logicalxor(left, right)
|
||||
"bitand" -> logicaland(left, right)
|
||||
"bitor" -> logicalor(left, right)
|
||||
"bitxor" -> logicalxor(left, right)
|
||||
"<" -> compareless(left, right)
|
||||
">" -> comparegreater(left, right)
|
||||
"<=" -> comparelessequal(left, right)
|
||||
@ -369,7 +369,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun logicalxor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot compute $left locical-xor $right"
|
||||
val error = "cannot compute $left locical-bitxor $right"
|
||||
val litval = when {
|
||||
left.intvalue!=null -> when {
|
||||
right.intvalue!=null -> LiteralValue(
|
||||
@ -392,7 +392,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun logicalor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot compute $left locical-or $right"
|
||||
val error = "cannot compute $left locical-bitor $right"
|
||||
val litval = when {
|
||||
left.intvalue!=null -> when {
|
||||
right.intvalue!=null -> LiteralValue(
|
||||
@ -415,7 +415,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun logicaland(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot compute $left locical-and $right"
|
||||
val error = "cannot compute $left locical-bitand $right"
|
||||
val litval = when {
|
||||
left.intvalue!=null -> when {
|
||||
right.intvalue!=null -> LiteralValue(
|
||||
@ -484,7 +484,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot add $left and $right"
|
||||
val error = "cannot add $left bitand $right"
|
||||
val litval = when {
|
||||
left.intvalue!=null -> when {
|
||||
right.intvalue!=null -> LiteralValue(intvalue = left.intvalue + right.intvalue)
|
||||
@ -503,7 +503,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun minus(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot subtract $left and $right"
|
||||
val error = "cannot subtract $left bitand $right"
|
||||
val litval = when {
|
||||
left.intvalue!=null -> when {
|
||||
right.intvalue!=null -> LiteralValue(intvalue = left.intvalue - right.intvalue)
|
||||
@ -522,7 +522,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot multiply $left and $right"
|
||||
val error = "cannot multiply $left bitand $right"
|
||||
val litval = when {
|
||||
left.intvalue!=null -> when {
|
||||
right.intvalue!=null -> LiteralValue(intvalue = left.intvalue * right.intvalue)
|
||||
|
@ -24,8 +24,8 @@ fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefini
|
||||
todo remove statements that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
|
||||
todo optimize addition with self into shift 1 (A+=A -> A<<=1)
|
||||
todo assignment optimization: optimize some simple multiplications into shifts (A*=8 -> A<<=3)
|
||||
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
|
||||
todo merge sequence of assignments into one (as long as the value is a constant and the target not a MEMORY type!)
|
||||
todo analyse for unreachable code bitand remove that (f.i. code after goto bitor return that has no label so can never be jumped to)
|
||||
todo merge sequence of assignments into one (as long as the value is a constant bitand the target not a MEMORY type!)
|
||||
todo report more always true/always false conditions
|
||||
*/
|
||||
|
||||
|
@ -21,13 +21,14 @@ enum class Opcode {
|
||||
PUSH_MEM_F, // push float value from memory to stack
|
||||
PUSH_VAR, // push a variable
|
||||
DUP, // push topmost value once again
|
||||
ARRAY, // create arrayvalue of number of integer values on the stack, given in argument
|
||||
|
||||
// popping values off the (evaluation) stack, possibly storing them in another location
|
||||
DISCARD, // discard top value
|
||||
POP_MEM, // pop value into destination memory address
|
||||
POP_VAR, // pop value into variable
|
||||
|
||||
// numeric and bitwise arithmetic
|
||||
// numeric bitand bitwise arithmetic
|
||||
ADD,
|
||||
SUB,
|
||||
MUL,
|
||||
@ -58,14 +59,17 @@ enum class Opcode {
|
||||
ROR2_MEM,
|
||||
ROR2_MEM_W,
|
||||
ROR2_VAR,
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
BITAND,
|
||||
BITOR,
|
||||
BITXOR,
|
||||
INV,
|
||||
LSB,
|
||||
MSB,
|
||||
|
||||
// logical operations (?)
|
||||
// logical operations
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
|
||||
// increment, decrement
|
||||
INC,
|
||||
@ -77,7 +81,13 @@ enum class Opcode {
|
||||
DEC_MEM_W,
|
||||
DEC_VAR,
|
||||
|
||||
// comparisons (?)
|
||||
// comparisons
|
||||
LESS,
|
||||
GREATER,
|
||||
LESSEQ,
|
||||
GREATEREQ,
|
||||
EQUAL,
|
||||
NOTEQUAL,
|
||||
|
||||
// branching
|
||||
JUMP,
|
||||
@ -100,25 +110,61 @@ enum class Opcode {
|
||||
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
||||
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
||||
NOP,
|
||||
TERMINATE
|
||||
BREAK, // breakpoint
|
||||
TERMINATE // end the program
|
||||
}
|
||||
|
||||
enum class Syscall(val callNr: Short) {
|
||||
WRITE_MEMCHR(10), // print a single char from the memory
|
||||
WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory
|
||||
WRITE_NUM(12), // pop from the evaluation stack and print it as a number
|
||||
WRITE_CHAR(13), // pop from the evaluation stack and print it as a single petscii character
|
||||
WRITE_VAR(14), // print the number or string from the given variable
|
||||
WRITE_NUM(12), // pop from the evaluation stack bitand print it as a number
|
||||
WRITE_CHAR(13), // pop from the evaluation stack bitand print it as a single petscii character
|
||||
WRITE_VAR(14), // print the number bitor string from the given variable
|
||||
INPUT_VAR(15), // user input a string into a variable
|
||||
GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order
|
||||
GFX_CLEARSCR(17), // clear the screen with color pushed on stack
|
||||
GFX_TEXT(18), // write text on screen at (x,y,text,color) pushed on stack in that order
|
||||
RANDOM(19), // push a random byte on the stack
|
||||
RANDOM_W(20) // push a random word on the stack
|
||||
RANDOM_W(20), // push a random word on the stack
|
||||
|
||||
|
||||
FUNC_P_CARRY(100),
|
||||
FUNC_P_IRQD(101),
|
||||
FUNC_ROL(102),
|
||||
FUNC_ROR(103),
|
||||
FUNC_ROL2(104),
|
||||
FUNC_ROR2(105),
|
||||
FUNC_LSL(106),
|
||||
FUNC_LSR(107),
|
||||
FUNC_SIN(108),
|
||||
FUNC_COS(109),
|
||||
FUNC_ABS(110),
|
||||
FUNC_ACOS(111),
|
||||
FUNC_ASIN(112),
|
||||
FUNC_TAN(113),
|
||||
FUNC_ATAN(114),
|
||||
FUNC_LOG(115),
|
||||
FUNC_LOG10(116),
|
||||
FUNC_SQRT(117),
|
||||
FUNC_RAD(118),
|
||||
FUNC_DEG(119),
|
||||
FUNC_ROUND(120),
|
||||
FUNC_FLOOR(121),
|
||||
FUNC_CEIL(122),
|
||||
FUNC_MAX(123),
|
||||
FUNC_MIN(124),
|
||||
FUNC_AVG(125),
|
||||
FUNC_SUM(126),
|
||||
FUNC_LEN(127),
|
||||
FUNC_ANY(128),
|
||||
FUNC_ALL(129),
|
||||
FUNC_LSB(130),
|
||||
FUNC_MSB(131)
|
||||
|
||||
}
|
||||
|
||||
class Memory {
|
||||
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
|
||||
private val mem = ShortArray(65536) // shorts because byte is signed bitand we store values 0..255
|
||||
|
||||
fun getByte(address: Int): Short {
|
||||
return mem[address]
|
||||
@ -174,17 +220,33 @@ class Memory {
|
||||
}
|
||||
|
||||
|
||||
data class Value(val type: DataType, private val numericvalue: Number?, val stringvalue: String?=null) {
|
||||
class Value(val type: DataType, private val numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) {
|
||||
private var byteval: Short? = null
|
||||
private var wordval: Int? = null
|
||||
private var floatval: Double? = null
|
||||
val asBooleanValue: Boolean
|
||||
|
||||
init {
|
||||
when(type) {
|
||||
DataType.BYTE -> byteval = (numericvalue!!.toInt() and 255).toShort()
|
||||
DataType.WORD -> wordval = numericvalue!!.toInt() and 65535
|
||||
DataType.FLOAT -> floatval = numericvalue!!.toDouble()
|
||||
DataType.STR -> if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type")
|
||||
DataType.BYTE -> {
|
||||
byteval = (numericvalue!!.toInt() and 255).toShort()
|
||||
asBooleanValue = byteval != (0.toShort())
|
||||
}
|
||||
DataType.WORD -> {
|
||||
wordval = numericvalue!!.toInt() and 65535
|
||||
asBooleanValue = wordval != 0
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
floatval = numericvalue!!.toDouble()
|
||||
asBooleanValue = floatval != 0.0
|
||||
}
|
||||
DataType.ARRAY -> {
|
||||
asBooleanValue = arrayvalue!!.isNotEmpty()
|
||||
}
|
||||
DataType.STR -> {
|
||||
if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type")
|
||||
asBooleanValue = stringvalue.isNotEmpty()
|
||||
}
|
||||
else -> throw VmExecutionException("invalid datatype $type")
|
||||
}
|
||||
}
|
||||
@ -257,7 +319,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
|
||||
}
|
||||
|
||||
fun rol(carry: Boolean): Pair<Value, Boolean> {
|
||||
// 9 or 17 bit rotate left (with carry))
|
||||
// 9 bitor 17 bit rotate left (with carry))
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
@ -276,7 +338,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
|
||||
}
|
||||
|
||||
fun ror(carry: Boolean): Pair<Value, Boolean> {
|
||||
// 9 or 17 bit rotate right (with carry)
|
||||
// 9 bitor 17 bit rotate right (with carry)
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
@ -295,7 +357,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
|
||||
}
|
||||
|
||||
fun rol2(): Value {
|
||||
// 8 or 16 bit rotate left
|
||||
// 8 bitor 16 bit rotate left
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
@ -314,7 +376,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
|
||||
}
|
||||
|
||||
fun ror2(): Value {
|
||||
// 8 or 16 bit rotate right
|
||||
// 8 bitor 16 bit rotate right
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
@ -341,27 +403,31 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
|
||||
}
|
||||
}
|
||||
|
||||
fun and(other: Value): Value {
|
||||
fun bitand(other: Value): Value {
|
||||
val v1 = integerValue()
|
||||
val v2 = other.integerValue()
|
||||
val result = v1.and(v2)
|
||||
return Value(type, result)
|
||||
}
|
||||
|
||||
fun or(other: Value): Value {
|
||||
fun bitor(other: Value): Value {
|
||||
val v1 = integerValue()
|
||||
val v2 = other.integerValue()
|
||||
val result = v1.or(v2)
|
||||
return Value(type, result)
|
||||
}
|
||||
|
||||
fun xor(other: Value): Value {
|
||||
fun bitxor(other: Value): Value {
|
||||
val v1 = integerValue()
|
||||
val v2 = other.integerValue()
|
||||
val result = v1.xor(v2)
|
||||
return Value(type, result)
|
||||
}
|
||||
|
||||
fun and(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue && other.asBooleanValue) 1 else 0)
|
||||
fun or(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue || other.asBooleanValue) 1 else 0)
|
||||
fun xor(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue xor other.asBooleanValue) 1 else 0)
|
||||
|
||||
fun inv(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv())
|
||||
@ -403,6 +469,13 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
|
||||
else -> throw VmExecutionException("not can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun compareLess(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() < other.numericValue().toDouble()) 1 else 0)
|
||||
fun compareGreater(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() > other.numericValue().toDouble()) 1 else 0)
|
||||
fun compareLessEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() <= other.numericValue().toDouble()) 1 else 0)
|
||||
fun compareGreaterEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() >= other.numericValue().toDouble()) 1 else 0)
|
||||
fun compareEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() == other.numericValue()) 1 else 0)
|
||||
fun compareNotEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() != other.numericValue()) 1 else 0)
|
||||
}
|
||||
|
||||
|
||||
@ -434,6 +507,8 @@ private class VmExecutionException(msg: String?) : Exception(msg)
|
||||
|
||||
private class VmTerminationException(msg: String?) : Exception(msg)
|
||||
|
||||
private class VmBreakpointException : Exception("breakpoint")
|
||||
|
||||
private class MyStack<T> : Stack<T>() {
|
||||
fun peek(amount: Int) : List<T> {
|
||||
return this.toList().subList(max(0, size-amount), size)
|
||||
@ -492,8 +567,13 @@ class Program (prog: MutableList<Instruction>,
|
||||
val opcode=Opcode.valueOf(parts[0].toUpperCase())
|
||||
val args = if(parts.size==2) parts[1] else null
|
||||
val instruction = when(opcode) {
|
||||
Opcode.JUMP -> {
|
||||
Instruction(opcode, callLabel = args)
|
||||
Opcode.JUMP, Opcode.CALL, Opcode.BMI, Opcode.BPL,
|
||||
Opcode.BEQ, Opcode.BNE, Opcode.BCS, Opcode.BCC -> {
|
||||
if(args!!.startsWith('$')) {
|
||||
Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16)))
|
||||
} else {
|
||||
Instruction(opcode, callLabel = args)
|
||||
}
|
||||
}
|
||||
Opcode.INC_VAR, Opcode.DEC_VAR,
|
||||
Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR,
|
||||
@ -510,7 +590,10 @@ class Program (prog: MutableList<Instruction>,
|
||||
val callValues = if(callValue==null) emptyList() else listOf(callValue)
|
||||
Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues)
|
||||
}
|
||||
else -> Instruction(opcode, getArgValue(args))
|
||||
else -> {
|
||||
println("INSTR $opcode at $lineNr args=$args") // TODO weg
|
||||
Instruction(opcode, getArgValue(args))
|
||||
}
|
||||
}
|
||||
instructions.add(instruction)
|
||||
if(nextInstructionLabelname.isNotEmpty()) {
|
||||
@ -633,18 +716,33 @@ class Program (prog: MutableList<Instruction>,
|
||||
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
|
||||
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
|
||||
Opcode.JUMP -> {
|
||||
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = target
|
||||
if(instr.callLabel==null) {
|
||||
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
|
||||
} else {
|
||||
// jump to label
|
||||
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = target
|
||||
}
|
||||
}
|
||||
Opcode.BCC, Opcode.BCS, Opcode.BEQ, Opcode.BNE, Opcode.BMI, Opcode.BPL -> {
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr
|
||||
if(instr.callLabel==null) {
|
||||
throw VmExecutionException("stackVm doesn't support branch to memory address")
|
||||
} else {
|
||||
// branch to label
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr
|
||||
}
|
||||
}
|
||||
Opcode.CALL -> {
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr // instruction to return to
|
||||
if(instr.callLabel==null) {
|
||||
throw VmExecutionException("stackVm doesn't support CALL to memory address")
|
||||
} else {
|
||||
// call label
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr // instruction to return to
|
||||
}
|
||||
}
|
||||
else -> instr.next = nextInstr
|
||||
}
|
||||
@ -669,6 +767,21 @@ class StackVm(val traceOutputFile: String?) {
|
||||
this.program = program.program
|
||||
this.canvas = canvas
|
||||
this.variables = program.variables.toMutableMap()
|
||||
if(this.variables.contains("A") ||
|
||||
this.variables.contains("X") ||
|
||||
this.variables.contains("Y") ||
|
||||
this.variables.contains("XY") ||
|
||||
this.variables.contains("AX") ||
|
||||
this.variables.contains("AY"))
|
||||
throw VmExecutionException("program contains variable(s) for the reserved registers A,X,...")
|
||||
// define the 'registers'
|
||||
this.variables["A"] = Value(DataType.BYTE, 0)
|
||||
this.variables["X"] = Value(DataType.BYTE, 0)
|
||||
this.variables["Y"] = Value(DataType.BYTE, 0)
|
||||
this.variables["AX"] = Value(DataType.WORD, 0)
|
||||
this.variables["AY"] = Value(DataType.WORD, 0)
|
||||
this.variables["XY"] = Value(DataType.WORD, 0)
|
||||
|
||||
initMemory(program.memory)
|
||||
currentIns = this.program[0]
|
||||
}
|
||||
@ -679,12 +792,17 @@ class StackVm(val traceOutputFile: String?) {
|
||||
val instructionsPerStep = 5000
|
||||
val start = System.currentTimeMillis()
|
||||
for(i:Int in 0..instructionsPerStep) {
|
||||
currentIns = dispatch(currentIns)
|
||||
try {
|
||||
currentIns = dispatch(currentIns)
|
||||
|
||||
if (evalstack.size > 128)
|
||||
throw VmExecutionException("too many values on evaluation stack")
|
||||
if (callstack.size > 128)
|
||||
throw VmExecutionException("too many nested/recursive calls")
|
||||
if (evalstack.size > 128)
|
||||
throw VmExecutionException("too many values on evaluation stack")
|
||||
if (callstack.size > 128)
|
||||
throw VmExecutionException("too many nested/recursive calls")
|
||||
} catch (bp: VmBreakpointException) {
|
||||
currentIns = currentIns.next
|
||||
throw bp
|
||||
}
|
||||
}
|
||||
val time = System.currentTimeMillis()-start
|
||||
if(time > 100) {
|
||||
@ -737,6 +855,17 @@ class StackVm(val traceOutputFile: String?) {
|
||||
evalstack.push(Value(DataType.FLOAT, mem.getFloat(address)))
|
||||
}
|
||||
Opcode.DUP -> evalstack.push(evalstack.peek())
|
||||
Opcode.ARRAY -> {
|
||||
val amount = ins.arg!!.integerValue()
|
||||
val array = mutableListOf<Int>()
|
||||
for (i in 0..amount) {
|
||||
val value = evalstack.pop()
|
||||
if(value.type!=DataType.BYTE && value.type!=DataType.WORD)
|
||||
throw VmExecutionException("array requires values to be all byte/word")
|
||||
array.add(value.integerValue())
|
||||
}
|
||||
evalstack.push(Value(DataType.ARRAY, null, arrayvalue = array.toIntArray()))
|
||||
}
|
||||
Opcode.DISCARD -> evalstack.pop()
|
||||
Opcode.SWAP -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
@ -805,17 +934,17 @@ class StackVm(val traceOutputFile: String?) {
|
||||
val v = evalstack.pop()
|
||||
evalstack.push(v.ror2())
|
||||
}
|
||||
Opcode.AND -> {
|
||||
Opcode.BITAND -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.and(top))
|
||||
evalstack.push(second.bitand(top))
|
||||
}
|
||||
Opcode.OR -> {
|
||||
Opcode.BITOR -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.or(top))
|
||||
evalstack.push(second.bitor(top))
|
||||
}
|
||||
Opcode.XOR -> {
|
||||
Opcode.BITXOR -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.xor(top))
|
||||
evalstack.push(second.bitxor(top))
|
||||
}
|
||||
Opcode.INV -> {
|
||||
val v = evalstack.pop()
|
||||
@ -884,6 +1013,7 @@ class StackVm(val traceOutputFile: String?) {
|
||||
Opcode.SEC -> carry = true
|
||||
Opcode.CLC -> carry = false
|
||||
Opcode.TERMINATE -> throw VmTerminationException("execution terminated")
|
||||
Opcode.BREAK -> throw VmBreakpointException()
|
||||
|
||||
Opcode.INC_MEM -> {
|
||||
val addr = ins.arg!!.integerValue()
|
||||
@ -985,10 +1115,10 @@ class StackVm(val traceOutputFile: String?) {
|
||||
Opcode.JUMP -> {} // do nothing; the next instruction is wired up already to the jump target
|
||||
Opcode.BCS -> return if(carry) ins.next else ins.nextAlt!!
|
||||
Opcode.BCC -> return if(carry) ins.nextAlt!! else ins.next
|
||||
Opcode.BEQ -> return if(evalstack.peek().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BNE -> return if(evalstack.peek().numericValue().toDouble()!=0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BMI -> return if(evalstack.peek().numericValue().toDouble()<0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BPL -> return if(evalstack.peek().numericValue().toDouble()>=0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BEQ -> return if(evalstack.pop().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BNE -> return if(evalstack.pop().numericValue().toDouble()!=0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BMI -> return if(evalstack.pop().numericValue().toDouble()<0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BPL -> return if(evalstack.pop().numericValue().toDouble()>=0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.CALL -> callstack.push(ins.nextAlt)
|
||||
Opcode.RETURN -> return callstack.pop()
|
||||
Opcode.PUSH_VAR -> {
|
||||
@ -1055,6 +1185,43 @@ class StackVm(val traceOutputFile: String?) {
|
||||
val v = evalstack.pop()
|
||||
evalstack.push(v.msb())
|
||||
}
|
||||
Opcode.AND -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.and(top))
|
||||
}
|
||||
Opcode.OR -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.or(top))
|
||||
}
|
||||
Opcode.XOR -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.xor(top))
|
||||
}
|
||||
Opcode.LESS -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.compareLess(top))
|
||||
}
|
||||
Opcode.GREATER -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.compareGreater(top))
|
||||
}
|
||||
Opcode.LESSEQ -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.compareLessEq(top))
|
||||
}
|
||||
Opcode.GREATEREQ -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.compareGreaterEq(top))
|
||||
}
|
||||
Opcode.EQUAL -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.compareEqual(top))
|
||||
}
|
||||
Opcode.NOTEQUAL -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
evalstack.push(second.compareNotEqual(top))
|
||||
}
|
||||
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}")
|
||||
}
|
||||
|
||||
if(traceOutput!=null) {
|
||||
@ -1068,7 +1235,7 @@ class StackVm(val traceOutputFile: String?) {
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val program = Program.load("examples/stackvmtest.txt")
|
||||
val program = Program.load(args.first())
|
||||
val vm = StackVm(traceOutputFile = null)
|
||||
val dialog = ScreenDialog()
|
||||
vm.load(program, dialog.canvas)
|
||||
@ -1077,7 +1244,14 @@ fun main(args: Array<String>) {
|
||||
dialog.isVisible = true
|
||||
dialog.start()
|
||||
|
||||
val programTimer = Timer(10) { _ -> vm.step() }
|
||||
val programTimer = Timer(10) { _ ->
|
||||
try {
|
||||
vm.step()
|
||||
} catch(bp: VmBreakpointException) {
|
||||
println("Breakpoint: execution halted. Press enter to resume.")
|
||||
readLine()
|
||||
}
|
||||
}
|
||||
programTimer.start()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user