Migrate base parsers to Kotlin

This commit is contained in:
2020-03-05 19:52:07 +01:00
parent d6ef2e9963
commit 4868528e3a
5 changed files with 245 additions and 13 deletions

View File

@@ -0,0 +1,24 @@
package dsl.ast.model.entity
import dsl.ast.model.enumeration.ParsingResult
import dsl.ast.model.node.Node
data class ParserOutput private constructor(val result: ParsingResult, val node: Node) {
fun map(mapper: (Node) -> Node): ParserOutput {
if(result == ParsingResult.FAILED) {
throw RuntimeException("Mapping operation is not allowed for failed parsing output")
}
return ok(mapper(node))
}
companion object {
fun ok(node: Node): ParserOutput {
return ParserOutput(ParsingResult.OK, node)
}
fun fail(): ParserOutput {
return ParserOutput(ParsingResult.FAILED, Node.NONE)
}
}
}

View File

@@ -0,0 +1,6 @@
package dsl.ast.model.enumeration
enum class ParsingResult {
OK,
FAILED
}

View File

@@ -0,0 +1,203 @@
package dsl.ast.parser
import dsl.ast.model.entity.ParserOutput
import dsl.ast.model.enumeration.ParsingResult
import dsl.ast.model.node.Node
import dsl.ast.model.node.TokenNode
import dsl.token.model.entity.Token
import dsl.token.model.entity.TokenList
import dsl.token.model.entity.TokenPosition
import dsl.token.model.enumeration.TokenType
abstract class Parser {
fun parse(input: TokenList): ParserOutput {
val snapshot = input.snapshot()
val output = tryToParse(input)
if(output.result == ParsingResult.FAILED) {
input.restore(snapshot)
}
return output
}
protected abstract fun tryToParse(input: TokenList): ParserOutput
companion object {
// a -> A
fun terminal(terminal: TokenType, createNode: (Token) -> Node = { TokenNode(it) }, assert: Boolean = false): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
if(input.hasCurrent() && input.current.type == terminal) {
val token = input.current
input.ahead()
return ParserOutput.ok(createNode(token))
}
else if(assert) {
throw RuntimeException("Expected $terminal")
}
return ParserOutput.fail()
}
}
}
// oneOf -> a | b | c | ...
fun oneOf(parsers: List<Parser>, expectedString: String? = null, exceptionSupplier: ((TokenList) -> Exception)? = null): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
if(parsers.isEmpty()) {
throw RuntimeException("Provide one parser at least")
}
for (parser in parsers) {
val output = parser.parse(input)
if(output.result == ParsingResult.OK) {
return output
}
}
if (expectedString != null) {
throw RuntimeException("Expected $expectedString")
}
if (exceptionSupplier != null) {
throw exceptionSupplier(input)
}
return ParserOutput.fail()
}
}
}
// a -> A | B | C | ...
fun terminals(terminals: List<TokenType>, createNode: (Token) -> Node = { TokenNode(it) }): Parser {
return oneOf(terminals.map { terminal(it, createNode) })
}
// allOf -> a b c ...
fun allOf(parsers: List<Parser>, createNode: (List<Node>, TokenPosition) -> Node, exceptionSupplier: ((TokenList) -> Exception)? = null): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
if(parsers.isEmpty()) {
throw RuntimeException("Provide one parser at least")
}
val nodes = mutableListOf<Node>()
for (parser in parsers) {
val output = parser.parse(input)
if (output.result == ParsingResult.FAILED) {
if (exceptionSupplier != null) {
throw exceptionSupplier(input)
}
return ParserOutput.fail()
}
nodes += output.node
}
return ParserOutput.ok(createNode(nodes, nodes.first().position))
}
}
}
// leftAssociative -> left | left OP right
fun leftAssociativeOperator(lhsParser: Parser, allowedOperators: List<TokenType>, rhsParser: Parser, createNode: (left: Node, operator: Node, right: Node) -> Node): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
val opParser = terminals(allowedOperators)
var lhs = lhsParser.parse(input)
if(lhs.result == ParsingResult.OK) {
var op = opParser.parse(input)
while (op.result == ParsingResult.OK) {
val rhs = rhsParser.parse(input)
if(rhs.result == ParsingResult.FAILED) { // Todo: Not sure if it should be here
return ParserOutput.fail()
}
lhs = ParserOutput.ok(createNode(lhs.node, op.node, rhs.node))
op = opParser.parse(input)
}
return lhs
}
return ParserOutput.fail()
}
}
}
// repeat -> item*
fun repeat(itemParser: Parser, createNode: (List<Node>, TokenPosition) -> Node): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
val items = mutableListOf<Node>()
while(true) {
val output = itemParser.parse(input)
if(output.result == ParsingResult.OK) {
items += output.node
} else if(items.isEmpty()) {
return ParserOutput.fail()
} else {
return ParserOutput.ok(createNode(items, items.first().position))
}
}
}
}
}
// loop -> begin item* end
fun loop(beginParser: Parser, itemParser: Parser, endParser: Parser, createNode: (Node, List<Node>, Node) -> Node): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
val items = mutableListOf<Node>()
val begin = beginParser.parse(input)
if(begin.result == ParsingResult.OK) {
while(true) {
val end = endParser.parse(input)
if(end.result == ParsingResult.OK) {
return ParserOutput.ok(createNode(begin.node, items, end.node))
}
val item = itemParser.parse(input)
if(item.result == ParsingResult.FAILED) {
return ParserOutput.fail()
}
items += item.node
}
}
return ParserOutput.fail()
}
}
}
fun assert(parser: Parser, expected: String): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
val output = parser.parse(input)
if (output.result == ParsingResult.FAILED) {
throw RuntimeException("Expected $expected")
}
return output
}
}
}
// optional -> a?
fun optional(parser: Parser): Parser {
return object : Parser() {
override fun tryToParse(input: TokenList): ParserOutput {
val output = parser.parse(input)
return if(output.result == ParsingResult.OK) output else ParserOutput.ok(Node.NONE)
}
}
}
}
}

View File

@@ -1,7 +1,5 @@
package dsl.token.model.entity
import java.lang.RuntimeException
class TokenList(val tokens: List<Token>, val lines: List<String>) {
private var cursor = 0
private var snap = 0
@@ -10,16 +8,17 @@ class TokenList(val tokens: List<Token>, val lines: List<String>) {
return tokens[index]
}
fun current(): Token {
if(!hasCurrent()) {
throw RuntimeException("Cursor points to not existing token! Cursor = ${cursor}, length = ${tokens.size}")
val current: Token
get() {
if (!hasCurrent()) {
throw RuntimeException("Cursor points to not existing token! Cursor = ${cursor}, length = ${tokens.size}")
}
return tokens[cursor]
}
return tokens[cursor]
}
fun currentPos(): TokenPosition {
return current().position
return current.position
}
fun hasCurrent(): Boolean {
@@ -54,5 +53,8 @@ class TokenList(val tokens: List<Token>, val lines: List<String>) {
cursor = 0
}
override fun toString(): String {
val currentStr = if(hasCurrent()) "current: ${cursor} -> ${current}" else "<all tokens consumed>"
return "size: ${tokens.size}\n${currentStr}\nall: ${tokens}"
}
}

View File

@@ -1,16 +1,13 @@
package interpreter
import dsl.token.tokenizer.DefaultTokenizer
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
class Interpreter {
fun run(code: String) {
val tokenizer = DefaultTokenizer()
val lines = code.split("\n")
val tokens = tokenizer.tokenize(lines)
println(tokens)
}
fun run(file: File) {