An actual command line tool to run pork programs.

This commit is contained in:
Alex Zenla 2023-08-21 23:08:56 -07:00
parent e12b51e8a7
commit 1445490770
Signed by: alex
GPG Key ID: C0780728420EBFE5
13 changed files with 200 additions and 150 deletions

View File

@ -5,28 +5,21 @@ A small BBQ language.
Very WIP. Like VERY.
```pork
main = {
three = 3
two = 2
calculateSimple = {
(50 + three) * two
}
calculateComplex = {
three + two + 50
}
calculateSimpleResult = calculateSimple()
calculateComplexResult = calculateComplex()
list = [10, 20, 30]
trueValue = true
falseValue = false
[
calculateSimpleResult,
calculateComplexResult,
list,
trueValue,
falseValue
]
/* fibonacci sequence */
fib = { n in
if n == 0
then 0
else if n == 1
then 1
else fib(n - 1) + fib(n - 2)
}
result = fib(20)
println(result)
```
## Usage
```
./gradlew -q run --args 'run examples/fib.pork'
```

View File

@ -23,6 +23,7 @@ java {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-bom")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.github.ajalt.clikt:clikt:4.2.0")
}
tasks.withType<KotlinCompile> {
@ -34,15 +35,17 @@ tasks.withType<Wrapper> {
}
application {
mainClass.set("gay.pizza.pork.MainKt")
mainClass.set("gay.pizza.pork.cli.MainKt")
}
graalvmNative {
binaries {
named("main") {
imageName.set("pork")
mainClass.set("gay.pizza.pork.MainKt")
mainClass.set("gay.pizza.pork.cli.MainKt")
sharedLibrary.set(false)
}
}
}
tasks.run.get().outputs.upToDateWhen { false }

View File

@ -7,4 +7,5 @@ fib = { n in
else fib(n - 1) + fib(n - 2)
}
main = { in fib(20) }
result = fib(20)
println(result)

View File

@ -1,7 +0,0 @@
check = { value in
if value then 50
}
main = { in
value = check(100)
}

View File

@ -1,53 +1,52 @@
/* main function */
main = { in
three = 3
two = 2
three = 3
two = 2
calculateSimple = { in
(50 + three) * two
}
calculateComplex = { in
three + two + 50
}
multiply = { a, b in
a * b
}
// calculates the result
calculateSimpleResult = calculateSimple()
calculateComplexResult = calculateComplex()
multiplyResult = multiply(50, 50)
list = [10, 20, 30]
trueValue = true
falseValue = false
invert = { value in
!value
}
notEqual = { a, b in
a != b
}
equal = { a, b in
a == b
}
[
calculateSimpleResult,
calculateComplexResult,
multiplyResult,
list,
trueValue,
falseValue,
invert(true),
invert(false),
equal(5, 5),
equal(5, 6),
notEqual(5, 5),
notEqual(5, 6)
]
calculateSimple = { in
(50 + three) * two
}
calculateComplex = { in
three + two + 50
}
multiply = { a, b in
a * b
}
// calculates the result
calculateSimpleResult = calculateSimple()
calculateComplexResult = calculateComplex()
multiplyResult = multiply(50, 50)
list = [10, 20, 30]
trueValue = true
falseValue = false
invert = { value in
!value
}
notEqual = { a, b in
a != b
}
equal = { a, b in
a == b
}
results = [
calculateSimpleResult,
calculateComplexResult,
multiplyResult,
list,
trueValue,
falseValue,
invert(true),
invert(false),
equal(5, 5),
equal(5, 6),
notEqual(5, 5),
notEqual(5, 6)
]
println(results)

View File

@ -0,0 +1,23 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.compiler.KotlinCompiler
import gay.pizza.pork.parse.PorkParser
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import gay.pizza.pork.parse.TokenStreamSource
import kotlin.io.path.readText
class GenerateKotlinCommand : CliktCommand(help = "Generate Kotlin Code", name = "generate-kotlin") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {
val content = path.readText()
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize()
val program = PorkParser(TokenStreamSource(tokenStream)).readProgram()
val compiler = KotlinCompiler()
println(compiler.visit(program))
}
}

View File

@ -0,0 +1,21 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.parse.AnsiHighlightScheme
import gay.pizza.pork.parse.Highlighter
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import kotlin.io.path.readText
class HighlightCommand : CliktCommand(help = "Syntax Highlighter", name = "highlight") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {
val content = path.readText()
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize()
val highlighter = Highlighter(AnsiHighlightScheme())
print(highlighter.highlight(tokenStream).joinToString(""))
}
}

