fix for loop code generation.

added flt conversion function.
attempt at implementing break and continue.
var initializer value can be omitted for numeric vars (and default to 0)
subroutine return statement not needed when no return values.
This commit is contained in:
Irmen de Jong 2018-09-18 23:14:32 +02:00
parent 63492a1805
commit 00d74551b3
9 changed files with 672 additions and 756 deletions

View File

@ -66,6 +66,8 @@ statement :
| labeldef
| returnstmt
| forloop
| breakstmt
| continuestmt
// @todo whileloop, repeatloop
;
@ -216,17 +218,5 @@ branchcondition: 'if_cs' | 'if_cc' | 'if_eq' | 'if_ne' | 'if_pl' | 'if_mi' | 'if
forloop :
'for' (register | identifier) 'in' expression EOL? loop_statement_block EOL
;
loop_statement_block :
'{' EOL
(statement_in_loopblock | EOL) *
'}'
;
statement_in_loopblock :
statement
| breakstmt
| continuestmt
'for' (register | identifier) 'in' expression EOL? statement_block EOL
;

View File

@ -0,0 +1,49 @@
%option enable_floats
~ main {
sub start() -> () {
const word width = 159
const word height = 127
word pixelx
byte pixely
float xx
float yy
float x
float y
float x2
byte iter
word plotx
byte ploty
_vm_gfx_clearscr(11)
for pixely in 0 to height { ; @todo 255 as upper limit doesn't work it overflows the loop
for pixelx in 0 to width {
xx=flt(pixelx)/width/3+0.2 ; @todo fix division to return float always, add // integer division
yy=flt(pixely)/height/3.6+0.4
x=0.0
y=0.0
for iter in 0 to 31 {
if(x*x + y*y > 4) break
x2 = x*x - y*y + xx
y=x*y*2 + yy
x=x2
}
plotx = pixelx*2
ploty = pixely*2
_vm_gfx_pixel(plotx, ploty, iter)
_vm_gfx_pixel(plotx+1, ploty, iter)
_vm_gfx_pixel(plotx, ploty+1, iter)
_vm_gfx_pixel(plotx+1, ploty+1, iter)
}
}
_vm_gfx_text(5, 5, 7, "Mandelbrot Fractal")
}
}

View File

