parser: various code and quality enhancements

This commit is contained in:
Alex Zenla 2023-10-08 04:46:23 -07:00
parent fdac4fb96a
commit 73a458b673
Signed by: alex
GPG Key ID: C0780728420EBFE5
23 changed files with 316 additions and 193 deletions

View File

@ -1,7 +1,7 @@
// GENERATED CODE FROM PORK AST CODEGEN // GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast.gen package gay.pizza.pork.ast.gen
class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> { class NodeCoalescer(val followChildren: Boolean = true, val handler: (Node) -> Unit) : NodeVisitor<Unit> {
override fun visitArgumentSpec(node: ArgumentSpec): Unit = override fun visitArgumentSpec(node: ArgumentSpec): Unit =
handle(node) handle(node)
@ -97,6 +97,8 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
fun handle(node: Node) { fun handle(node: Node) {
handler(node) handler(node)
node.visitChildren(this) if (followChildren) {
node.visitChildren(this)
}
} }
} }

View File

@ -193,6 +193,11 @@ class AstStandardCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
"NodeCoalescer", "NodeCoalescer",
inherits = mutableListOf("NodeVisitor<Unit>"), inherits = mutableListOf("NodeVisitor<Unit>"),
members = mutableListOf( members = mutableListOf(
KotlinMember(
"followChildren",
"Boolean",
value = "true"
),
KotlinMember( KotlinMember(
"handler", "handler",
"(Node) -> Unit" "(Node) -> Unit"
@ -227,7 +232,9 @@ class AstStandardCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
) )
) )
handleFunction.body.add("handler(node)") handleFunction.body.add("handler(node)")
handleFunction.body.add("node.visitChildren(this)") handleFunction.body.add("if (followChildren) {")
handleFunction.body.add(" node.visitChildren(this)")
handleFunction.body.add("}")
coalescerClass.functions.add(handleFunction) coalescerClass.functions.add(handleFunction)
write("NodeCoalescer.kt", KotlinWriter(coalescerClass)) write("NodeCoalescer.kt", KotlinWriter(coalescerClass))

View File

@ -0,0 +1,16 @@
package gay.pizza.pork.common
class IndentBuffer(
val buffer: StringBuilder = StringBuilder(),
indent: String = " "
) : IndentTracked(indent), Appendable by buffer, CharSequence by buffer {
override fun emit(text: String) {
append(text)
}
override fun emitLine(text: String) {
appendLine(text)
}
override fun toString(): String = buffer.toString()
}

View File

@ -1,36 +1,11 @@
package gay.pizza.pork.common package gay.pizza.pork.common
class IndentPrinter( class IndentPrinter(indent: String = " ") : IndentTracked(indent) {
val buffer: StringBuilder = StringBuilder(), override fun emit(text: String) {
val indent: String = " " print(text)
) : Appendable by buffer, CharSequence by buffer {
private var indentLevel: Int = 0
private var indentLevelText: String = ""
fun emitIndent() {
append(indentLevelText)
} }
fun emitIndentedLine(line: String) { override fun emitLine(text: String) {
emitIndent() println(text)
appendLine(line)
} }
fun increaseIndent() {
indentLevel++
indentLevelText += indent
}
fun decreaseIndent() {
indentLevel--
indentLevelText = indent.repeat(indentLevel)
}
inline fun indented(block: IndentPrinter.() -> Unit) {
increaseIndent()
block(this)
decreaseIndent()
}
override fun toString(): String = buffer.toString()
} }

View File

@ -0,0 +1,37 @@
package gay.pizza.pork.common
abstract class IndentTracked(val indent: String) {
private var internalIndentLevel = 0
private var indentLevelText = ""
val indentLevel: Int
get() = internalIndentLevel
fun emitIndent() {
emit(indentLevelText)
}
fun emitIndentedLine(line: String) {
emitIndent()
emitLine(line)
}
fun increaseIndent() {
internalIndentLevel++
indentLevelText += indent
}
fun decreaseIndent() {
internalIndentLevel--
indentLevelText = indent.repeat(indentLevel)
}
inline fun indented(block: IndentTracked.() -> Unit) {
increaseIndent()
block(this)
decreaseIndent()
}
abstract fun emit(text: String)
abstract fun emitLine(text: String)
}

View File

