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)
]
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 visitBooleanLiteral(node: BooleanLiteral) = 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 visitPrefixOperation(node: PrefixOperation) = handler(node)
override fun visitInfixOperation(node: InfixOperation) = handler(node)

View File

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

View File

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

View File

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

View File

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

View File

@ -58,6 +58,7 @@ class Evaluator(root: Scope) : NodeVisitor<Any> {
override fun visitIntLiteral(node: IntLiteral): Any = node.value
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value
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)

View File

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

View File

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

View File

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

View File

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

View File

@ -89,6 +89,23 @@ class Tokenizer(val source: CharSource) {
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 {
while (source.peek() != CharSource.NullChar) {
tokenStart = source.currentIndex
@ -132,6 +149,11 @@ class Tokenizer(val source: CharSource) {
if (isSymbol(char)) {
return readSymbolOrKeyword(char)
}
if (char == '"') {
return readStringLiteral(char)
}
throw RuntimeException("Failed to parse: (${char}) next ${source.peek()}")
}
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)
}