restructure

This commit is contained in:
Irmen de Jong 2018-08-16 16:22:51 +02:00
parent b6ea33efa3
commit d28ce881e4
9 changed files with 174 additions and 146 deletions

View File

@ -9,15 +9,10 @@
memory byte derpA = abs(-2.5-0.5)
memory byte derpB = max(1, 2.2, 4.4, 100)
memory byte cderp = min($ffdd)+ (1/1)
memory byte cderp1 = foobar
memory byte cderp2 = boo.bar.booz
memory byte cderp3 = main.doesnt_exist
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))
memory byte derp3 = round2(100*sin(3))
const byte hopla=55-33
const byte hopla2=100+(-main.hopla2)
const byte hopla3=100+(-hopla)
const byte hopla4 = 100-hopla
const byte hopla1=main.hopla
@ -33,6 +28,8 @@
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
equalQQ = foo(33)
equalQQ = main.foo(33)
XY = hopla*2+hopla1
A = "derp" * %000100
@ -79,6 +76,7 @@ cool:
}
some_label_def: A=44
return 1+999
%breakpoint

View File

@ -1,130 +1,19 @@
package il65
import java.nio.file.Paths
import il65.ast.*
import il65.parser.*
import il65.optimizing.optimizeExpressions
import il65.optimizing.optimizeStatements
import il65.parser.il65Lexer
import il65.parser.il65Parser
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.Lexer
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.collections.HashMap
class MyTokenStream(lexer: Lexer) : CommonTokenStream(lexer) {
data class Comment(val type: String, val line: Int, val comment: String)
fun commentTokens() : List<Comment> {
// extract the comments
val commentTokenChannel = il65Lexer.channelNames.indexOf("HIDDEN")
val theLexer = tokenSource as Lexer
return get(0, size())
.filter { it.channel == commentTokenChannel }
.map {
Comment(theLexer.vocabulary.getSymbolicName(it.type),
it.line, it.text.substringAfter(';').trim())
}
}
}
class ParsingFailedError(override var message: String) : Exception(message)
private val importedModules : HashMap<String, Module> = hashMapOf()
fun loadModule(filePath: Path) : Module {
println("importing '${filePath.fileName}' (from ${filePath.parent})...")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val moduleName = fileNameWithoutSuffix(filePath)
val input = CharStreams.fromPath(filePath)
val lexer = il65Lexer(input)
val tokens = MyTokenStream(lexer)
val parser = il65Parser(tokens)
val parseTree = parser.module()
if(parser.numberOfSyntaxErrors > 0)
throw ParsingFailedError("There are ${parser.numberOfSyntaxErrors} syntax errors in '${filePath.fileName}'.")
// TODO the comments:
// tokens.commentTokens().forEach { println(it) }
// convert to Ast
val moduleAst = parseTree.toAst(moduleName,true)
importedModules[moduleAst.name] = moduleAst
// process imports
val lines = moduleAst.statements.toMutableList()
val imports = lines
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.map { Pair(it.first, executeImportDirective(it.second as Directive, filePath)) }
imports.reversed().forEach {
if(it.second==null) {
// this import was already satisfied. just remove this line.
lines.removeAt(it.first)
} else {
// merge imported lines at this spot
lines.addAll(it.first, it.second!!.statements)
}
}
moduleAst.statements = lines
return moduleAst
}
fun fileNameWithoutSuffix(filePath: Path) =
filePath.fileName.toString().substringBeforeLast('.')
fun discoverImportedModule(name: String, importedFrom: Path, position: Position?): Path {
val fileName = name + ".ill"
val locations = mutableListOf(Paths.get(importedFrom.parent.toString()))
val propPath = System.getProperty("il65.libdir")
if(propPath!=null)
locations.add(Paths.get(propPath))
val envPath = System.getenv("IL65_LIBDIR")
if(envPath!=null)
locations.add(Paths.get(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "lib65"))
locations.forEach {
val file = Paths.get(it.toString(), fileName)
if (Files.isReadable(file)) return file
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
fun executeImportDirective(import: Directive, importedFrom: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if(importedModules.containsKey(moduleName))
return null
val modulePath = discoverImportedModule(moduleName, importedFrom, import.position)
val importedModule = loadModule(modulePath)
importedModule.checkImportedValid()
return importedModule
}
fun main(args: Array<String>) {
try {
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")
val filepath = Paths.get(args[0]).normalize()
val moduleAst = loadModule(filepath)
val moduleAst = importModule(filepath)
moduleAst.linkParents()
val globalNamespace = moduleAst.namespace()
// globalNamespace.debugPrint()
@ -137,10 +26,6 @@ fun main(args: Array<String>) {
moduleAst.statements.forEach {
println(it)
}
// } catch(sx: SyntaxError) {
// System.err.println(sx)
// } catch(ex: ExpressionException) {
// System.err.println(ex)
} catch (px: ParsingFailedError) {
System.err.println(px.message)
}

View File

@ -130,7 +130,7 @@ interface IStatement : Node {
interface IFunctionCall {
var location: Identifier
var target: Identifier
var arglist: List<IExpression>
}
@ -565,20 +565,20 @@ data class Jump(val address: Int?, val identifier: Identifier?) : IStatement {
}
data class FunctionCall(override var location: Identifier, override var arglist: List<IExpression>) : IExpression, IFunctionCall {
data class FunctionCall(override var target: Identifier, override var arglist: List<IExpression>) : IExpression, IFunctionCall {
override var position: Position? = null
override var parent: Node? = null
override fun linkParents(parent: Node) {
this.parent = parent
location.linkParents(this)
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
}
override fun constValue(namespace: INameScope): LiteralValue? {
// if the function is a built-in function and the args are consts, should evaluate!
if(location.scopedName.size>1) return null
return when(location.scopedName[0]){
if(target.scopedName.size>1) return null
return when(target.scopedName[0]){
"sin" -> builtin_sin(arglist, position, namespace)
"cos" -> builtin_cos(arglist, position, namespace)
"abs" -> builtin_abs(arglist, position, namespace)
@ -599,17 +599,17 @@ data class FunctionCall(override var location: Identifier, override var arglist:
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = location.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
}
data class FunctionCallStatement(override var location: Identifier, override var arglist: List<IExpression>) : IStatement, IFunctionCall {
data class FunctionCallStatement(override var target: Identifier, override var arglist: List<IExpression>) : IStatement, IFunctionCall {
override var position: Position? = null
override var parent: Node? = null
override fun linkParents(parent: Node) {
this.parent = parent
location.linkParents(this)
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
}

View File

@ -1,6 +1,7 @@
package il65.ast
import il65.ParsingFailedError
import il65.functions.BuiltIns
import il65.parser.ParsingFailedError
/**
* General checks on the Ast
@ -97,6 +98,9 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
if(subroutine.parent !is Block)
err("subroutines can only be defined in a block (not in other scopes)")
if(BuiltIns.contains(subroutine.name))
err("cannot override a built-in function")
val uniqueNames = subroutine.parameters.map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names should be unique")
@ -154,8 +158,8 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
* Check the variable declarations (values within range etc)
*/
override fun process(decl: VarDecl): IStatement {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, decl.position))
fun err(msg: String, position: Position?=null) {
checkResult.add(SyntaxError(msg, position ?: decl.position))
}
// the initializer value can't refer to the variable itself (recursive definition)
@ -182,13 +186,13 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
}
}
VarDeclType.MEMORY -> {
if(decl.value !is LiteralValue)
// @todo normal error reporting
throw SyntaxError("value of memory var decl is not a literal (it is a ${decl.value!!::class.simpleName}).", decl.value?.position)
val value = decl.value as LiteralValue
if(value.intvalue==null || value.intvalue<0 || value.intvalue>65535) {
err("memory address must be valid integer 0..\$ffff")
if(decl.value !is LiteralValue) {
err("value of memory var decl is not a literal (it is a ${decl.value!!::class.simpleName}).", decl.value?.position)
} else {
val value = decl.value as LiteralValue
if (value.intvalue == null || value.intvalue < 0 || value.intvalue > 65535) {
err("memory address must be valid integer 0..\$ffff")
}
}
}
}
@ -340,6 +344,27 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
return range
}
override fun process(functionCall: FunctionCall): IExpression {
// this function call is (part of) an expression, which should be in a statement somewhere.
var statementNode: Node? = functionCall
while(statementNode !is IStatement && statementNode?.parent != null) statementNode = statementNode.parent
if(statementNode==null)
throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
checkFunctionExists(functionCall.target, statementNode as IStatement)
return super.process(functionCall)
}
override fun process(functionCall: FunctionCallStatement): IStatement {
checkFunctionExists(functionCall.target, functionCall)
return super.process(functionCall)
}
private fun checkFunctionExists(target: Identifier, statement: IStatement) {
if(globalNamespace.lookup(target.scopedName, statement)==null)
checkResult.add(SyntaxError("undefined function or subroutine: ${target.scopedName.joinToString(".")}", statement.position))
}
private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(SyntaxError(msg, position))

View File

@ -1,6 +1,7 @@
package il65.ast
import il65.ParsingFailedError
import il65.parser.ParsingFailedError
/**
* Checks that are specific for imported modules.

View File

@ -2,6 +2,10 @@ package il65.functions
import il65.ast.*
val BuiltIns = listOf("sin", "cos", "abs", "acos", "asin", "tan", "atan", "log", "log10", "sqrt", "max", "min", "round", "rad", "deg")
private fun oneDoubleArg(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Double)->Double): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)

View File

@ -1,6 +1,6 @@
package il65.optimizing
import il65.ParsingFailedError
import il65.parser.ParsingFailedError
import il65.ast.*
import kotlin.math.pow

View File

@ -0,0 +1,22 @@
package il65.parser
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.Lexer
class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) {
data class Comment(val type: String, val line: Int, val comment: String)
fun commentTokens() : List<Comment> {
// extract the comments
val commentTokenChannel = il65Lexer.channelNames.indexOf("HIDDEN")
val theLexer = tokenSource as Lexer
return get(0, size())
.filter { it.channel == commentTokenChannel }
.map {
Comment(theLexer.vocabulary.getSymbolicName(it.type),
it.line, it.text.substringAfter(';').trim())
}
}
}

View File

@ -0,0 +1,93 @@
package il65.parser
import org.antlr.v4.runtime.CharStreams
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import il65.ast.*
class ParsingFailedError(override var message: String) : Exception(message)
private val importedModules : HashMap<String, Module> = hashMapOf()
fun importModule(filePath: Path) : Module {
println("importing '${filePath.fileName}' (from ${filePath.parent})...")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val moduleName = filePath.fileName.toString().substringBeforeLast('.')
val input = CharStreams.fromPath(filePath)
val lexer = il65Lexer(input)
val tokens = CommentHandlingTokenStream(lexer)
val parser = il65Parser(tokens)
val parseTree = parser.module()
if(parser.numberOfSyntaxErrors > 0)
throw ParsingFailedError("There are ${parser.numberOfSyntaxErrors} syntax errors in '${filePath.fileName}'.")
// TODO the comments:
// tokens.commentTokens().forEach { println(it) }
// convert to Ast
val moduleAst = parseTree.toAst(moduleName,true)
importedModules[moduleAst.name] = moduleAst
// process imports
val lines = moduleAst.statements.toMutableList()
val imports = lines
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.map { Pair(it.first, executeImportDirective(it.second as Directive, filePath)) }
imports.reversed().forEach {
if(it.second==null) {
// this import was already satisfied. just remove this line.
lines.removeAt(it.first)
} else {
// merge imported lines at this spot
lines.addAll(it.first, it.second!!.statements)
}
}
moduleAst.statements = lines
return moduleAst
}
fun discoverImportedModule(name: String, importedFrom: Path, position: Position?): Path {
val fileName = name + ".ill"
val locations = mutableListOf(Paths.get(importedFrom.parent.toString()))
val propPath = System.getProperty("il65.libdir")
if(propPath!=null)
locations.add(Paths.get(propPath))
val envPath = System.getenv("IL65_LIBDIR")
if(envPath!=null)
locations.add(Paths.get(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "lib65"))
locations.forEach {
val file = Paths.get(it.toString(), fileName)
if (Files.isReadable(file)) return file
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
fun executeImportDirective(import: Directive, importedFrom: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if(importedModules.containsKey(moduleName))
return null
val modulePath = discoverImportedModule(moduleName, importedFrom, import.position)
val importedModule = importModule(modulePath)
importedModule.checkImportedValid()
return importedModule
}