@ -655,7 +655,7 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer
Register.AX, Register.AY, Register.XY -> DataType.WORD
}
val symbol = namespace.lookup(identifier!!.nameInSource, stmt)
val symbol = namespace.lookup(identifier!!.nameInSource, stmt) ?: throw FatalAstException("symbol lookup failed: ${identifier!!.nameInSource}")
if(symbol is VarDecl) return symbol.datatype
throw FatalAstException("cannot determine datatype of assignment target $this")
}
@ -729,8 +729,7 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.WORD -> when(rightDt) {
DataType.BYTE -> DataType.BYTE
DataType.WORD -> DataType.WORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
@ -1057,6 +1056,7 @@ class FunctionCall(override var target: IdentifierReference,
"len" -> builtinLen(arglist, position, namespace)
"lsb" -> builtinLsb(arglist, position, namespace)
"msb" -> builtinMsb(arglist, position, namespace)
"flt" -> builtinFlt(arglist, position, namespace)
"any" -> builtinAny(arglist, position, namespace)
"all" -> builtinAll(arglist, position, namespace)
"floor" -> builtinFloor(arglist, position, namespace)
@ -1109,10 +1109,14 @@ class FunctionCall(override var target: IdentifierReference,
return null // no return value
}
if(stmt.returnvalues.size==1) {
return when(stmt.returnvalues[0].register) {
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
else -> TODO("return type for non-register result from subroutine $stmt")
if(stmt.returnvalues[0].register!=null) {
return when(stmt.returnvalues[0].register!!) {
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
}
} else if(stmt.returnvalues[0].statusflag!=null) {
val flag = stmt.returnvalues[0].statusflag!!
TODO("return value in status flag $flag")
}
}
TODO("return type for subroutine with multiple return values $stmt")
@ -1122,7 +1126,7 @@ class FunctionCall(override var target: IdentifierReference,
override val isIterable: Boolean
get() {
TODO("iterable function call result?")
TODO("isIterable of function call result")
}
}
@ -1364,6 +1368,12 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
val forloop = forloop()?.toAst()
if(forloop!=null) return forloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
@ -1607,7 +1617,7 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst()
val body = loop_statement_block().toAst()
val body = statement_block().toAst()
return ForLoop(loopregister, loopvar, iterable, body, toPosition())
}
@ -1637,17 +1647,3 @@ class ForLoop(val loopRegister: Register?,
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.Loop_statement_blockContext.toAst() : MutableList<IStatement> {
return statement_in_loopblock().asSequence().map {it.toAst()}.toMutableList()
}
private fun prog8Parser.Statement_in_loopblockContext.toAst(): IStatement {
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val contstmt = continuestmt()?.toAst()
if(contstmt!=null) return contstmt
return this.statement().toAst()
}

View File

@ -178,8 +178,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
.map {(it as InlineAssembly).assembly}
.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)")
if(amount==0 && subroutine.returnvalues.isNotEmpty())
err("subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
// @todo validate return values versus subroutine signature
}
}
@ -266,10 +267,22 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
err("float var/const declaration, but floating point is not enabled via options")
}
// for now, variables can only be declared in a block or subroutine (not in a loop statement block) @todo fix this
if(decl.parent !is Block && decl.parent !is Subroutine) {
err ("variables must be declared at block or subroutine level")
}
when(decl.type) {
VarDeclType.VAR, VarDeclType.CONST -> {
if (decl.value == null) {
err("var/const declaration needs a compile-time constant initializer value")
if(decl.datatype == DataType.BYTE || decl.datatype==DataType.WORD || decl.datatype==DataType.FLOAT) {
// initialize numeric var with value zero by default.
val litVal = LiteralValue(DataType.BYTE, 0, position = decl.position)
litVal.parent = decl
decl.value = litVal
} else {
err("var/const declaration needs a compile-time constant initializer value for this type")
}
return super.process(decl)
}
when {
@ -523,10 +536,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
}
when (targetDt) {
DataType.FLOAT -> {
val number = value.floatvalue
?: return err("floating point value expected")
val number = when(value.type) {
DataType.BYTE -> value.bytevalue!!.toDouble()
DataType.WORD -> value.wordvalue!!.toDouble()
DataType.FLOAT -> value.floatvalue!!
else -> return err("numeric value expected")
}
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
return err("floating point value '$number' out of range for MFLPT format")
return err("value '$number' out of range for MFLPT format")
}
DataType.BYTE -> {
val number = value.asIntegerValue ?: return if (value.floatvalue!=null)

View File

@ -3,6 +3,7 @@ package prog8.compiler
import prog8.ast.*
import prog8.stackvm.*
import java.io.PrintStream
import java.util.*
import kotlin.math.abs
@ -132,6 +133,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
var stmtUniqueSequenceNr = 0
private set
val breakStmtLabelStack : Stack<String> = Stack()
val continueStmtLabelStack : Stack<String> = Stack()
override fun process(subroutine: Subroutine): IStatement {
stackvmProg.label(subroutine.scopedname)
translate(subroutine.statements)
@ -182,16 +186,18 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
private fun translate(stmt: Continue) {
stackvmProg.line(stmt.position)
TODO("translate CONTINUE")
// * ..continue statement: goto continue
// we somehow have to know what the correct 'continue' label is
if(continueStmtLabelStack.empty())
throw CompilerException("continue outside of loop statement block")
val label = continueStmtLabelStack.peek()
stackvmProg.instr(Opcode.JUMP, null, label)
}
private fun translate(stmt: Break) {
stackvmProg.line(stmt.position)
TODO("translate BREAK")
// * ..break statement: goto break
// we somehow have to know what the correct 'break' label is
if(breakStmtLabelStack.empty())
throw CompilerException("break outside of loop statement block")
val label = breakStmtLabelStack.peek()
stackvmProg.instr(Opcode.JUMP, null, label)
}
private fun translate(branch: BranchStatement) {
@ -532,7 +538,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
loop.loopRegister!=null ->
translateForOverVariableRange(null, loop.loopRegister, loopVarDt, loop.iterable as RangeExpr, loop.body)
else ->
translateForOverVariableRange(loopVarName, null, loopVarDt, loop.iterable as RangeExpr, loop.body)
translateForOverVariableRange(loop.loopVar!!.nameInSource, null, loopVarDt, loop.iterable as RangeExpr, loop.body)
}
} else {
val litVal = loop.iterable as? LiteralValue
@ -574,6 +580,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
val loopLabel = makeLabel("loop")
val continueLabel = makeLabel("continue")
val breakLabel = makeLabel("break")
continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel)
val varValue = Value(DataType.STR, null, varname)
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.first))
stackvmProg.instr(Opcode.POP_VAR, varValue)
@ -606,9 +615,13 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
stackvmProg.instr(Opcode.BNE, callLabel = loopLabel)
}
stackvmProg.label(breakLabel)
stackvmProg.instr(Opcode.NOP)
breakStmtLabelStack.pop()
continueStmtLabelStack.pop()
}
private fun translateForOverVariableRange(varname: String?, register: Register?, varDt: DataType, range: RangeExpr, body: MutableList<IStatement>) {
private fun translateForOverVariableRange(varname: List<String>?, register: Register?, varDt: DataType, range: RangeExpr, body: MutableList<IStatement>) {
/**
* for LV in start..last { body }
* (and we already know that the range is not empty)
@ -628,29 +641,42 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
* break:
*
*/
val assignmentTarget =
if(varname!=null)
AssignTarget(null, IdentifierReference(listOf(varname), range.position), range.position)
else
AssignTarget(register, null, range.position)
fun makeAssignmentTarget(): AssignTarget {
return if(varname!=null)
AssignTarget(null, IdentifierReference(varname, range.position), range.position)
else
AssignTarget(register, null, range.position)
}
var assignmentTarget = makeAssignmentTarget()
val startAssignment = Assignment(assignmentTarget, null, range.from, range.position)
startAssignment.linkParents(range.parent)
var stepIncrement: PostIncrDecr? = null
var stepAddition: Assignment? = null
if(range.step==null)
if(range.step==null) {
assignmentTarget = makeAssignmentTarget()
stepIncrement = PostIncrDecr(assignmentTarget, "++", range.position)
else
stepIncrement.linkParents(range.parent)
}
else {
assignmentTarget = makeAssignmentTarget()
stepAddition = Assignment(
assignmentTarget,
"+=",
range.step ?: LiteralValue(DataType.BYTE, 1, position = range.position),
range.position
assignmentTarget,
"+=",
range.step ?: LiteralValue(DataType.BYTE, 1, position = range.position),
range.position
)
stepAddition.linkParents(range.parent)
}
translate(startAssignment)
val loopLabel = makeLabel("loop")
val continueLabel = makeLabel("continue")
val breakLabel = makeLabel("break")
continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel)
stackvmProg.label(loopLabel)
translate(body)
stackvmProg.label(continueLabel)
@ -661,12 +687,17 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
val comparison = BinaryExpression(
if(varname!=null)
IdentifierReference(listOf(varname), range.position)
IdentifierReference(varname, range.position)
else
RegisterExpr(register!!, range.position)
,"<=", range.to, range.position)
comparison.linkParents(range.parent)
translate(comparison)
stackvmProg.instr(Opcode.BNE, callLabel = loopLabel)
stackvmProg.label(breakLabel)
stackvmProg.instr(Opcode.NOP)
breakStmtLabelStack.pop()
continueStmtLabelStack.pop()
}
}

