Skip to content

Commit

Permalink
- initial js codegen
Browse files Browse the repository at this point in the history
- fixing ast/parsing issues with structs and struct access
- fixing modulus operator lexing issue
- updating playground
- adding deploy to gh-pages for playground
- update readme
  • Loading branch information
dfirebaugh committed Jan 4, 2025
1 parent b3e02cf commit 3b69dbe
Show file tree
Hide file tree
Showing 38 changed files with 1,889 additions and 354 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tools/ast_explorer/static/main.wasm
tools/ast_explorer/static/wasm_exec.js

9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
punch:
go build ./cmd/punch/

run-ast-explorer:
bash ./scripts/build_wasm.sh
go run ./tools/ast_explorer/

clean:
rm punch
73 changes: 35 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
# PUNCH 🥊
`punch` is a hobby programming language. At the moment, `punch` targets wasm.
`punch` is a hobby programming language.
> I'm mainly working on this as a learning experience.
[demo playground](https://dfirebaugh.github.io/punch/)

### Build
Compile a punch program to wasm:
To build you will need [golang installed](https://go.dev/doc/install).

To run code locally, you will need `node` or `bun` installed in your PATH.

```bash
# build the wasm file
punch -o ./examples/adder/adder ./examples/adder/adder.p
# execute the wasm file
cd ./examples/adder
node adder.js
```
go build ./cmd/punch/

> a `.wat` file and `.ast` file will also be output for debug purposes
./punch ./examples/simple.pun # output: Hello, World!
```

#### Functions

```rust
// function declaration
bool is_best(i8 a, i8 b)
bool is_best(i32 a, i32 b)

// simple function
i8 add(i8 a, i8 b) {
i8 add(i32 a, i32 b) {
return a + b
}

// exported function
pub i8 add_two(i8 a, i8 b) {
pub i32 add_two(i32 a, i32 b) {
return a + b
}

// multiple return types
(i8, bool) add_eq(i8 a, i8 b) {
(i32, bool) add_eq(i32 a, i32 b) {
return a + b, a == b
}

// no return
main() {
fn main() {
println("hello world")
}
```
Expand All @@ -52,12 +53,8 @@ if a && b {
#### Assignment

```rust
i8 a = 42
i16 b = 42
i32 c = 42
i64 d = 42
u8 e = 42
u16 f = 42
u32 g = 42
u64 h = 42
f32 k = 42.0
Expand Down Expand Up @@ -111,33 +108,33 @@ import (
"fmt"
)

main() {
fn main() {
fmt.Println("hello, world!")
}
```

#### Status
> work in progress
| Feature | ast | wasm |
| - | - | - |
| function declaration |||
| function calls |||
| function multiple returns | ||
| if/else |||
| strings |||
| integers |||
| floats |||
| structs |||
| struct access | ||
| loops | ||
| lists |||
| maps |||
| pointers |||
| enums |||
| modules |||
| type inference | | |
| interfaces |||
| Feature | ast | wasm | js |
| - | - | - | - |
| function declaration ||||
| function calls ||||
| function multiple returns | | ||
| if/else ||||
| strings ||||
| integers ||||
| floats ||||
| structs ||||
| struct access | | | |
| loops | | | |
| lists ||||
| maps ||||
| pointers ||||
| enums ||||
| modules ||||
| type inference | | | |
| interfaces ||||

## Reference
- [WebAssembly Text Format (WAT)](https://webassembly.github.io/spec/core/text/index.html)
Expand Down
88 changes: 72 additions & 16 deletions cmd/punch/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"

"github.com/dfirebaugh/punch/internal/compiler"
"github.com/dfirebaugh/punch/internal/emitters/js"
"github.com/dfirebaugh/punch/internal/lexer"
"github.com/dfirebaugh/punch/internal/parser"
)

func main() {
var outputFile string
var outputWat bool
var outputTokens bool
var outputJS bool
var outputAst bool
var showHelp bool

flag.StringVar(&outputFile, "o", "", "output file (default: <input_filename>.wasm)")
flag.BoolVar(&outputWat, "wat", false, "output WebAssembly Text Format (WAT) file")
flag.BoolVar(&outputTokens, "tokens", false, "output tokens")
flag.BoolVar(&outputAst, "ast", false, "output Abstract Syntax Tree (AST) file")
flag.BoolVar(&outputJS, "js", false, "outputs js to stdout")
flag.BoolVar(&showHelp, "help", false, "show help message")
flag.Parse()

Expand All @@ -36,41 +42,91 @@ func main() {
panic(err)
}

wat, wasm, ast := compiler.Compile(filename, string(fileContents))
l := lexer.New(filename, string(fileContents))

if outputTokens {
tokens := l.Run()
for _, token := range tokens {
fmt.Println(token)
}
return
}

p := parser.New(l)
program := p.ParseProgram(filename)
ast, err := program.JSONPretty()
if err != nil {
panic(err)
}

if outputFile == "" {
outputFile = filename
}

if outputWat {
err = os.WriteFile(outputFile+".wat", []byte(wat), 0o644)
if outputAst {
fmt.Printf("%s\n", ast)
}

t := js.NewTranspiler()
jsCode, err := t.Transpile(program)
if err != nil {
log.Fatalf("error transpiling to js: %v", err)
}

if outputJS {
fmt.Printf("%s\n", jsCode)
return
}

bunPath, err := exec.LookPath("bun")
if err != nil {
log.Printf("bun is not available on the system, trying node: %v", err)
nodePath, err := exec.LookPath("node")
if err != nil {
panic(err)
log.Fatalf("neither bun nor node is available on the system. Please install one of them.")
}
}
cmd := exec.Command(nodePath, "--input-type=module")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if outputAst {
err = os.WriteFile(outputFile+".ast", []byte(ast), 0o644)
nodeStdin, err := cmd.StdinPipe()
if err != nil {
panic(err)
log.Fatalf("failed to open pipe to node: %v", err)
}

go func() {
defer nodeStdin.Close()
nodeStdin.Write([]byte(jsCode))
}()

err = cmd.Run()
if err != nil {
log.Fatalf("failed to run node: %v", err)
}
return
}

err = os.WriteFile(outputFile+".wasm", wasm, 0o644)
cmd := exec.Command(bunPath, "-e", jsCode)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

err = cmd.Run()
if err != nil {
panic(err)
log.Fatalf("failed to run bun: %v", err)
}
}

func printUsage() {
fmt.Println("Usage:", os.Args[0], "[-o output_file] [--wat] [--ast] <filename>")
fmt.Println("Usage:", os.Args[0], "[-o output_file] [--tokens] [--wat] [--ast] [--js] <filename>")
fmt.Println("Options:")
fmt.Println(" -o string")
fmt.Println(" output file (default: <input_filename>.wasm)")
fmt.Println(" --wat")
fmt.Println(" output WebAssembly Text Format (WAT) file")
fmt.Println(" --tokens")
fmt.Println(" output tokens")
fmt.Println(" --ast")
fmt.Println(" output Abstract Syntax Tree (AST) file")
fmt.Println(" --js")
fmt.Println(" output Javascript to stdout")
fmt.Println(" --help")
fmt.Println(" show help message")
}
2 changes: 1 addition & 1 deletion examples/adder/adder.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const fs = require('node:fs');

const wasmBuffer = fs.readFileSync('./adder.wasm');
const wasmBuffer = fs.readFileSync('./adder.punch.wasm');

const encode = function stringToIntegerArray(string, array) {
const alphabet = "abcdefghijklmnopqrstuvwxyz";
Expand Down
30 changes: 30 additions & 0 deletions examples/example.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pkg main

bool is_eq(i32 a, i32 b) {
return a == b
}

i32 add_two(i32 x, i32 y, i32 z) {
println("x =", x, "y =", y, "z =", z)
println("Hello, World!")
println("some other string")
return x + y
}

i32 add_four(i32 a, i32 b, i32 c, i32 d) {
return a + b + c + d
}

pub fn hello(bool is_hello) {
if is_hello {
println("hello, again")
}
if is_eq(2, 2) {
println("2 is equal")
}

println("adding some nums:", add_two(4,5,2))
}


hello(true)
11 changes: 11 additions & 0 deletions examples/factorial.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pkg main

i32 factorial(i32 n) {
i32 result = 1
for i32 i = 1; i <= n; i = i + 1 {
result = result * i
}
return result
}

println("Factorial of 5 is {}", factorial(5));
13 changes: 13 additions & 0 deletions examples/fib.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pkg main

i32 fibonacci(i32 n) {
if n == 0 {
return 0
}
if n == 1 {
return 1
}
return fibonacci(n - 1) + fibonacci(n - 2)
}

println("Fibonacci(10) is {}", fibonacci(10))
7 changes: 7 additions & 0 deletions examples/greet.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pkg main

pub fn greet(str name) {
println("Hello,", name)
}

greet("World!")
11 changes: 11 additions & 0 deletions examples/if.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pkg main

pub fn check_number(i32 x) {
if x > 0 {
println(x, "is positive")
}
}

check_number(5)

check_number(-3)
9 changes: 9 additions & 0 deletions examples/loop.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pkg main

pub fn count_to(i32 n) {
for i32 i = 1; i <= n; i = i + 1 {
println(i)
}
}

count_to(5)
13 changes: 13 additions & 0 deletions examples/math.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pkg main

pub fn math_operations() {
i32 a = 10
i32 b = 20
println("Addition: ", a + b)
println("Subtraction: ", a - b)
println("Multiplication: ", a * b)
println("Division: ", b / a)
println("Modulus: ", b % a)
}

math_operations()
13 changes: 13 additions & 0 deletions examples/recursion.pun
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pkg main

i32 fibonacci(i32 n) {
if n == 0 {
return 0
}
if n == 1 {
return 1
}
return fibonacci(n - 1) + fibonacci(n - 2)
}

println("Fibonacci(10) is {}", fibonacci(10))
Loading

0 comments on commit 3b69dbe

Please sign in to comment.