View File

@ -0,0 +1,20 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.subcommands
class RootCommand : CliktCommand(
help = "Pork - The BBQ Language",
name = "pork"
) {
init {
subcommands(
RunCommand(),
HighlightCommand(),
TokenizeCommand(),
GenerateKotlinCommand()
)
}
override fun run() {}
}

View File

@ -0,0 +1,32 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.eval.CallableFunction
import gay.pizza.pork.eval.PorkEvaluator
import gay.pizza.pork.eval.Scope
import gay.pizza.pork.parse.PorkParser
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import gay.pizza.pork.parse.TokenStreamSource
import kotlin.io.path.readText
class RunCommand : CliktCommand(help = "Run Program", name = "run") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {
val content = path.readText()
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize()
val program = PorkParser(TokenStreamSource(tokenStream)).readProgram()
val scope = Scope()
scope.define("println", CallableFunction { arguments ->
for (argument in arguments.values) {
println(argument)
}
})
val evaluator = PorkEvaluator(scope)
evaluator.visit(program)
}
}

View File

@ -0,0 +1,25 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import kotlin.io.path.readText
class TokenizeCommand : CliktCommand(help = "Tokenize Program", name = "tokenize") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {
val content = path.readText()
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize()
for (token in tokenStream.tokens) {
println("${token.start} ${token.type.name} '${sanitize(token.text)}'")
}
}
private fun sanitize(input: String): String =
input
.replace("\n", "\\n")
.replace("\r", "\\r")
}

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.cli
fun main(args: Array<String>) = RootCommand().main(args)

View File

@ -1,65 +0,0 @@
package gay.pizza.pork
import gay.pizza.pork.ast.Printer
import gay.pizza.pork.ast.nodes.Program
import gay.pizza.pork.eval.Arguments
import gay.pizza.pork.eval.PorkEvaluator
import gay.pizza.pork.eval.Scope
import gay.pizza.pork.parse.*
import kotlin.io.path.Path
import kotlin.io.path.readText
fun eval(ast: Program) {
val scope = Scope()
val evaluator = PorkEvaluator(scope)
evaluator.visit(ast)
println("> ${scope.call("main", Arguments.Zero)}")
}
fun validateTokenSoundness(input: String, stream: TokenStream) {
var expectedIndex = 0
for (token in stream.tokens) {
if (token.start != expectedIndex) {
throw RuntimeException("Expected token to be at index $expectedIndex but was ${token.start}")
}
val slice = input.slice(token.start until token.start + token.text.length)
if (slice != token.text) {
throw RuntimeException(
"Expected index ${token.start} for length ${token.text.length} to" +
" equal '${token.text}' but was '$slice'")
}
expectedIndex += token.text.length
}
}
fun main(args: Array<String>) {
val code = Path(args[0]).readText()
val stream = tokenize(code)
println(stream.tokens.joinToString("\n"))
val program = parse(stream)
eval(program)
val exactCode = stream.tokens.joinToString("") { it.text }
validateTokenSoundness(code, stream)
if (exactCode != code) {
throw RuntimeException("Token reconstruction didn't succeed.")
}
val generated = buildString { Printer(this).visit(program) }
val parsedAst = parse(tokenize(generated))
parse(tokenize(generated))
println(generated)
if (program != parsedAst) {
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 =
PorkTokenizer(StringCharSource(input)).tokenize()
fun parse(stream: TokenStream): Program =
PorkParser(TokenStreamSource(stream)).readProgram()

View File

@ -108,7 +108,9 @@ class PorkParser(source: PeekableSource<Token>) {
}
else -> {
throw RuntimeException("Failed to parse token: ${token.type} '${token.text}' as expression")
throw RuntimeException(
"Failed to parse token: ${token.type} '${token.text}' as expression" +
" (index ${unsanitizedSource.currentIndex})")
}
}