Implement syntax highlighter.

This commit is contained in:
Alex Zenla 2023-08-21 20:15:31 -07:00
parent 95fe6e9abb
commit e12b51e8a7
Signed by: alex
GPG Key ID: C0780728420EBFE5
10 changed files with 80 additions and 56 deletions

View File

@ -53,6 +53,9 @@ fun main(args: Array<String>) {
if (program != parsedAst) { if (program != parsedAst) {
throw RuntimeException("Equality of parsed AST from printer was not proven.") throw RuntimeException("Equality of parsed AST from printer was not proven.")
} }
val highlighter = Highlighter(AnsiHighlightScheme())
println(highlighter.highlight(stream).joinToString(""))
} }
fun tokenize(input: String): TokenStream = fun tokenize(input: String): TokenStream =

View File

@ -0,0 +1,29 @@
package gay.pizza.pork.parse
open class AnsiHighlightScheme : HighlightScheme {
override fun highlight(token: Token): Highlight {
val attributes = when (token.type.family) {
TokenFamily.OperatorFamily -> operator()
TokenFamily.KeywordFamily -> keyword()
TokenFamily.SymbolFamily -> symbol()
TokenFamily.CommentFamily -> comment()
else -> null
}
return if (attributes != null) {
Highlight(token, ansi(attributes, token.text))
} else Highlight(token)
}
open fun operator(): AnsiAttributes = AnsiAttributes("32m")
open fun symbol(): AnsiAttributes = AnsiAttributes("33m")
open fun keyword(): AnsiAttributes = AnsiAttributes("35m")
open fun comment(): AnsiAttributes = AnsiAttributes("37m")
private fun ansi(attributes: AnsiAttributes, text: String): String =
"\u001b[${attributes.color}${text}\u001b[0m"
class AnsiAttributes(
val color: String
)
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.parse
class Highlight(val token: Token, val text: String? = null) {
override fun toString(): String = text ?: token.text
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.parse
interface HighlightScheme {
fun highlight(token: Token): Highlight
}

View File

@ -1,19 +1,6 @@
package gay.pizza.pork.parse package gay.pizza.pork.parse
abstract class Highlighter(source: TokenSource) : TokenProcessor(source) { class Highlighter(val scheme: HighlightScheme) {
override fun process(token: Token) { fun highlight(stream: TokenStream): List<Highlight> =
when { stream.tokens.map { scheme.highlight(it) }
token.type.keyword != null -> { }
keyword(token)
}
token.type == TokenType.Symbol -> {
symbol(token)
}
else -> other(token)
}
}
abstract fun keyword(token: Token)
abstract fun symbol(token: Token)
abstract fun other(token: Token)
}

View File

@ -0,0 +1,10 @@
package gay.pizza.pork.parse
enum class TokenFamily : TokenTypeProperty {
OperatorFamily,
KeywordFamily,
SymbolFamily,
NumericLiteralFamily,
CommentFamily,
OtherFamily
}

View File

@ -1,15 +0,0 @@
package gay.pizza.pork.parse
abstract class TokenProcessor(val source: TokenSource) {
fun processAll() {
while (true) {
val token = source.next()
process(token)
if (token.type == TokenType.EndOfFile) {
break
}
}
}
abstract fun process(token: Token)
}

View File

@ -1,8 +1,5 @@
package gay.pizza.pork.parse package gay.pizza.pork.parse
class TokenStream(val tokens: List<Token>) { class TokenStream(val tokens: List<Token>) {
fun excludeAllWhitespace(): TokenStream =
TokenStream(tokens.filter { it.type != TokenType.Whitespace })
override fun toString(): String = tokens.toString() override fun toString(): String = tokens.toString()
} }

View File

@ -1,39 +1,42 @@
package gay.pizza.pork.parse package gay.pizza.pork.parse
import gay.pizza.pork.parse.TokenTypeProperty.* import gay.pizza.pork.parse.TokenTypeProperty.*
import gay.pizza.pork.parse.TokenFamily.*
enum class TokenType(vararg properties: TokenTypeProperty) { enum class TokenType(vararg properties: TokenTypeProperty) {
Symbol, Symbol(SymbolFamily),
IntLiteral, IntLiteral(NumericLiteralFamily),
Equality, Equality(OperatorFamily),
Inequality, Inequality(OperatorFamily),
Equals(SingleChar('='), Promotion('=', Equality)), Equals(SingleChar('='), Promotion('=', Equality)),
Plus(SingleChar('+')), Plus(SingleChar('+'), OperatorFamily),
Minus(SingleChar('-')), Minus(SingleChar('-'), OperatorFamily),
Multiply(SingleChar('*')), Multiply(SingleChar('*'), OperatorFamily),
Divide(SingleChar('/')), Divide(SingleChar('/'), OperatorFamily),
LeftCurly(SingleChar('{')), LeftCurly(SingleChar('{')),
RightCurly(SingleChar('}')), RightCurly(SingleChar('}')),
LeftBracket(SingleChar('[')), LeftBracket(SingleChar('[')),
RightBracket(SingleChar(']')), RightBracket(SingleChar(']')),
LeftParentheses(SingleChar('(')), LeftParentheses(SingleChar('(')),
RightParentheses(SingleChar(')')), RightParentheses(SingleChar(')')),
Negation(SingleChar('!'), Promotion('=', Inequality)), Negation(SingleChar('!'), Promotion('=', Inequality), OperatorFamily),
Comma(SingleChar(',')), Comma(SingleChar(',')),
False(Keyword("false")), False(Keyword("false"), KeywordFamily),
True(Keyword("true")), True(Keyword("true"), KeywordFamily),
In(Keyword("in")), In(Keyword("in"), KeywordFamily),
If(Keyword("if")), If(Keyword("if"), KeywordFamily),
Then(Keyword("then")), Then(Keyword("then"), KeywordFamily),
Else(Keyword("else")), Else(Keyword("else"), KeywordFamily),
Whitespace, Whitespace,
BlockComment, BlockComment(CommentFamily),
LineComment, LineComment(CommentFamily),
EndOfFile; EndOfFile;
val promotions: List<Promotion> = properties.filterIsInstance<Promotion>() val promotions: List<Promotion> = properties.filterIsInstance<Promotion>()
val keyword: Keyword? = properties.filterIsInstance<Keyword>().singleOrNull() val keyword: Keyword? = properties.filterIsInstance<Keyword>().singleOrNull()
val singleChar: SingleChar? = properties.filterIsInstance<SingleChar>().singleOrNull() val singleChar: SingleChar? = properties.filterIsInstance<SingleChar>().singleOrNull()
val family: TokenFamily =
properties.filterIsInstance<TokenFamily>().singleOrNull() ?: OtherFamily
companion object { companion object {
val Keywords = entries.filter { item -> item.keyword != null } val Keywords = entries.filter { item -> item.keyword != null }

View File

@ -1,7 +1,7 @@
package gay.pizza.pork.parse package gay.pizza.pork.parse
sealed class TokenTypeProperty { interface TokenTypeProperty {
class SingleChar(val char: Char) : TokenTypeProperty() class SingleChar(val char: Char) : TokenTypeProperty
class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty() class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty
class Keyword(val text: String) : TokenTypeProperty() class Keyword(val text: String) : TokenTypeProperty
} }