String literal support.

This commit is contained in:
Alex Zenla 2023-08-22 21:58:56 -07:00
parent c418694307
commit ddde99f38d
Signed by: alex
GPG Key ID: C0780728420EBFE5
15 changed files with 84 additions and 17 deletions

View File

@ -49,4 +49,5 @@ results = [
notEqual(5, 6) notEqual(5, 6)
] ]
println("results:")
println(results) println(results)

View File

@ -12,6 +12,7 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
override fun visitIntLiteral(node: IntLiteral) = handler(node) override fun visitIntLiteral(node: IntLiteral) = handler(node)
override fun visitBooleanLiteral(node: BooleanLiteral) = handler(node) override fun visitBooleanLiteral(node: BooleanLiteral) = handler(node)
override fun visitListLiteral(node: ListLiteral) = handler(node) override fun visitListLiteral(node: ListLiteral) = handler(node)
override fun visitStringLiteral(node: StringLiteral) = handler(node)
override fun visitParentheses(node: Parentheses) = handler(node) override fun visitParentheses(node: Parentheses) = handler(node)
override fun visitPrefixOperation(node: PrefixOperation) = handler(node) override fun visitPrefixOperation(node: PrefixOperation) = handler(node)
override fun visitInfixOperation(node: InfixOperation) = handler(node) override fun visitInfixOperation(node: InfixOperation) = handler(node)

View File

