mirror of
https://github.com/irmen/prog8.git
synced 2024-12-27 20:33:39 +00:00
evals
This commit is contained in:
parent
4c250fdc17
commit
74fd5d29b8
@ -1,10 +1,18 @@
|
||||
~ main $c003 {
|
||||
memory byte derp = $ffdd
|
||||
;memory byte derp = $ffdd
|
||||
;memory byte derp2 = 2+$ffdd+sin(3)
|
||||
memory byte derp3 = round(sin(3))
|
||||
const byte hopla=55-33
|
||||
const byte hopla2=55-hopla
|
||||
const byte hopla2=100+(-main.hopla)
|
||||
const byte hopla3=100+(-hopla)
|
||||
const byte hopla4 = 100-hopla
|
||||
const byte hopla1=main.hopla
|
||||
const float blerp1 = zwop / 2.22
|
||||
const float zwop = -1.7014118345e+38
|
||||
const float zwop = -1.7014118345e+38 + derp.foo.bar
|
||||
const float zwop2 = -1.7014118345e+38
|
||||
const float blerp2 = zwop / 2.22
|
||||
|
||||
XY = hopla*2+hopla1
|
||||
A = "derp" * %000100
|
||||
|
||||
mega:
|
||||
@ -13,6 +21,7 @@ cool:
|
||||
Y=2
|
||||
|
||||
sub foo () -> () {
|
||||
byte blerp = 3
|
||||
A=99
|
||||
return 33
|
||||
X =33
|
||||
|
@ -58,7 +58,7 @@ fun loadModule(filePath: Path) : Module {
|
||||
importedModules[moduleAst.name] = moduleAst
|
||||
|
||||
// process imports
|
||||
val lines = moduleAst.lines.toMutableList()
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
val imports = lines
|
||||
.mapIndexed { i, it -> Pair(i, it) }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
@ -70,11 +70,11 @@ fun loadModule(filePath: Path) : Module {
|
||||
lines.removeAt(it.first)
|
||||
} else {
|
||||
// merge imported lines at this spot
|
||||
lines.addAll(it.first, it.second!!.lines)
|
||||
lines.addAll(it.first, it.second!!.statements)
|
||||
}
|
||||
}
|
||||
|
||||
moduleAst.lines = lines
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
}
|
||||
|
||||
@ -124,16 +124,16 @@ fun main(args: Array<String>) {
|
||||
val filepath = Paths.get(args[0]).normalize()
|
||||
val moduleAst = loadModule(filepath)
|
||||
moduleAst.linkParents()
|
||||
var globalNamespace = moduleAst.namespace()
|
||||
val globalNamespace = moduleAst.namespace()
|
||||
globalNamespace.debugPrint()
|
||||
|
||||
// moduleAst.optimize(namespace)
|
||||
// moduleAst.checkValid() // check if final tree is valid
|
||||
//
|
||||
// // todo compile to asm...
|
||||
// moduleAst.lines.forEach {
|
||||
// println(it)
|
||||
// }
|
||||
moduleAst.optimize(globalNamespace)
|
||||
moduleAst.checkValid(globalNamespace) // check if final tree is valid
|
||||
|
||||
// todo compile to asm...
|
||||
moduleAst.statements.forEach {
|
||||
println(it)
|
||||
}
|
||||
} catch(sx: SyntaxError) {
|
||||
sx.printError()
|
||||
} catch (px: ParsingFailedError) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package il65.ast
|
||||
|
||||
import il65.functions.*
|
||||
import il65.parser.il65Parser
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
@ -36,7 +37,7 @@ class ExpressionException(override var message: String) : AstException(message)
|
||||
|
||||
class SyntaxError(override var message: String, val position: Position?) : AstException(message) {
|
||||
fun printError() {
|
||||
val location = if(position == null) "" else position.toString()
|
||||
val location = position?.toString() ?: ""
|
||||
System.err.println("$location $message")
|
||||
}
|
||||
}
|
||||
@ -49,7 +50,7 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
|
||||
|
||||
interface IAstProcessor {
|
||||
fun process(module: Module) {
|
||||
module.lines = module.lines.map { it.process(this) }
|
||||
module.statements = module.statements.map { it.process(this) }
|
||||
}
|
||||
fun process(expr: PrefixExpression): IExpression {
|
||||
expr.expression = expr.expression.process(this)
|
||||
@ -80,6 +81,9 @@ interface IAstProcessor {
|
||||
functionCall.arglist = functionCall.arglist.map { it.process(this) }
|
||||
return functionCall
|
||||
}
|
||||
fun process(identifier: Identifier): IExpression {
|
||||
return identifier
|
||||
}
|
||||
fun process(jump: Jump): IStatement {
|
||||
return jump
|
||||
}
|
||||
@ -103,28 +107,59 @@ interface INameScope {
|
||||
val position: Position?
|
||||
var statements: List<IStatement>
|
||||
|
||||
fun subScopes(): List<INameScope> = statements.filter { it is INameScope }.map { it as INameScope }
|
||||
fun subScopes() = statements.filter { it is INameScope } .map { it as INameScope }.associate { it.name to it }
|
||||
|
||||
fun definedNames(): List<IStatement> = statements.filter { it is Label || it is VarDecl }
|
||||
fun definedNames() = statements.filter { it is Label || it is VarDecl }
|
||||
.associate {
|
||||
when(it) {
|
||||
is Label -> it.name to it
|
||||
is VarDecl -> it.name to it
|
||||
else -> throw AstException("expected label or vardecl")
|
||||
}
|
||||
}
|
||||
|
||||
fun lookup(scopedName: List<String>) : IStatement? {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
|
||||
if(scopedName.size>1) {
|
||||
// it's a qualified name, look it up from the namespace root
|
||||
var scope: INameScope? = this
|
||||
scopedName.dropLast(1).forEach {
|
||||
scope = scope?.subScopes()?.get(it)
|
||||
if(scope==null)
|
||||
return null
|
||||
}
|
||||
val foundScope : INameScope = scope!!
|
||||
return foundScope.definedNames()[scopedName.last()]
|
||||
?:
|
||||
foundScope.subScopes()[scopedName.last()] as IStatement?
|
||||
} else {
|
||||
// unqualified name, find the scope the statement is in, look in that first
|
||||
var statementScope: Node? = statement
|
||||
while(true) {
|
||||
while (statementScope !is INameScope && statementScope?.parent != null)
|
||||
statementScope = statementScope.parent
|
||||
if (statementScope == null)
|
||||
return null
|
||||
val localScope = statementScope as INameScope
|
||||
val result = localScope.definedNames()[scopedName[0]]
|
||||
if (result != null)
|
||||
return result
|
||||
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
|
||||
if (subscope != null)
|
||||
return subscope
|
||||
// not found in this scope, look one higher up
|
||||
statementScope = statementScope.parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun debugPrint() {
|
||||
fun printNames(indent: Int, namespace: INameScope) {
|
||||
println(" ".repeat(4*indent) + "${namespace.name} -> ${namespace::class.simpleName} at ${namespace.position}")
|
||||
namespace.definedNames().forEach {
|
||||
val name =
|
||||
when(it) {
|
||||
is Label -> it.name
|
||||
is VarDecl -> it.name
|
||||
else -> throw AstException("expected label or vardecl")
|
||||
}
|
||||
println(" ".repeat(4 * (1 + indent)) + "$name -> ${it::class.simpleName} at ${it.position}")
|
||||
println(" ".repeat(4 * (1 + indent)) + "${it.key} -> ${it.value::class.simpleName} at ${it.value.position}")
|
||||
}
|
||||
namespace.subScopes().forEach {
|
||||
printNames(indent+1, it)
|
||||
printNames(indent+1, it.value)
|
||||
}
|
||||
}
|
||||
printNames(0, this)
|
||||
@ -132,8 +167,8 @@ interface INameScope {
|
||||
}
|
||||
|
||||
|
||||
data class Module(val name: String,
|
||||
var lines: List<IStatement>) : Node {
|
||||
data class Module(override val name: String,
|
||||
override var statements: List<IStatement>) : Node, INameScope {
|
||||
override var position: Position? = null
|
||||
override var parent: Node? = null
|
||||
|
||||
@ -142,7 +177,7 @@ data class Module(val name: String,
|
||||
}
|
||||
fun linkParents() {
|
||||
parent = null
|
||||
lines.forEach {it.linkParents(this)}
|
||||
statements.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
fun process(processor: IAstProcessor) {
|
||||
@ -154,7 +189,7 @@ data class Module(val name: String,
|
||||
override var statements: List<IStatement>,
|
||||
override val position: Position?) : INameScope
|
||||
|
||||
return GlobalNamespace("<<<global>>>", lines, position)
|
||||
return GlobalNamespace("<<<global>>>", statements, position)
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,10 +362,7 @@ data class PrefixExpression(val operator: String, var expression: IExpression) :
|
||||
expression.linkParents(this)
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? {
|
||||
throw ExpressionException("should have been optimized away before const value was asked")
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? = null
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
}
|
||||
|
||||
@ -434,20 +466,21 @@ data class Identifier(val scopedName: List<String>) : IExpression {
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? {
|
||||
val node = namespace.lookup(scopedName)
|
||||
return if(node==null) null
|
||||
else {
|
||||
var vardecl = node as VarDecl
|
||||
if(vardecl!=null){
|
||||
if(vardecl.type!=VarDeclType.CONST)
|
||||
throw SyntaxError("constant expected", position)
|
||||
return vardecl.value?.constValue(namespace)
|
||||
}
|
||||
throw SyntaxError("expected a literal value", position)
|
||||
val node = namespace.lookup(scopedName, this)
|
||||
?:
|
||||
throw SyntaxError("undefined symbol: ${scopedName.joinToString(".")}", position) // todo add to a list of errors instead
|
||||
val vardecl = node as? VarDecl
|
||||
if(vardecl==null) {
|
||||
// todo add to a list of errors instead
|
||||
throw SyntaxError("name should be a constant, instead of: ${node::class.simpleName}", position)
|
||||
} else if(vardecl.type!=VarDeclType.CONST) {
|
||||
// todo add to a list of errors instead
|
||||
throw SyntaxError("name should be a constant, instead of: ${vardecl.type}", position)
|
||||
}
|
||||
return vardecl.value?.constValue(namespace)
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor) = this
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
}
|
||||
|
||||
|
||||
@ -492,7 +525,24 @@ data class FunctionCall(var location: Identifier, var arglist: List<IExpression>
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? {
|
||||
// if the function is a built-in function and the args are consts, should evaluate!
|
||||
return null
|
||||
println("CONSTVALUE of Function call $location") // todo
|
||||
if(location.scopedName.size>1) return null
|
||||
return when(location.scopedName[0]){
|
||||
"sin" -> builtin_sin(arglist, namespace)
|
||||
"cos" -> builtin_cos(arglist, namespace)
|
||||
"abs" -> builtin_abs(arglist, namespace)
|
||||
"acos" -> builtin_acos(arglist, namespace)
|
||||
"asin" -> builtin_asin(arglist, namespace)
|
||||
"tan" -> builtin_tan(arglist, namespace)
|
||||
"atan" -> builtin_atan(arglist, namespace)
|
||||
"log" -> builtin_log(arglist, namespace)
|
||||
"log10" -> builtin_log10(arglist, namespace)
|
||||
"sqrt" -> builtin_sqrt(arglist, namespace)
|
||||
"max" -> builtin_max(arglist, namespace)
|
||||
"min" -> builtin_min(arglist, namespace)
|
||||
"round" -> builtin_round(arglist, namespace)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
|
@ -15,7 +15,7 @@ fun Module.checkValid(globalNamespace: INameScope) {
|
||||
}
|
||||
|
||||
|
||||
class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
|
||||
class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
||||
private val blockNames: HashMap<String, Position?> = hashMapOf()
|
||||
|
||||
@ -26,13 +26,13 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
|
||||
override fun process(jump: Jump): IStatement {
|
||||
super.process(jump)
|
||||
if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
|
||||
checkResult.add(SyntaxError("jump address must be valid 0..\$ffff", jump.position))
|
||||
checkResult.add(SyntaxError("jump address must be valid integer 0..\$ffff", jump.position))
|
||||
return jump
|
||||
}
|
||||
|
||||
override fun process(block: Block): IStatement {
|
||||
if(block.address!=null && (block.address<0 || block.address>65535)) {
|
||||
checkResult.add(SyntaxError("block memory address must be valid 0..\$ffff", block.position))
|
||||
checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position))
|
||||
}
|
||||
val existing = blockNames[block.name]
|
||||
if(existing!=null) {
|
||||
@ -54,6 +54,31 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
|
||||
labelnames[it.name] = it.position
|
||||
}
|
||||
}
|
||||
|
||||
// check if var names are unique
|
||||
val variables = block.statements.filter { it is VarDecl }.map{ it as VarDecl }
|
||||
val varnames= mutableMapOf<String, Position?>()
|
||||
variables.forEach {
|
||||
val existing = varnames[it.name]
|
||||
if(existing!=null) {
|
||||
checkResult.add(SyntaxError("variable name conflict, first defined on line ${existing.line}", it.position))
|
||||
} else {
|
||||
varnames[it.name] = it.position
|
||||
}
|
||||
}
|
||||
|
||||
// check if subroutine names are unique
|
||||
val subroutines = block.statements.filter { it is Subroutine }.map{ it as Subroutine }
|
||||
val subnames = mutableMapOf<String, Position?>()
|
||||
subroutines.forEach {
|
||||
val existing = subnames[it.name]
|
||||
if(existing!=null) {
|
||||
checkResult.add(SyntaxError("subroutine name conflict, first defined on line ${existing.line}", it.position))
|
||||
} else {
|
||||
subnames[it.name] = it.position
|
||||
}
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
@ -88,6 +113,18 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// check if var names are unique
|
||||
val variables = subroutine.statements.filter { it is VarDecl }.map{ it as VarDecl }
|
||||
val varnames= mutableMapOf<String, Position?>()
|
||||
variables.forEach {
|
||||
val existing = varnames[it.name]
|
||||
if(existing!=null) {
|
||||
checkResult.add(SyntaxError("variable name conflict, first defined on line ${existing.line}", it.position))
|
||||
} else {
|
||||
varnames[it.name] = it.position
|
||||
}
|
||||
}
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp')
|
||||
if(subroutine.statements.count { it is Return || it is Jump } == 0) {
|
||||
@ -129,9 +166,12 @@ class AstChecker(val globalNamespace: INameScope) : IAstProcessor {
|
||||
}
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
if(decl.value !is LiteralValue)
|
||||
throw AstException("${decl.value?.position} value of memory var decl is not a literal (it is a ${decl.value!!::class.simpleName}). This is likely a bug in the AstOptimizer")
|
||||
|
||||
val value = decl.value as LiteralValue
|
||||
if(value.intvalue==null || value.intvalue<0 || value.intvalue>65535) {
|
||||
err("memory address must be valid 0..\$ffff")
|
||||
err("memory address must be valid integer 0..\$ffff")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ fun Module.optimize(globalNamespace: INameScope) {
|
||||
}
|
||||
|
||||
|
||||
class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
|
||||
class AstOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
|
||||
@ -26,6 +26,14 @@ class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
|
||||
optimizationsDone = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* some identifiers can be replaced with the constant value they refer to
|
||||
*/
|
||||
override fun process(identifier: Identifier): IExpression {
|
||||
println("PROCESS ID $identifier") // todo
|
||||
val const = identifier.constValue(globalNamespace)
|
||||
return const ?: identifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process a unary prefix expression.
|
||||
@ -43,7 +51,7 @@ class AstOptimizer(val globalNamespace: INameScope) : IAstProcessor {
|
||||
expr.operator == "-" -> when {
|
||||
subexpr.intvalue != null -> {
|
||||
optimizationsDone++
|
||||
LiteralValue(intvalue = subexpr.intvalue)
|
||||
LiteralValue(intvalue = -subexpr.intvalue)
|
||||
}
|
||||
subexpr.floatvalue != null -> {
|
||||
optimizationsDone++
|
||||
|
@ -24,8 +24,8 @@ class ImportedAstChecker : IAstProcessor {
|
||||
|
||||
override fun process(module: Module) {
|
||||
super.process(module)
|
||||
val newLines : MutableList<IStatement> = mutableListOf()
|
||||
module.lines.forEach {
|
||||
val newStatements : MutableList<IStatement> = mutableListOf()
|
||||
module.statements.forEach {
|
||||
val stmt = it.process(this)
|
||||
if(stmt is Directive) {
|
||||
if(stmt.parent is Module) {
|
||||
@ -33,12 +33,12 @@ class ImportedAstChecker : IAstProcessor {
|
||||
"%output", "%launcher", "%zp", "%address" ->
|
||||
println("${stmt.position} Warning: ignoring module directive because it was imported: ${stmt.directive}")
|
||||
else ->
|
||||
newLines.add(stmt)
|
||||
newStatements.add(stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
else newLines.add(stmt)
|
||||
else newStatements.add(stmt)
|
||||
}
|
||||
module.lines = newLines
|
||||
module.statements = newStatements
|
||||
}
|
||||
}
|
||||
|
62
il65/src/il65/functions/BuiltinFunctions.kt
Normal file
62
il65/src/il65/functions/BuiltinFunctions.kt
Normal file
@ -0,0 +1,62 @@
|
||||
package il65.functions
|
||||
|
||||
import il65.ast.IExpression
|
||||
import il65.ast.INameScope
|
||||
import il65.ast.LiteralValue
|
||||
|
||||
private fun oneDoubleArg(args: List<IExpression>, namespace: INameScope, function: (arg: Double)->Double): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw UnsupportedOperationException("built-in function requires one floating point argument")
|
||||
|
||||
val float = args[0].constValue(namespace)?.asFloat()
|
||||
if(float!=null) {
|
||||
val result = LiteralValue(floatvalue = function(float))
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw UnsupportedOperationException("built-in function requires floating point value as argument")
|
||||
}
|
||||
|
||||
private fun oneDoubleArgOutputInt(args: List<IExpression>, namespace: INameScope, function: (arg: Double)->Int): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw UnsupportedOperationException("built-in function requires one floating point argument")
|
||||
|
||||
val float = args[0].constValue(namespace)?.asFloat()
|
||||
if(float!=null) {
|
||||
val result = LiteralValue(intvalue = function(float))
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw UnsupportedOperationException("built-in function requires floating point value as argument")
|
||||
}
|
||||
|
||||
private fun twoDoubleArg(args: List<IExpression>, namespace: INameScope, function: (arg1: Double, arg2: Double)->Double): LiteralValue {
|
||||
if(args.size!=2)
|
||||
throw UnsupportedOperationException("built-in function requires two floating point arguments")
|
||||
|
||||
val float1 = args[0].constValue(namespace)?.asFloat()
|
||||
val float2 = args[1].constValue(namespace)?.asFloat()
|
||||
if(float1!=null && float2!=null) {
|
||||
val result = LiteralValue(floatvalue = function(float1, float2))
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw UnsupportedOperationException("built-in function requires two floating point values as argument")
|
||||
}
|
||||
|
||||
fun builtin_round(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArgOutputInt(args, namespace) { it -> Math.round(it).toInt() }
|
||||
fun builtin_sin(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::sin)
|
||||
fun builtin_cos(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::cos)
|
||||
fun builtin_abs(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::abs)
|
||||
fun builtin_acos(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::acos)
|
||||
fun builtin_asin(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::asin)
|
||||
fun builtin_tan(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::tan)
|
||||
fun builtin_atan(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::atan)
|
||||
fun builtin_log(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::log)
|
||||
fun builtin_log10(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::log10)
|
||||
fun builtin_sqrt(args: List<IExpression>, namespace: INameScope): LiteralValue = oneDoubleArg(args, namespace, Math::sqrt)
|
||||
fun builtin_max(args: List<IExpression>, namespace: INameScope): LiteralValue = twoDoubleArg(args, namespace, Math::max)
|
||||
fun builtin_min(args: List<IExpression>, namespace: INameScope): LiteralValue = twoDoubleArg(args, namespace, Math::min)
|
Loading…
Reference in New Issue
Block a user