@ -337,7 +337,6 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
InfixOperator.Minus -> subtract(convert(left), convert(right)) InfixOperator.Minus -> subtract(convert(left), convert(right))
InfixOperator.Multiply -> multiply(convert(left), convert(right)) InfixOperator.Multiply -> multiply(convert(left), convert(right))
InfixOperator.Divide -> divide(convert(left), convert(right)) InfixOperator.Divide -> divide(convert(left), convert(right))
InfixOperator.Equals, InfixOperator.NotEquals, InfixOperator.BooleanAnd, InfixOperator.BooleanOr -> throw RuntimeException("Unable to handle operation $op")
InfixOperator.BinaryAnd -> binaryAnd(convert(left), convert(right)) InfixOperator.BinaryAnd -> binaryAnd(convert(left), convert(right))
InfixOperator.BinaryOr -> binaryOr(convert(left), convert(right)) InfixOperator.BinaryOr -> binaryOr(convert(left), convert(right))
InfixOperator.BinaryExclusiveOr -> binaryExclusiveOr(convert(left), convert(right)) InfixOperator.BinaryExclusiveOr -> binaryExclusiveOr(convert(left), convert(right))
@ -347,6 +346,8 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
InfixOperator.Greater -> greater(convert(left), convert(right)) InfixOperator.Greater -> greater(convert(left), convert(right))
InfixOperator.LesserEqual -> lesserEqual(convert(left), convert(right)) InfixOperator.LesserEqual -> lesserEqual(convert(left), convert(right))
InfixOperator.GreaterEqual -> greaterEqual(convert(left), convert(right)) InfixOperator.GreaterEqual -> greaterEqual(convert(left), convert(right))
InfixOperator.Equals, InfixOperator.NotEquals, InfixOperator.BooleanAnd, InfixOperator.BooleanOr ->
throw RuntimeException("Unable to handle operation $op")
} }
} }

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.parser
interface CharConsumer {
fun consume(type: TokenType, tokenizer: Tokenizer): String?
}

View File

@ -1,8 +1,10 @@
package gay.pizza.pork.parser package gay.pizza.pork.parser
interface CharSource : PeekableSource<Char> { interface CharSource : PeekableSource<Char> {
fun peek(index: Int): Char
companion object { companion object {
@Suppress("ConstPropertyName") @Suppress("ConstPropertyName")
const val NullChar = 0.toChar() const val EndOfFile = 0.toChar()
} }
} }

View File

@ -1,7 +1,7 @@
package gay.pizza.pork.parser package gay.pizza.pork.parser
fun CharSource.readToString(): String = buildString { fun CharSource.readToString(): String = buildString {
while (peek() != CharSource.NullChar) { while (peek() != CharSource.EndOfFile) {
append(next()) append(next())
} }
} }

View File

@ -0,0 +1,48 @@
package gay.pizza.pork.parser
@Suppress("CanBeParameter")
class MatchedCharConsumer(
val start: CharSequence,
val end: CharSequence,
vararg val options: Options
) : CharConsumer {
private val eofTerminationAllowed = options.contains(Options.AllowEofTermination)
override fun consume(type: TokenType, tokenizer: Tokenizer): String? {
if (!tokenizer.peek(start)) {
return null
}
val buffer = StringBuilder()
tokenizer.read(start.length, buffer)
var endsNeededToTerminate = 1
while (true) {
if (tokenizer.peek(start)) {
endsNeededToTerminate++
tokenizer.read(start.length, buffer)
continue
}
if (tokenizer.peek(end)) {
endsNeededToTerminate--
tokenizer.read(end.length, buffer)
}
if (endsNeededToTerminate == 0) {
return buffer.toString()
}
val char = tokenizer.source.next()
if (char == CharSource.EndOfFile) {
if (eofTerminationAllowed) {
return buffer.toString()
}
throw UnterminatedTokenError(type.name, tokenizer.source.currentSourceIndex())
}
buffer.append(char)
}
}
enum class Options {
AllowEofTermination
}
}

View File