View File

@ -8,7 +8,7 @@ val BuiltinFunctionNames = setOf(
"P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr",
"sin", "cos", "abs", "acos", "asin", "tan", "atan", "rnd", "rndw", "rndf",
"ln", "log2", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil",
"max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb",
"max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb", "flt",
"_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char",
"_vm_write_str", "_vm_input_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text"
)
@ -45,7 +45,8 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
}
return when (function) {
"sin", "cos", "tan", "asin", "acos", "atan", "ln", "log2", "log10", "sqrt", "rad", "deg", "avg", "rndf" -> DataType.FLOAT
"sin", "cos", "tan", "asin", "acos", "atan", "ln", "log2", "log10",
"sqrt", "rad", "deg", "avg", "rndf", "flt" -> DataType.FLOAT
"lsb", "msb", "any", "all", "rnd" -> DataType.BYTE
"rndw" -> DataType.WORD
"rol", "rol2", "ror", "ror2", "P_carry", "P_irqd" -> null // no return value so no datatype
@ -200,6 +201,16 @@ fun builtinRad(args: List<IExpression>, position: Position, namespace:INameScope
fun builtinDeg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::toDegrees)
fun builtinFlt(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
// 1 numeric arg, convert to float
if(args.size!=1)
throw SyntaxError("flt requires one numeric argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val number = constval.asNumericValue ?: throw SyntaxError("flt requires one numeric argument", position)
return LiteralValue(DataType.FLOAT, floatvalue = number.toDouble(), position = position)
}
fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
// 1 arg, type = float or int, result type= same as argument type
if(args.size!=1)

File diff suppressed because it is too large Load Diff

View File

@ -160,7 +160,8 @@ enum class Syscall(val callNr: Short) {
FUNC_ALL(88),
FUNC_RND(89), // push a random byte on the stack
FUNC_RNDW(90), // push a random word on the stack
FUNC_RNDF(91) // push a random float on the stack (between 0.0 and 1.0)
FUNC_RNDF(91), // push a random float on the stack (between 0.0 and 1.0)
FUNC_FLT(92)
// note: not all builtin functions of the Prog8 language are present as functions:
// some of them are already opcodes (such as MSB and ROL)!
@ -581,13 +582,14 @@ class Program (val name: String,
val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+")
var nextInstructionLabelname = ""
val nextInstructionLabels = Stack<String>() // more than one label can occur on the same line
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_instructions")
return Pair(instructions, labels)
if(!line.startsWith(' ') && line.endsWith(':')) {
nextInstructionLabelname = line.substring(0, line.length-1)
nextInstructionLabels.push(line.substring(0, line.length-1))
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcodeStr = parts[0].toUpperCase()
@ -621,9 +623,9 @@ class Program (val name: String,
}
}
instructions.add(instruction)
if(nextInstructionLabelname.isNotEmpty()) {
labels[nextInstructionLabelname] = instruction
nextInstructionLabelname = ""
while(nextInstructionLabels.isNotEmpty()) {
val label = nextInstructionLabels.pop()
labels[label] = instruction
}
} else throw VmExecutionException("syntax error at line ${lineNr+1}")
}
@ -844,8 +846,8 @@ class StackVm(val traceOutputFile: String?) {
fun step() {
// step is invoked every 1/100 sec
// we execute 5000 instructions in one go so we end up doing 500.000 instructions per second
val instructionsPerStep = 5000
// we execute 10k instructions in one go so we end up doing 1 million vm instructions per second
val instructionsPerStep = 10000
val start = System.currentTimeMillis()
for(i:Int in 0..instructionsPerStep) {
try {
@ -1111,6 +1113,7 @@ class StackVm(val traceOutputFile: String?) {
Syscall.FUNC_SQRT -> evalstack.push(Value(DataType.FLOAT, sqrt(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_RAD -> evalstack.push(Value(DataType.FLOAT, Math.toRadians(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_DEG -> evalstack.push(Value(DataType.FLOAT, Math.toDegrees(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_FLT -> evalstack.push(Value(DataType.FLOAT, evalstack.pop().numericValue().toDouble()))
Syscall.FUNC_FLOOR -> {
val value = evalstack.pop()
val result =

View File

@ -153,7 +153,8 @@ Variables and values
--------------------
Variables are named values that can change during the execution of the program.
When declaring a variable it is required to specify the initial value it should get.
When declaring a numeric variable it is possible to specify the initial value, if you don't want it to be zero.
For other data types it is required to specify that initial value it should get.
Values will usually be part of an expression or assignment statement::
12345 ; integer number
@ -290,7 +291,9 @@ The resulting value is simply a 16 bit word. Example::
Loops
-----
The *for*-loop is used to iterate over a range of values. Iteration is done in steps of 1, but you can change this.
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
The loop variable must be declared as byte or word earlier. Floating point iteration is not supported.
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
@ -492,6 +495,10 @@ lsb(x)
msb(x)
Get the most significant byte of the word x.
flt(x)
Explicitly convert the number x to a floating point number.
Usually this is done automatically but sometimes it may be required to force this.
any(x)
1 ('true') if any of the values in the non-scalar (array or matrix) value x is 'true' (not zero), else 0 ('false')