177 lines
5.2 KiB
Kotlin
177 lines
5.2 KiB
Kotlin
package net.sergeych.lyng
|
|
|
|
class CompilerContext(val tokens: List<Token>) {
|
|
val labels = mutableSetOf<String>()
|
|
|
|
var breakFound = false
|
|
|
|
var loopLevel = 0
|
|
|
|
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
|
if (++loopLevel == 0) breakFound = false
|
|
val result = f()
|
|
return Pair(breakFound, result).also {
|
|
--loopLevel
|
|
}
|
|
}
|
|
|
|
var currentIndex = 0
|
|
|
|
fun hasNext() = currentIndex < tokens.size
|
|
fun hasPrevious() = currentIndex > 0
|
|
fun next() =
|
|
if( currentIndex < tokens.size ) tokens[currentIndex++]
|
|
else Token("", tokens.last().pos, Token.Type.EOF)
|
|
// throw IllegalStateException("No more tokens")
|
|
|
|
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
|
|
|
fun savePos() = currentIndex
|
|
fun restorePos(pos: Int) {
|
|
currentIndex = pos
|
|
}
|
|
|
|
fun ensureLabelIsValid(pos: Pos, label: String) {
|
|
if (label !in labels)
|
|
throw ScriptError(pos, "Undefined label '$label'")
|
|
}
|
|
|
|
@Suppress("unused")
|
|
fun requireId() = requireToken(Token.Type.ID, "identifier is required")
|
|
|
|
fun requireToken(type: Token.Type, message: String = "required ${type.name}"): Token =
|
|
next().also {
|
|
if (type != it.type) throw ScriptError(it.pos, message)
|
|
}
|
|
|
|
@Suppress("unused")
|
|
fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
|
|
throw ScriptError(at, message)
|
|
}
|
|
|
|
fun currentPos(): Pos = tokens[currentIndex].pos
|
|
|
|
/**
|
|
* If the next token is identifier `name`, skip it and return `true`.
|
|
* else leave where it is and return `false`
|
|
*/
|
|
fun skipId(name: String): Boolean {
|
|
current().let { t ->
|
|
if( t.type == Token.Type.ID && t.value == name ) {
|
|
next()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Skips next token if its type is `tokenType`, returns `true` if so.
|
|
* @param errorMessage message to throw if next token is not `tokenType`
|
|
* @param isOptional if `true` and token is not of `tokenType`, just return `false` and does not skip it
|
|
* @return `true` if the token was skipped
|
|
* @throws ScriptError if [isOptional] is `false` and next token is not of [tokenType]
|
|
*/
|
|
fun skipTokenOfType(
|
|
tokenType: Token.Type,
|
|
errorMessage: String = "expected ${tokenType.name}",
|
|
isOptional: Boolean = false
|
|
): Boolean {
|
|
val t = next()
|
|
return if (t.type != tokenType) {
|
|
if (!isOptional) {
|
|
println("unexpected: $t (needed $tokenType)")
|
|
throw ScriptError(t.pos, errorMessage)
|
|
} else {
|
|
previous()
|
|
false
|
|
}
|
|
} else true
|
|
}
|
|
|
|
@Suppress("unused")
|
|
fun skipTokens(vararg tokenTypes: Token.Type) {
|
|
while (next().type in tokenTypes) { /**/
|
|
}
|
|
previous()
|
|
}
|
|
|
|
|
|
inline fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
|
val t = next()
|
|
return if (t.type == typeId) {
|
|
f(t)
|
|
true
|
|
} else {
|
|
previous()
|
|
false
|
|
}
|
|
}
|
|
|
|
inline fun addBreak() {
|
|
breakFound = true
|
|
}
|
|
|
|
/**
|
|
* Return value of the next token if it is an identifier, null otherwise.
|
|
* Does not change position.
|
|
*/
|
|
@Suppress("unused")
|
|
fun nextIdValue(): String? {
|
|
return if (hasNext()) {
|
|
val nt = tokens[currentIndex]
|
|
if (nt.type == Token.Type.ID)
|
|
nt.value
|
|
else null
|
|
} else null
|
|
}
|
|
|
|
@Suppress("unused")
|
|
fun current(): Token = tokens[currentIndex]
|
|
|
|
/**
|
|
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
|
*/
|
|
@Suppress("unused")
|
|
fun atOffset(offset: Int): Token? =
|
|
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
|
|
|
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
|
val pos = savePos()
|
|
var count = 0
|
|
while( count < qualifiers.size) {
|
|
val t = next()
|
|
when(t.type) {
|
|
Token.Type.ID -> {
|
|
if( t.value in qualifiers ) count++
|
|
else { restorePos(pos); return false }
|
|
}
|
|
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
|
else -> { restorePos(pos); return false }
|
|
}
|
|
}
|
|
val t = next()
|
|
if( t.type == Token.Type.ID && t.value == keyword ) {
|
|
return true
|
|
} else {
|
|
restorePos(pos)
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Skip newlines and comments. Returns (and reads) first non-whitespace token.
|
|
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
|
*/
|
|
fun skipWsTokens(): Token {
|
|
while( current().type in wstokens ) {
|
|
println("skipws ${current()}")
|
|
next()
|
|
}
|
|
return next()
|
|
}
|
|
|
|
companion object {
|
|
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
|
}
|
|
} |