diff --git a/examples/syntax.pork b/examples/syntax.pork index 4f9d27c..f186372 100644 --- a/examples/syntax.pork +++ b/examples/syntax.pork @@ -49,4 +49,5 @@ results = [ notEqual(5, 6) ] +println("results:") println(results) diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt index 8fad95b..64ee30c 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt @@ -12,6 +12,7 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { 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) diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt index b1dae8d..6a2e66d 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -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); diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt index 71f3bde..68e2cc8 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt @@ -13,6 +13,7 @@ interface NodeVisitor { 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 { 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 { 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 = diff --git a/src/main/kotlin/gay/pizza/pork/ast/Printer.kt b/src/main/kotlin/gay/pizza/pork/ast/Printer.kt index 1022f46..d979114 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Printer.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Printer.kt @@ -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 { private var indent = 0 @@ -109,6 +110,12 @@ class Printer(private val buffer: StringBuilder) : NodeVisitor { append("]") } + override fun visitStringLiteral(node: StringLiteral) { + append("\"") + append(StringEscape.escape(node.text)) + append("\"") + } + override fun visitParentheses(node: Parentheses) { append("(") visit(node.expression) diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt new file mode 100644 index 0000000..7c3eba2 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt @@ -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 + } +} diff --git a/src/main/kotlin/gay/pizza/pork/compiler/KotlinCompiler.kt b/src/main/kotlin/gay/pizza/pork/compiler/KotlinCompiler.kt index 1b2dd81..d3fa4a6 100644 --- a/src/main/kotlin/gay/pizza/pork/compiler/KotlinCompiler.kt +++ b/src/main/kotlin/gay/pizza/pork/compiler/KotlinCompiler.kt @@ -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 { override fun visitDefine(node: Define): String = @@ -55,6 +56,9 @@ class KotlinCompiler : NodeVisitor { append(")") } + override fun visitStringLiteral(node: StringLiteral): String = + "\"" + StringEscape.escape(node.text) + "\"" + override fun visitParentheses(node: Parentheses): String = "(${visit(node.expression)})" diff --git a/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt b/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt index b9cc82c..28d48e1 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt @@ -1,7 +1,3 @@ package gay.pizza.pork.eval -class Arguments(val values: List) { - companion object { - val Zero = Arguments(emptyList()) - } -} +class Arguments(val values: List) diff --git a/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt b/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt index 1d73ef9..bb4d126 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt @@ -58,6 +58,7 @@ class Evaluator(root: Scope) : NodeVisitor { 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) diff --git a/src/main/kotlin/gay/pizza/pork/parse/AnsiHighlightScheme.kt b/src/main/kotlin/gay/pizza/pork/parse/AnsiHighlightScheme.kt index 31818a6..f96f38d 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/AnsiHighlightScheme.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/AnsiHighlightScheme.kt @@ -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") diff --git a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt index 7cd0338..f42de4b 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt @@ -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) { private val unsanitizedSource = source @@ -65,6 +66,11 @@ class Parser(source: PeekableSource) { 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() } diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenFamily.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenFamily.kt index ec05ec0..c22a6d1 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/TokenFamily.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenFamily.kt @@ -5,6 +5,7 @@ enum class TokenFamily : TokenTypeProperty { KeywordFamily, SymbolFamily, NumericLiteralFamily, + StringLiteralFamily, CommentFamily, OtherFamily } diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt index 1f00ad6..3b879e5 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt @@ -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)), diff --git a/src/main/kotlin/gay/pizza/pork/parse/Tokenizer.kt b/src/main/kotlin/gay/pizza/pork/parse/Tokenizer.kt index 53ad435..440b890 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/Tokenizer.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/Tokenizer.kt @@ -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) diff --git a/src/main/kotlin/gay/pizza/pork/util/StringEscape.kt b/src/main/kotlin/gay/pizza/pork/util/StringEscape.kt new file mode 100644 index 0000000..2582b59 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/util/StringEscape.kt @@ -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) +}