Pyle is a dynamic programming language implemented in Go. It features a custom bytecode compiler and a stack-based virtual machine.
This project began as an educational exercise to gain a deeper understanding of language design, interpreters, and the internal workings of compilers by writing everything completely from scratch. The syntax is influenced by the simplicity and readability of languages like Python and JavaScript.
- Variables: Supports mutable (
let) and immutable (const) variable declarations. - Data Types: Includes integers, floats, strings, booleans,
null, arrays, and maps. - Control Flow: Provides standard
if/elsestatements,for...inloops,whileloops, andbreak/continuestatements. - Functions: Supports first-class functions, closures, and recursion.
- Built-in Methods: Common methods are available on built-in types, such as
.map()on arrays and.format()on strings.
Here are a few examples to demonstrate Pyle's syntax.
Variables and Functions:
// A recursive function to calculate Fibonacci numbers
fn fib(n: number) { // optional type annotation/hints
if n <= 1 {
return n
}
return fib(n - 1) + fib(n - 2)
}
const result = fib(10)
echo("Fibonacci of 10 is:", result)Loops and Methods:
// A function to get the ASCII codes for each character in a string
fn getAsciiCodes(str: string) -> result {
return Ok(str.split(" ").map(fn(x) {
const wordCodes = array(x)!.map(fn (y) {
return asciiCode(y)!
})!
return wordCodes
})?)
}
echo(getAsciiCodes("Hello Pyle")?)To execute a Pyle script, you can run the command-line tool from the root of the project:
go run ./cmd/pyle/ examples/basic.pylePyle's syntax draws from Python and JavaScript while keeping things minimal. Blocks are delimited with curly braces, but most semicolons are optional. Variables use let for mutable and const for immutable declarations.
let count = 0
const name = "Pyle"Functions are defined with fn and can be assigned to variables or passed around as first-class values.
fn greet(who) {
echo("Hello, " + who)
}
const sayHi = fn(x) { echo("Hi " + x) }Loops come in two flavors. The for...in loop iterates over arrays, strings, ranges, or map iterators. The while loop runs as long as a condition holds true.
for i in 0:10 { echo(i) } // range from 0 to 9
for i in 0:10:2 { echo(i) } // range with step of 2
for item in ["a", "b", "c"] {
echo(item)
}
let count = 0
while count < 5 {
count += 1
}Maps use a JavaScript-like literal syntax. Bare identifiers become string keys, and computed keys go inside brackets.
const person = {
name: "Alice",
["is_" + "active"]: true,
[42]: "answer"
}
echo(person.name)
echo(person["is_active"])Type hints are optional annotations that can be added to function parameters and help with documentation. They have no runtime effect and are purely informational.
fn add(a: number, b: number) {
return a + b
}
fn greet(name: string) {
echo("Hello, " + name)
}Pyle provides methods on its core types. Learn more in the docs.
const text = "Hello World"
echo(text.len()) // 11
echo(text.split(" ")) // ["Hello", "World"]
echo("hi {}".format("there")?) // "hi there"
const nums = [1, 2, 3]
nums.append(4)
echo(nums.map(fn(x) { return x * 2 })) // result containing [2, 4, 6, 8]
const m = { a: 1, b: 2 }
for key in m.keys() { echo(key) }The os module provides filesystem operations. You can read and write files, check if paths exist, create directories, and list directory contents.
os.writeFile("data.txt", "Hello, file!")
const content = os.readFile("data.txt")!
echo(content)
if os.exists("data.txt")! {
os.remove("data.txt")!
}
os.mkdir("mydir")!
echo(os.listdir(".")!)
const info = os.stat("somefile.txt")!
echo(info.size, info.isDir)User input comes from scan(), which displays a prompt and returns the entered line.
let name = scan("Enter your name: ")!
echo("Hello, " + name)Pyle uses a result-based error handling system. Functions that can fail return a result type which wraps either a value or an error. You can handle these results in several ways.
The ? operator propagates errors. If the result contains an error, it immediately returns that error from the current function. Otherwise it unwraps the value.
fn loadData(path) -> result {
const content = os.readFile(path)?
return Ok(content.split("\n"))
}The ! operator (unwrap) extracts the value or panics if there's an error. Use it when you're confident the operation will succeed.
const age = int(scan("Age: ")!)!You can also deconstruct results into value and error pairs for explicit handling.
let value, err = int("not a number")
if err != null {
echo("Conversion failed:", err)
}Result objects also have methods: .unwrap() panics on error, .unwrapOr(default) returns a fallback value, and .catch(fn) lets you handle errors with a callback.
const num = int("abc").unwrapOr(0)
const data = os.readFile("missing.txt").catch(fn(e) {
echo("Using default because:", e)
return Ok("default content")
})Pyle comes with improved built-in modules and a powerful Go interoperability layer.
Create web servers easily using the http module.
use http
http.handle("/", fn(req, res) {
res.setHeader("Content-Type", "application/json")
res.send({ message: "Hello Pyle!" })
})
echo("Server starting on :8080")
http.listen(":8080")Built-in support for the Ebiten game engine (v2).
use pylegame as pg
pg.init(640, 480, "Pyle Game")
pg.run(update_fn, draw_fn)Pyle can wrap arbitrary Go objects (structs, images, database connections) and interact with them using reflection.
- Go methods are mapped automatically (e.g.
obj.MethodName()in Go is callable asobj.methodName()in Pyle).
There are many examples available in the examples directory covering arithmetic, HTTP servers, and a simple game.
Main Menu |
Gameplay |
Star Sentinel - A classic arcade shooter built entirely in Pyle (View Source)
To generate HTML documentation for the standard library:
go run ./cmd/pyledoc/
