more stackvm opcodes, and sort of finished the stackvm compiler

This commit is contained in:
Irmen de Jong 2018-09-13 00:46:52 +02:00
parent efd3b1f5c6
commit 949e468543
14 changed files with 512 additions and 161 deletions

View File

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

View File

@ -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.
*/

View File

@ -18,6 +18,7 @@
label_in_extra2:
X = 33
return
sub sub_in_extra2() -> () {
return

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
*/

View File

@ -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,9 +567,14 @@ 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 -> {
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,
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_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,19 +716,34 @@ 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 -> {
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 -> {
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 -> {
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) {
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")
} 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()
}
}