@ -9,8 +9,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
ArgumentSpec(symbol, next(TokenType.DotDotDot)) ArgumentSpec(symbol, next(TokenType.DotDotDot))
} }
override fun parseBlock(): Block = guarded(NodeType.Block) { override fun parseBlock(): Block = expect(NodeType.Block, TokenType.LeftCurly) {
expect(TokenType.LeftCurly)
val items = collect(TokenType.RightCurly) { val items = collect(TokenType.RightCurly) {
parseExpression() parseExpression()
} }
@ -18,7 +17,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
Block(items) Block(items)
} }
override fun parseExpression(): Expression = guarded { override fun parseExpression(): Expression {
val token = peek() val token = peek()
var expression = when (token.type) { var expression = when (token.type) {
TokenType.NumberLiteral -> parseNumberLiteral() TokenType.NumberLiteral -> parseNumberLiteral()
@ -66,7 +65,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
} }
} }
if (peek( return if (peek(
TokenType.Plus, TokenType.Minus, TokenType.Multiply, TokenType.Divide, TokenType.Ampersand, TokenType.Plus, TokenType.Minus, TokenType.Multiply, TokenType.Divide, TokenType.Ampersand,
TokenType.Pipe, TokenType.Caret, TokenType.Equality, TokenType.Inequality, TokenType.Mod, TokenType.Pipe, TokenType.Caret, TokenType.Equality, TokenType.Inequality, TokenType.Mod,
TokenType.Rem, TokenType.Lesser, TokenType.Greater, TokenType.LesserEqual, TokenType.GreaterEqual, TokenType.Rem, TokenType.Lesser, TokenType.Greater, TokenType.LesserEqual, TokenType.GreaterEqual,
@ -176,12 +175,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
) )
} }
override fun parseDoubleLiteral(): DoubleLiteral = guarded(NodeType.DoubleLiteral) { override fun parseDoubleLiteral(): DoubleLiteral = expect(NodeType.DoubleLiteral, TokenType.NumberLiteral) {
DoubleLiteral(expect(TokenType.NumberLiteral).text.toDouble()) DoubleLiteral(it.text.toDouble())
} }
override fun parseForIn(): ForIn = guarded(NodeType.ForIn) { override fun parseForIn(): ForIn = expect(NodeType.ForIn, TokenType.For) {
expect(TokenType.For)
val forInItem = parseForInItem() val forInItem = parseForInItem()
expect(TokenType.In) expect(TokenType.In)
val value = parseExpression() val value = parseExpression()
@ -223,8 +221,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
FunctionDefinition(modifiers, name, arguments, block, native) FunctionDefinition(modifiers, name, arguments, block, native)
} }
override fun parseIf(): If = guarded(NodeType.If) { override fun parseIf(): If = expect(NodeType.If, TokenType.If) {
expect(TokenType.If)
val condition = parseExpression() val condition = parseExpression()
val thenBlock = parseBlock() val thenBlock = parseBlock()
var elseBlock: Block? = null var elseBlock: Block? = null
@ -234,8 +231,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
If(condition, thenBlock, elseBlock) If(condition, thenBlock, elseBlock)
} }
override fun parseImportDeclaration(): ImportDeclaration = guarded(NodeType.ImportDeclaration) { override fun parseImportDeclaration(): ImportDeclaration = expect(NodeType.ImportDeclaration, TokenType.Import) {
expect(TokenType.Import)
val form = parseSymbol() val form = parseSymbol()
val components = oneAndContinuedBy(TokenType.Dot) { val components = oneAndContinuedBy(TokenType.Dot) {
parseSymbol() parseSymbol()
@ -257,13 +253,13 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
InfixOperation(parseExpression(), infixOperator, parseExpression()) InfixOperation(parseExpression(), infixOperator, parseExpression())
} }
private fun parseNumberLiteral(): Expression = guarded { private fun parseNumberLiteral(): Expression {
val token = peek() val token = peek()
if (token.type != TokenType.NumberLiteral) { if (token.type != TokenType.NumberLiteral) {
expect(TokenType.NumberLiteral) expect(TokenType.NumberLiteral)
} }
when { return when {
token.text.contains(".") -> parseDoubleLiteral() token.text.contains(".") -> parseDoubleLiteral()
token.text.toIntOrNull() != null -> parseIntegerLiteral() token.text.toIntOrNull() != null -> parseIntegerLiteral()
token.text.toLongOrNull() != null -> parseLongLiteral() token.text.toLongOrNull() != null -> parseLongLiteral()
@ -271,12 +267,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
} }
} }
override fun parseIntegerLiteral(): IntegerLiteral = guarded(NodeType.IntegerLiteral) { override fun parseIntegerLiteral(): IntegerLiteral = expect(NodeType.IntegerLiteral, TokenType.NumberLiteral) {
IntegerLiteral(expect(TokenType.NumberLiteral).text.toInt()) IntegerLiteral(it.text.toInt())
} }
override fun parseLetAssignment(): LetAssignment = guarded(NodeType.LetAssignment) { override fun parseLetAssignment(): LetAssignment = expect(NodeType.LetAssignment, TokenType.Let) {
expect(TokenType.Let)
val symbol = parseSymbol() val symbol = parseSymbol()
expect(TokenType.Equals) expect(TokenType.Equals)
val value = parseExpression() val value = parseExpression()
@ -292,8 +287,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
LetDefinition(definitionModifiers, name, value) LetDefinition(definitionModifiers, name, value)
} }
override fun parseListLiteral(): ListLiteral = guarded(NodeType.ListLiteral) { override fun parseListLiteral(): ListLiteral = expect(NodeType.ListLiteral, TokenType.LeftBracket) {
expect(TokenType.LeftBracket)
val items = collect(TokenType.RightBracket, TokenType.Comma) { val items = collect(TokenType.RightBracket, TokenType.Comma) {
parseExpression() parseExpression()
} }
@ -301,12 +295,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
ListLiteral(items) ListLiteral(items)
} }
override fun parseLongLiteral(): LongLiteral = guarded(NodeType.LongLiteral) { override fun parseLongLiteral(): LongLiteral = expect(NodeType.LongLiteral, TokenType.NumberLiteral) {
LongLiteral(expect(TokenType.NumberLiteral).text.toLong()) LongLiteral(it.text.toLong())
} }
override fun parseNative(): Native = guarded(NodeType.Native) { override fun parseNative(): Native = expect(NodeType.Native, TokenType.Native) {
expect(TokenType.Native)
val form = parseSymbol() val form = parseSymbol()
val definitions = mutableListOf<StringLiteral>() val definitions = mutableListOf<StringLiteral>()
while (peek(TokenType.StringLiteral)) { while (peek(TokenType.StringLiteral)) {
@ -315,22 +308,22 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
Native(form, definitions) Native(form, definitions)
} }
override fun parseNoneLiteral(): NoneLiteral = guarded(NodeType.NoneLiteral) { override fun parseNoneLiteral(): NoneLiteral = expect(NodeType.NoneLiteral, TokenType.None) {
expect(TokenType.None)
NoneLiteral() NoneLiteral()
} }
override fun parseParentheses(): Parentheses = guarded(NodeType.Parentheses) { override fun parseParentheses(): Parentheses = expect(NodeType.Parentheses, TokenType.LeftParentheses) {
expect(TokenType.LeftParentheses)
val expression = parseExpression() val expression = parseExpression()
expect(TokenType.RightParentheses) expect(TokenType.RightParentheses)
Parentheses(expression) Parentheses(expression)
} }
override fun parsePrefixOperation(): PrefixOperation = guarded(NodeType.PrefixOperation) { override fun parsePrefixOperation(): PrefixOperation = expect(
expect(TokenType.Not, TokenType.Plus, TokenType.Minus, TokenType.Tilde) { NodeType.PrefixOperation,
PrefixOperation(ParserHelpers.convertPrefixOperator(it), parseExpression()) TokenType.Not, TokenType.Plus,
} TokenType.Minus, TokenType.Tilde
) {
PrefixOperation(ParserHelpers.convertPrefixOperator(it), parseExpression())
} }
override fun parseSetAssignment(): SetAssignment = guarded(NodeType.SetAssignment) { override fun parseSetAssignment(): SetAssignment = guarded(NodeType.SetAssignment) {
@ -340,11 +333,9 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
SetAssignment(symbol, value) SetAssignment(symbol, value)
} }
override fun parseStringLiteral(): StringLiteral = guarded(NodeType.StringLiteral) { override fun parseStringLiteral(): StringLiteral = expect(NodeType.StringLiteral, TokenType.StringLiteral) {
expect(TokenType.StringLiteral) { val content = StringEscape.unescape(StringEscape.unquote(it.text))
val content = StringEscape.unescape(StringEscape.unquote(it.text)) StringLiteral(content)
StringLiteral(content)
}
} }
override fun parseSuffixOperation(): SuffixOperation = guarded(NodeType.SuffixOperation) { override fun parseSuffixOperation(): SuffixOperation = guarded(NodeType.SuffixOperation) {
@ -354,32 +345,30 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
} }
} }
private fun parseSymbolCases(): Expression = guarded { private fun parseSymbolCases(): Expression {
if (peek(1, TokenType.LeftParentheses)) { return if (peek(1, TokenType.LeftParentheses)) {
parseFunctionCall() parseFunctionCall()
} else if (peek(1, TokenType.PlusPlus, TokenType.MinusMinus)) { } else if (peek(1, TokenType.PlusPlus, TokenType.MinusMinus)) {
parseSuffixOperation() parseSuffixOperation()
} else parseSymbolReference() } else parseSymbolReference()
} }
override fun parseSymbol(): Symbol = guarded(NodeType.Symbol) { override fun parseSymbol(): Symbol = expect(NodeType.Symbol, TokenType.Symbol) {
expect(TokenType.Symbol) { Symbol(it.text) } Symbol(it.text)
} }
override fun parseSymbolReference(): SymbolReference = guarded(NodeType.SymbolReference) { override fun parseSymbolReference(): SymbolReference = guarded(NodeType.SymbolReference) {
SymbolReference(parseSymbol()) SymbolReference(parseSymbol())
} }
override fun parseVarAssignment(): VarAssignment = guarded(NodeType.VarAssignment) { override fun parseVarAssignment(): VarAssignment = expect(NodeType.VarAssignment, TokenType.Var) {
expect(TokenType.Var)
val symbol = parseSymbol() val symbol = parseSymbol()
expect(TokenType.Equals) expect(TokenType.Equals)
val value = parseExpression() val value = parseExpression()
VarAssignment(symbol, value) VarAssignment(symbol, value)
} }
override fun parseWhile(): While = guarded(NodeType.While) { override fun parseWhile(): While = expect(NodeType.While, TokenType.While) {
expect(TokenType.While)
val condition = parseExpression() val condition = parseExpression()
val block = parseBlock() val block = parseBlock()
While(condition, block) While(condition, block)

View File

@ -7,6 +7,14 @@ import gay.pizza.pork.ast.gen.visit
data class ParserAttributes(val tokens: List<Token>) { data class ParserAttributes(val tokens: List<Token>) {
companion object { companion object {
fun recallOwnedTokens(node: Node): List<Token> {
val attributes = node.data<ParserAttributes>()
if (attributes != null) {
return attributes.tokens
}
return emptyList()
}
fun recallAllTokens(node: Node): List<Token> { fun recallAllTokens(node: Node): List<Token> {
val all = mutableListOf<Token>() val all = mutableListOf<Token>()
val coalescer = NodeCoalescer { item -> val coalescer = NodeCoalescer { item ->

View File

@ -11,6 +11,12 @@ abstract class ParserBase(source: TokenSource, val attribution: NodeAttribution)
protected inline fun <T: Node> guarded(type: NodeType? = null, noinline block: () -> T): T = protected inline fun <T: Node> guarded(type: NodeType? = null, noinline block: () -> T): T =
attribution.guarded(type, block) attribution.guarded(type, block)
@Suppress("NOTHING_TO_INLINE")
protected inline fun <T: Node> expect(type: NodeType? = null, vararg tokenTypes: TokenType, noinline block: (Token) -> T): T =
guarded(type) {
block(expect(*tokenTypes))
}
protected fun <T> collect( protected fun <T> collect(
peeking: TokenType, peeking: TokenType,
consuming: TokenType? = null, consuming: TokenType? = null,
@ -66,7 +72,11 @@ abstract class ParserBase(source: TokenSource, val attribution: NodeAttribution)
throw ExpectedTokenError(token, token.sourceIndex, *types) throw ExpectedTokenError(token, token.sourceIndex, *types)
} }
protected fun next(): Token = source.next() protected fun next(): Token {
val token = source.next()
attribution.push(token)
return token
}
protected fun peek(): Token = source.peek() protected fun peek(): Token = source.peek()
protected fun peek(ahead: Int): TokenType = source.peekTypeAhead(ahead) protected fun peek(ahead: Int): TokenType = source.peekTypeAhead(ahead)
protected fun peek(ahead: Int, vararg types: TokenType): Boolean = types.contains(source.peekTypeAhead(ahead)) protected fun peek(ahead: Int, vararg types: TokenType): Boolean = types.contains(source.peekTypeAhead(ahead))

View File

@ -23,7 +23,7 @@ internal object ParserHelpers {
TokenType.GreaterEqual -> InfixOperator.GreaterEqual TokenType.GreaterEqual -> InfixOperator.GreaterEqual
TokenType.And -> InfixOperator.BooleanAnd TokenType.And -> InfixOperator.BooleanAnd
TokenType.Or -> InfixOperator.BooleanOr TokenType.Or -> InfixOperator.BooleanOr
else -> throw ParseError("Unknown Infix Operator") else -> throw ParseError("Unknown infix operator: ${token.type}")
} }
fun convertPrefixOperator(token: Token): PrefixOperator = when (token.type) { fun convertPrefixOperator(token: Token): PrefixOperator = when (token.type) {
@ -31,12 +31,12 @@ internal object ParserHelpers {
TokenType.Plus -> PrefixOperator.UnaryPlus TokenType.Plus -> PrefixOperator.UnaryPlus
TokenType.Minus -> PrefixOperator.UnaryMinus TokenType.Minus -> PrefixOperator.UnaryMinus
TokenType.Tilde -> PrefixOperator.BinaryNot TokenType.Tilde -> PrefixOperator.BinaryNot
else -> throw ParseError("Unknown Prefix Operator") else -> throw ParseError("Unknown prefix operator: ${token.type}")
} }
fun convertSuffixOperator(token: Token): SuffixOperator = when (token.type) { fun convertSuffixOperator(token: Token): SuffixOperator = when (token.type) {
TokenType.PlusPlus -> SuffixOperator.Increment TokenType.PlusPlus -> SuffixOperator.Increment
TokenType.MinusMinus -> SuffixOperator.Decrement TokenType.MinusMinus -> SuffixOperator.Decrement
else -> throw ParseError("Unknown Suffix Operator") else -> throw ParseError("Unknown suffix operator: ${token.type}")
} }
} }

View File

@ -1,10 +1,10 @@
package gay.pizza.pork.parser package gay.pizza.pork.parser
import gay.pizza.pork.ast.gen.* import gay.pizza.pork.ast.gen.*
import gay.pizza.pork.common.IndentPrinter import gay.pizza.pork.common.IndentBuffer
class Printer(buffer: StringBuilder) : NodeVisitor<Unit> { class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
private val out = IndentPrinter(buffer) private val out = IndentBuffer(buffer)
private var autoIndentState = false private var autoIndentState = false
private fun append(text: String) { private fun append(text: String) {

View File

@ -0,0 +1,19 @@
package gay.pizza.pork.parser
class SourceIndexCharSource(val delegate: CharSource) : CharSource by delegate {
private var currentLineIndex = 1
private var currentLineColumn = 0
override fun next(): Char {
val char = delegate.next()
if (char == '\n') {
currentLineIndex++
currentLineColumn = 0
}
currentLineColumn++
return char
}
fun currentSourceIndex(): SourceIndex =
SourceIndex(delegate.currentIndex, currentLineIndex, currentLineColumn)
}

View File

@ -0,0 +1,29 @@
package gay.pizza.pork.parser
object StringCharConsumer : CharConsumer {
override fun consume(type: TokenType, tokenizer: Tokenizer): String? {
if (!tokenizer.peek("\"")) {
return null
}
val buffer = StringBuilder()
buffer.append(tokenizer.source.next())
var escape = false
while (true) {
val char = tokenizer.source.peek()
if (char == CharSource.EndOfFile) {
throw UnterminatedTokenError("String", tokenizer.source.currentSourceIndex())
}
buffer.append(tokenizer.source.next())
if (char == '\\') {
escape = true
} else if (char == '"' && !escape) {
break
}
}
return buffer.toString()
}
}

View File

@ -6,12 +6,13 @@ class StringCharSource(
val endIndex: Int = input.length - 1 val endIndex: Int = input.length - 1
) : CharSource { ) : CharSource {
private var index = startIndex private var index = startIndex
override val currentIndex: Int override val currentIndex: Int
get() = index get() = index
override fun next(): Char { override fun next(): Char {
if (index == endIndex) { if (index == endIndex) {
return CharSource.NullChar return CharSource.EndOfFile
} }
val char = input[index] val char = input[index]
index++ index++
@ -20,8 +21,16 @@ class StringCharSource(
override fun peek(): Char { override fun peek(): Char {
if (index == endIndex) { if (index == endIndex) {
return CharSource.NullChar return CharSource.EndOfFile
} }
return input[index] return input[index]
} }
override fun peek(index: Int): Char {
val target = this.index + index
if (target >= endIndex) {
return CharSource.EndOfFile
}
return input[target]
}
} }

View File

@ -1,22 +1,23 @@
package gay.pizza.pork.parser package gay.pizza.pork.parser
import gay.pizza.pork.parser.CharMatcher.* import gay.pizza.pork.parser.CharMatcher.*
import gay.pizza.pork.parser.MatchedCharConsumer.Options.AllowEofTermination
import gay.pizza.pork.parser.TokenTypeProperty.* import gay.pizza.pork.parser.TokenTypeProperty.*
import gay.pizza.pork.parser.TokenFamily.* import gay.pizza.pork.parser.TokenFamily.*
import gay.pizza.pork.parser.TokenTypeProperty.AnyOf import gay.pizza.pork.parser.TokenTypeProperty.AnyOf
enum class TokenType(vararg properties: TokenTypeProperty) { enum class TokenType(vararg properties: TokenTypeProperty) {
NumberLiteral(NumericLiteralFamily, CharConsumer(CharMatcher.AnyOf( NumberLiteral(NumericLiteralFamily, CharMatch(CharMatcher.AnyOf(
MatchRange('0'..'9'), MatchRange('0'..'9'),
NotAtIndex(0, MatchSingle('.')) NotAtIndex(0, MatchSingle('.'))
))), ))),
Symbol(SymbolFamily, CharConsumer(CharMatcher.AnyOf( Symbol(SymbolFamily, CharMatch(CharMatcher.AnyOf(
MatchRange('a'..'z'), MatchRange('a'..'z'),
MatchRange('A'..'Z'), MatchRange('A'..'Z'),
MatchRange('0' .. '9'), MatchRange('0' .. '9'),
MatchSingle('_') MatchSingle('_')
)), KeywordUpgrader), )), KeywordUpgrader),
StringLiteral(StringLiteralFamily), StringLiteral(StringLiteralFamily, CharConsume(StringCharConsumer)),
Equality(OperatorFamily), Equality(OperatorFamily),
Inequality(ManyChars("!="), OperatorFamily), Inequality(ManyChars("!="), OperatorFamily),
ExclamationPoint(SingleChar('!'), Promotion('=', Inequality)), ExclamationPoint(SingleChar('!'), Promotion('=', Inequality)),
@ -66,14 +67,14 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
Native(ManyChars("native"), KeywordFamily), Native(ManyChars("native"), KeywordFamily),
Let(ManyChars("let"), KeywordFamily), Let(ManyChars("let"), KeywordFamily),
Var(ManyChars("var"), KeywordFamily), Var(ManyChars("var"), KeywordFamily),
Whitespace(CharConsumer(CharMatcher.AnyOf( Whitespace(CharMatch(CharMatcher.AnyOf(
MatchSingle(' '), MatchSingle(' '),
MatchSingle('\r'), MatchSingle('\r'),
MatchSingle('\n'), MatchSingle('\n'),
MatchSingle('\t') MatchSingle('\t')
))), ))),
BlockComment(CommentFamily), BlockComment(CharConsume(MatchedCharConsumer("/*", "*/")), CommentFamily),
LineComment(CommentFamily), LineComment(CharConsume(MatchedCharConsumer("//", "\n", AllowEofTermination)), CommentFamily),
EndOfFile; EndOfFile;
val promotions: List<Promotion> = val promotions: List<Promotion> =
@ -86,7 +87,8 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
properties.filterIsInstance<SingleChar>().singleOrNull() properties.filterIsInstance<SingleChar>().singleOrNull()
val family: TokenFamily = val family: TokenFamily =
properties.filterIsInstance<TokenFamily>().singleOrNull() ?: OtherFamily properties.filterIsInstance<TokenFamily>().singleOrNull() ?: OtherFamily
val charConsumer: CharConsumer? = properties.filterIsInstance<CharConsumer>().singleOrNull() val charMatch: CharMatch? = properties.filterIsInstance<CharMatch>().singleOrNull()
val charConsume: CharConsume? = properties.filterIsInstance<CharConsume>().singleOrNull()
val tokenUpgrader: TokenUpgrader? = val tokenUpgrader: TokenUpgrader? =
properties.filterIsInstance<TokenUpgrader>().singleOrNull() properties.filterIsInstance<TokenUpgrader>().singleOrNull()
@ -96,8 +98,8 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
val AnyOf = entries.filter { item -> item.anyOf != null } val AnyOf = entries.filter { item -> item.anyOf != null }
val ManyChars = entries.filter { item -> item.manyChars != null } val ManyChars = entries.filter { item -> item.manyChars != null }
val SingleChars = entries.filter { item -> item.singleChar != null } val SingleChars = entries.filter { item -> item.singleChar != null }
val CharConsumers = entries.filter { item -> val CharMatches = entries.filter { item -> item.charMatch != null }
item.charConsumer != null } val CharConsumes = entries.filter { item -> item.charConsume != null }
val ParserIgnoredTypes: Array<TokenType> = arrayOf( val ParserIgnoredTypes: Array<TokenType> = arrayOf(
Whitespace, Whitespace,

View File

@ -5,7 +5,8 @@ interface TokenTypeProperty {
class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty
class ManyChars(val text: String) : TokenTypeProperty class ManyChars(val text: String) : TokenTypeProperty
class AnyOf(vararg val strings: String): TokenTypeProperty class AnyOf(vararg val strings: String): TokenTypeProperty
open class CharConsumer(val matcher: CharMatcher) : TokenTypeProperty open class CharMatch(val matcher: CharMatcher) : TokenTypeProperty
open class CharConsume(val consumer: CharConsumer) : TokenTypeProperty
open class TokenUpgrader(val maybeUpgrade: (Token) -> Token?) : TokenTypeProperty open class TokenUpgrader(val maybeUpgrade: (Token) -> Token?) : TokenTypeProperty
object KeywordUpgrader : TokenUpgrader({ token -> object KeywordUpgrader : TokenUpgrader({ token ->

View File

@ -1,80 +1,22 @@
package gay.pizza.pork.parser package gay.pizza.pork.parser
class Tokenizer(val source: CharSource) { class Tokenizer(source: CharSource) {
val source: SourceIndexCharSource = SourceIndexCharSource(source)
private var startIndex: SourceIndex = SourceIndex.zero() private var startIndex: SourceIndex = SourceIndex.zero()
private var currentLineIndex = 1
private var currentLineColumn = 0
private fun readBlockComment(firstChar: Char): Token {
val comment = buildString {
append(firstChar)
var endOfComment = false
while (true) {
val char = nextChar()
if (char == CharSource.NullChar) {
throw UnterminatedTokenError("block comment", currentSourceIndex())
}
append(char)
if (endOfComment) {
if (char != '/') {
endOfComment = false
continue
}
break
}
if (char == '*') {
endOfComment = true
}
}
}
return produceToken(TokenType.BlockComment, comment)
}
private fun readLineComment(firstChar: Char): Token {
val comment = buildString {
append(firstChar)
while (true) {
val char = source.peek()
if (char == CharSource.NullChar || char == '\n') {
break
}
append(nextChar())
}
}
return produceToken(TokenType.LineComment, comment)
}
private fun readStringLiteral(firstChar: Char): Token {
val string = buildString {
append(firstChar)
while (true) {
val char = source.peek()
if (char == CharSource.NullChar) {
throw UnterminatedTokenError("string", currentSourceIndex())
}
append(nextChar())
if (char == '"') {
break
}
}
}
return produceToken(TokenType.StringLiteral, string)
}
fun next(): Token { fun next(): Token {
while (source.peek() != CharSource.NullChar) { while (source.peek() != CharSource.EndOfFile) {
startIndex = currentSourceIndex() startIndex = source.currentSourceIndex()
val char = nextChar()
if (char == '/' && source.peek() == '*') { for (item in TokenType.CharConsumes) {
return readBlockComment(char) val text = item.charConsume!!.consumer.consume(item, this)
if (text != null) {
return produceToken(item, text)
}
} }
if (char == '/' && source.peek() == '/') { val char = source.next()
return readLineComment(char)
}
for (item in TokenType.SingleChars) { for (item in TokenType.SingleChars) {
val itemChar = item.singleChar!!.char val itemChar = item.singleChar!!.char
@ -91,7 +33,7 @@ class Tokenizer(val source: CharSource) {
if (source.peek() != promotion.nextChar) { if (source.peek() != promotion.nextChar) {
continue continue
} }
val nextChar = nextChar() val nextChar = source.next()
type = promotion.type type = promotion.type
text += nextChar text += nextChar
promoted = true promoted = true
@ -101,16 +43,16 @@ class Tokenizer(val source: CharSource) {
} }
var index = 0 var index = 0
for (item in TokenType.CharConsumers) { for (item in TokenType.CharMatches) {
if (!item.charConsumer!!.matcher.valid(char, index)) { if (!item.charMatch!!.matcher.valid(char, index)) {
continue continue
} }
val text = buildString { val text = buildString {
append(char) append(char)
while (item.charConsumer.matcher.valid(source.peek(), ++index)) { while (item.charMatch.matcher.valid(source.peek(), ++index)) {
append(nextChar()) append(source.next())
} }
} }
var token = produceToken(item, text) var token = produceToken(item, text)
@ -121,10 +63,6 @@ class Tokenizer(val source: CharSource) {
return token return token
} }
if (char == '"') {
return readStringLiteral(char)
}
throw BadCharacterError(char, startIndex) throw BadCharacterError(char, startIndex)
} }
return Token.endOfFile(startIndex.copy(index = source.currentIndex)) return Token.endOfFile(startIndex.copy(index = source.currentIndex))
@ -142,19 +80,23 @@ class Tokenizer(val source: CharSource) {
return TokenStream(tokens) return TokenStream(tokens)
} }
private fun produceToken(type: TokenType, text: String) = internal fun produceToken(type: TokenType, text: String) =
Token(type, startIndex, text) Token(type, startIndex, text)
private fun nextChar(): Char { internal fun peek(what: CharSequence): Boolean {
val char = source.next() var current = 0
if (char == '\n') { for (c in what) {
currentLineIndex++ if (source.peek(current) != c) {
currentLineColumn = 0 return false
}
current++
} }
currentLineColumn++ return true
return char
} }
private fun currentSourceIndex(): SourceIndex = internal fun read(count: Int, buffer: StringBuilder) {
SourceIndex(source.currentIndex, currentLineIndex, currentLineColumn) for (i in 1..count) {
buffer.append(source.next())
}
}
} }

View File

@ -8,6 +8,8 @@ plugins {
dependencies { dependencies {
api(project(":minimal")) api(project(":minimal"))
api("com.github.ajalt.clikt:clikt:4.2.0") api("com.github.ajalt.clikt:clikt:4.2.0")
implementation(project(":common"))
} }
application { application {

View File

@ -2,14 +2,19 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import gay.pizza.dough.fs.PlatformFsProvider import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.ast.gen.Node
import gay.pizza.pork.ast.gen.NodeCoalescer import gay.pizza.pork.ast.gen.NodeCoalescer
import gay.pizza.pork.ast.gen.visit import gay.pizza.pork.ast.gen.visit
import gay.pizza.pork.common.IndentPrinter
import gay.pizza.pork.minimal.FileTool import gay.pizza.pork.minimal.FileTool
import gay.pizza.pork.parser.ParserAttributes import gay.pizza.pork.parser.ParserAttributes
import gay.pizza.pork.parser.ParserNodeAttribution import gay.pizza.pork.parser.ParserNodeAttribution
class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute") { class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute") {
val hierarchical by option("--hierarchical", help = "Print Hierarchical Output").flag(default = true)
val path by argument("file") val path by argument("file")
override fun run() { override fun run() {
@ -17,13 +22,27 @@ class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute"
val attribution = ParserNodeAttribution() val attribution = ParserNodeAttribution()
val compilationUnit = tool.parse(attribution) val compilationUnit = tool.parse(attribution)
val coalescer = NodeCoalescer { node -> if (hierarchical) {
val allTokens = ParserAttributes.recallAllTokens(node) val output = IndentPrinter()
println("node ${node.type.name}") fun visit(node: Node) {
for (token in allTokens) { output.emitIndentedLine("${node.type.name} ->")
println("token $token") output.indented {
for (token in ParserAttributes.recallOwnedTokens(node)) {
output.emitIndentedLine(token.toString())
}
node.visitChildren(NodeCoalescer(followChildren = false, handler = ::visit))
}
} }
visit(compilationUnit)
} else {
val coalescer = NodeCoalescer { node ->
val allTokens = ParserAttributes.recallAllTokens(node)
println("node ${node.type.name}")
for (token in allTokens) {
println("token $token")
}
}
coalescer.visit(compilationUnit)
} }
coalescer.visit(compilationUnit)
} }
} }