mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-03 05:10:55 +00:00
An actual command line tool to run pork programs.
This commit is contained in:
parent
e12b51e8a7
commit
1445490770
39
README.md
39
README.md
@ -5,28 +5,21 @@ A small BBQ language.
|
|||||||
Very WIP. Like VERY.
|
Very WIP. Like VERY.
|
||||||
|
|
||||||
```pork
|
```pork
|
||||||
main = {
|
/* fibonacci sequence */
|
||||||
three = 3
|
fib = { n in
|
||||||
two = 2
|
if n == 0
|
||||||
calculateSimple = {
|
then 0
|
||||||
(50 + three) * two
|
else if n == 1
|
||||||
}
|
then 1
|
||||||
calculateComplex = {
|
else fib(n - 1) + fib(n - 2)
|
||||||
three + two + 50
|
|
||||||
}
|
|
||||||
calculateSimpleResult = calculateSimple()
|
|
||||||
calculateComplexResult = calculateComplex()
|
|
||||||
|
|
||||||
list = [10, 20, 30]
|
|
||||||
trueValue = true
|
|
||||||
falseValue = false
|
|
||||||
|
|
||||||
[
|
|
||||||
calculateSimpleResult,
|
|
||||||
calculateComplexResult,
|
|
||||||
list,
|
|
||||||
trueValue,
|
|
||||||
falseValue
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = fib(20)
|
||||||
|
println(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
./gradlew -q run --args 'run examples/fib.pork'
|
||||||
```
|
```
|
||||||
|
@ -23,6 +23,7 @@ java {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-bom")
|
implementation("org.jetbrains.kotlin:kotlin-bom")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
|
implementation("com.github.ajalt.clikt:clikt:4.2.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
@ -34,15 +35,17 @@ tasks.withType<Wrapper> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("gay.pizza.pork.MainKt")
|
mainClass.set("gay.pizza.pork.cli.MainKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
graalvmNative {
|
graalvmNative {
|
||||||
binaries {
|
binaries {
|
||||||
named("main") {
|
named("main") {
|
||||||
imageName.set("pork")
|
imageName.set("pork")
|
||||||
mainClass.set("gay.pizza.pork.MainKt")
|
mainClass.set("gay.pizza.pork.cli.MainKt")
|
||||||
sharedLibrary.set(false)
|
sharedLibrary.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.run.get().outputs.upToDateWhen { false }
|
||||||
|
@ -7,4 +7,5 @@ fib = { n in
|
|||||||
else fib(n - 1) + fib(n - 2)
|
else fib(n - 1) + fib(n - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
main = { in fib(20) }
|
result = fib(20)
|
||||||
|
println(result)
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
check = { value in
|
|
||||||
if value then 50
|
|
||||||
}
|
|
||||||
|
|
||||||
main = { in
|
|
||||||
value = check(100)
|
|
||||||
}
|
|
@ -1,53 +1,52 @@
|
|||||||
/* main function */
|
three = 3
|
||||||
main = { in
|
two = 2
|
||||||
three = 3
|
|
||||||
two = 2
|
|
||||||
|
|
||||||
calculateSimple = { in
|
calculateSimple = { in
|
||||||
(50 + three) * two
|
(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)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
23
src/main/kotlin/gay/pizza/pork/cli/GenerateKotlinCommand.kt
Normal file
23
src/main/kotlin/gay/pizza/pork/cli/GenerateKotlinCommand.kt
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
21
src/main/kotlin/gay/pizza/pork/cli/HighlightCommand.kt
Normal file
21
src/main/kotlin/gay/pizza/pork/cli/HighlightCommand.kt
Normal 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(""))
|
||||||
|
}
|
||||||
|
}
|
20
src/main/kotlin/gay/pizza/pork/cli/RootCommand.kt
Normal file
20
src/main/kotlin/gay/pizza/pork/cli/RootCommand.kt
Normal 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() {}
|
||||||
|
}
|
32
src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt
Normal file
32
src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
25
src/main/kotlin/gay/pizza/pork/cli/TokenizeCommand.kt
Normal file
25
src/main/kotlin/gay/pizza/pork/cli/TokenizeCommand.kt
Normal 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")
|
||||||
|
}
|
3
src/main/kotlin/gay/pizza/pork/cli/main.kt
Normal file
3
src/main/kotlin/gay/pizza/pork/cli/main.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package gay.pizza.pork.cli
|
||||||
|
|
||||||
|
fun main(args: Array<String>) = RootCommand().main(args)
|
@ -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()
|
|
@ -108,7 +108,9 @@ class PorkParser(source: PeekableSource<Token>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
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})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user