@ -1,20 +1,19 @@
package gay.pizza.pork.ast package gay.pizza.pork.ast
import gay.pizza.pork.ast.NodeTypeTrait.* enum class NodeType(val parent: NodeType? = null) {
enum class NodeType(val parent: NodeType? = null, vararg traits: NodeTypeTrait) {
Node, Node,
Symbol(Node), Symbol(Node),
Expression(Node, Intermediate), Expression(Node),
Program(Node), Program(Node),
IntLiteral(Expression, Literal), IntLiteral(Expression),
BooleanLiteral(Expression, Literal), BooleanLiteral(Expression),
ListLiteral(Expression, Literal), ListLiteral(Expression),
StringLiteral(Expression),
Parentheses(Expression), Parentheses(Expression),
Define(Expression), Define(Expression),
Lambda(Expression), Lambda(Expression),
PrefixOperation(Expression, Operation), PrefixOperation(Expression),
InfixOperation(Expression, Operation), InfixOperation(Expression),
SymbolReference(Expression), SymbolReference(Expression),
FunctionCall(Expression), FunctionCall(Expression),
If(Expression); If(Expression);

View File

@ -13,6 +13,7 @@ interface NodeVisitor<T> {
fun visitIntLiteral(node: IntLiteral): T fun visitIntLiteral(node: IntLiteral): T
fun visitBooleanLiteral(node: BooleanLiteral): T fun visitBooleanLiteral(node: BooleanLiteral): T
fun visitListLiteral(node: ListLiteral): T fun visitListLiteral(node: ListLiteral): T
fun visitStringLiteral(node: StringLiteral): T
fun visitParentheses(node: Parentheses): T fun visitParentheses(node: Parentheses): T
fun visitPrefixOperation(node: PrefixOperation): T fun visitPrefixOperation(node: PrefixOperation): T
@ -24,6 +25,7 @@ interface NodeVisitor<T> {
is IntLiteral -> visitIntLiteral(node) is IntLiteral -> visitIntLiteral(node)
is BooleanLiteral -> visitBooleanLiteral(node) is BooleanLiteral -> visitBooleanLiteral(node)
is ListLiteral -> visitListLiteral(node) is ListLiteral -> visitListLiteral(node)
is StringLiteral -> visitStringLiteral(node)
is Parentheses -> visitParentheses(node) is Parentheses -> visitParentheses(node)
is InfixOperation -> visitInfixOperation(node) is InfixOperation -> visitInfixOperation(node)
is PrefixOperation -> visitPrefixOperation(node) is PrefixOperation -> visitPrefixOperation(node)
@ -32,14 +34,12 @@ interface NodeVisitor<T> {
is FunctionCall -> visitFunctionCall(node) is FunctionCall -> visitFunctionCall(node)
is SymbolReference -> visitReference(node) is SymbolReference -> visitReference(node)
is If -> visitIf(node) is If -> visitIf(node)
else -> throw RuntimeException("Unknown Expression")
} }
fun visit(node: Node): T = when (node) { fun visit(node: Node): T = when (node) {
is Expression -> visitExpression(node) is Expression -> visitExpression(node)
is Symbol -> visitSymbol(node) is Symbol -> visitSymbol(node)
is Program -> visitProgram(node) is Program -> visitProgram(node)
else -> throw RuntimeException("Unknown Node")
} }
fun visitNodes(vararg nodes: Node?): List<T> = fun visitNodes(vararg nodes: Node?): List<T> =

View File

@ -1,6 +1,7 @@
package gay.pizza.pork.ast package gay.pizza.pork.ast
import gay.pizza.pork.ast.nodes.* import gay.pizza.pork.ast.nodes.*
import gay.pizza.pork.util.StringEscape
class Printer(private val buffer: StringBuilder) : NodeVisitor<Unit> { class Printer(private val buffer: StringBuilder) : NodeVisitor<Unit> {
private var indent = 0 private var indent = 0
@ -109,6 +110,12 @@ class Printer(private val buffer: StringBuilder) : NodeVisitor<Unit> {
append("]") append("]")
} }
override fun visitStringLiteral(node: StringLiteral) {
append("\"")
append(StringEscape.escape(node.text))
append("\"")
}
override fun visitParentheses(node: Parentheses) { override fun visitParentheses(node: Parentheses) {
append("(") append("(")
visit(node.expression) visit(node.expression)

View File

@ -0,0 +1,18 @@
package gay.pizza.pork.ast.nodes
import gay.pizza.pork.ast.NodeType
class StringLiteral(val text: String) : Expression() {
override val type: NodeType = NodeType.StringLiteral
override fun equals(other: Any?): Boolean {
if (other !is StringLiteral) return false
return other.text == text
}
override fun hashCode(): Int {
var result = text.hashCode()
result = 31 * result + type.hashCode()
return result
}
}

View File

@ -2,6 +2,7 @@ package gay.pizza.pork.compiler
import gay.pizza.pork.ast.* import gay.pizza.pork.ast.*
import gay.pizza.pork.ast.nodes.* import gay.pizza.pork.ast.nodes.*
import gay.pizza.pork.util.StringEscape
class KotlinCompiler : NodeVisitor<String> { class KotlinCompiler : NodeVisitor<String> {
override fun visitDefine(node: Define): String = override fun visitDefine(node: Define): String =
@ -55,6 +56,9 @@ class KotlinCompiler : NodeVisitor<String> {
append(")") append(")")
} }
override fun visitStringLiteral(node: StringLiteral): String =
"\"" + StringEscape.escape(node.text) + "\""
override fun visitParentheses(node: Parentheses): String = override fun visitParentheses(node: Parentheses): String =
"(${visit(node.expression)})" "(${visit(node.expression)})"

View File

@ -1,7 +1,3 @@
package gay.pizza.pork.eval package gay.pizza.pork.eval
class Arguments(val values: List<Any>) { class Arguments(val values: List<Any>)
companion object {
val Zero = Arguments(emptyList())
}
}

View File

@ -58,6 +58,7 @@ class Evaluator(root: Scope) : NodeVisitor<Any> {
override fun visitIntLiteral(node: IntLiteral): Any = node.value override fun visitIntLiteral(node: IntLiteral): Any = node.value
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value
override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) } override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) }
override fun visitStringLiteral(node: StringLiteral): Any = node.text
override fun visitParentheses(node: Parentheses): Any = visit(node.expression) override fun visitParentheses(node: Parentheses): Any = visit(node.expression)

View File

@ -3,6 +3,7 @@ package gay.pizza.pork.parse
open class AnsiHighlightScheme : HighlightScheme { open class AnsiHighlightScheme : HighlightScheme {
override fun highlight(token: Token): Highlight { override fun highlight(token: Token): Highlight {
val attributes = when (token.type.family) { val attributes = when (token.type.family) {
TokenFamily.StringLiteralFamily -> string()
TokenFamily.OperatorFamily -> operator() TokenFamily.OperatorFamily -> operator()
TokenFamily.KeywordFamily -> keyword() TokenFamily.KeywordFamily -> keyword()
TokenFamily.SymbolFamily -> symbol() TokenFamily.SymbolFamily -> symbol()
@ -15,8 +16,9 @@ open class AnsiHighlightScheme : HighlightScheme {
} else Highlight(token) } else Highlight(token)
} }
open fun operator(): AnsiAttributes = AnsiAttributes("32m") open fun string(): AnsiAttributes = AnsiAttributes("32m")
open fun symbol(): AnsiAttributes = AnsiAttributes("33m") open fun symbol(): AnsiAttributes = AnsiAttributes("33m")
open fun operator(): AnsiAttributes = AnsiAttributes("34m")
open fun keyword(): AnsiAttributes = AnsiAttributes("35m") open fun keyword(): AnsiAttributes = AnsiAttributes("35m")
open fun comment(): AnsiAttributes = AnsiAttributes("37m") open fun comment(): AnsiAttributes = AnsiAttributes("37m")

View File

@ -1,6 +1,7 @@
package gay.pizza.pork.parse package gay.pizza.pork.parse
import gay.pizza.pork.ast.nodes.* import gay.pizza.pork.ast.nodes.*
import gay.pizza.pork.util.StringEscape
class Parser(source: PeekableSource<Token>) { class Parser(source: PeekableSource<Token>) {
private val unsanitizedSource = source private val unsanitizedSource = source
@ -65,6 +66,11 @@ class Parser(source: PeekableSource<Token>) {
fun readExpression(): Expression { fun readExpression(): Expression {
val token = peek() val token = peek()
val expression = when (token.type) { val expression = when (token.type) {
TokenType.StringLiteral -> {
expect(TokenType.StringLiteral)
return StringLiteral(StringEscape.unescape(StringEscape.unquote(token.text)))
}
TokenType.IntLiteral -> { TokenType.IntLiteral -> {
readIntLiteral() readIntLiteral()
} }

View File

@ -5,6 +5,7 @@ enum class TokenFamily : TokenTypeProperty {
KeywordFamily, KeywordFamily,
SymbolFamily, SymbolFamily,
NumericLiteralFamily, NumericLiteralFamily,
StringLiteralFamily,
CommentFamily, CommentFamily,
OtherFamily OtherFamily
} }

View File

@ -6,6 +6,7 @@ import gay.pizza.pork.parse.TokenFamily.*
enum class TokenType(vararg properties: TokenTypeProperty) { enum class TokenType(vararg properties: TokenTypeProperty) {
Symbol(SymbolFamily), Symbol(SymbolFamily),
IntLiteral(NumericLiteralFamily), IntLiteral(NumericLiteralFamily),
StringLiteral(StringLiteralFamily),
Equality(OperatorFamily), Equality(OperatorFamily),
Inequality(OperatorFamily), Inequality(OperatorFamily),
Equals(SingleChar('='), Promotion('=', Equality)), Equals(SingleChar('='), Promotion('=', Equality)),

View File

@ -89,6 +89,23 @@ class Tokenizer(val source: CharSource) {
return Token(TokenType.LineComment, tokenStart, comment) return Token(TokenType.LineComment, tokenStart, comment)
} }
private fun readStringLiteral(firstChar: Char): Token {
val string = buildString {
append(firstChar)
while (true) {
val char = source.peek()
if (char == CharSource.NullChar) {
throw RuntimeException("Unterminated string.")
}
append(source.next())
if (char == '"') {
break
}
}
}
return Token(TokenType.StringLiteral, tokenStart, string)
}
fun next(): Token { fun next(): Token {
while (source.peek() != CharSource.NullChar) { while (source.peek() != CharSource.NullChar) {
tokenStart = source.currentIndex tokenStart = source.currentIndex
@ -132,6 +149,11 @@ class Tokenizer(val source: CharSource) {
if (isSymbol(char)) { if (isSymbol(char)) {
return readSymbolOrKeyword(char) return readSymbolOrKeyword(char)
} }
if (char == '"') {
return readStringLiteral(char)
}
throw RuntimeException("Failed to parse: (${char}) next ${source.peek()}") throw RuntimeException("Failed to parse: (${char}) next ${source.peek()}")
} }
return Token.endOfFile(source.currentIndex) return Token.endOfFile(source.currentIndex)

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.util
object StringEscape {
fun escape(input: String): String = input.replace("\n", "\\n")
fun unescape(input: String): String = input.replace("\\n", "\n")
fun unquote(input: String): String = input.substring(1, input.length - 1)
}