basic parser and interpreter from example

This commit is contained in:
Jim 2025-10-16 18:25:22 -04:00
commit a587f1be24
Signed by: jim
GPG key ID: 3236D2F059A7C0AC
6 changed files with 531 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

285
Cargo.lock generated Normal file
View file

@ -0,0 +1,285 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "cc"
version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chumsky"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cd3ef0a728f561e3b4157213d178ae7523cbc405423f862da757447588ae103"
dependencies = [
"hashbrown",
"regex-automata",
"serde",
"stacker",
"unicode-ident",
"unicode-segmentation",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "jang"
version = "0.1.0"
dependencies = [
"chumsky",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "psm"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c"
dependencies = [
"cc",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex-automata"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "stacker"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "jang"
version = "0.1.0"
edition = "2024"
[dependencies]
chumsky = "0.11.1"

1
README.md Normal file
View file

@ -0,0 +1 @@
this is a programming language maybe

231
src/main.rs Normal file
View file

@ -0,0 +1,231 @@
use std::{collections::HashMap, rc::Rc};
use chumsky::prelude::*;
#[derive(Debug, Clone)]
enum Expr<'src> {
Num(f64),
Var(&'src str),
Neg(Box<Expr<'src>>),
Add(Box<Expr<'src>>, Box<Expr<'src>>),
Sub(Box<Expr<'src>>, Box<Expr<'src>>),
Mul(Box<Expr<'src>>, Box<Expr<'src>>),
Div(Box<Expr<'src>>, Box<Expr<'src>>),
Call(&'src str, Vec<Expr<'src>>),
Let {
name: &'src str,
rhs: Box<Expr<'src>>,
then: Box<Expr<'src>>,
},
Fn {
name: &'src str,
args: Vec<&'src str>,
body: Rc<Expr<'src>>,
then: Box<Expr<'src>>,
},
}
fn main() {
let path = std::env::args().nth(1).unwrap();
let src = std::fs::read_to_string(&path).unwrap();
println!("Parsing file at {path}");
println!("src:\n{}\n---", src);
let parse_result = create_parser().parse(&src);
println!("parsed:\n{:?}\n---", parse_result);
if let Ok(ast) = parse_result.into_result() {
let mut vars = HashMap::new();
let output = eval(&ast, &mut vars);
println!("{:?}", output);
}
}
#[derive(Clone)]
enum IdentValue<'src> {
Var(f64),
Func {
name: &'src str,
params: Vec<&'src str>,
body: Rc<Expr<'src>>,
},
}
fn eval<'src>(
expr: &Expr<'src>,
vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>,
) -> Result<f64, String> {
fn bind_var<'src>(
vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>,
name: &'src str,
val: IdentValue<'src>,
) {
vars.entry(name).or_insert(Vec::new()).push(val);
}
fn unbind_var<'src>(vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>, name: &'src str) {
vars.get_mut(name).unwrap().pop();
}
match expr {
Expr::Num(x) => Ok(*x),
Expr::Neg(a) => Ok(-eval(a, vars)?),
Expr::Add(a, b) => Ok(eval(a, vars)? + eval(b, vars)?),
Expr::Sub(a, b) => Ok(eval(a, vars)? - eval(b, vars)?),
Expr::Mul(a, b) => Ok(eval(a, vars)? * eval(b, vars)?),
Expr::Div(a, b) => Ok(eval(a, vars)? / eval(b, vars)?),
Expr::Var(name) => {
let ident_value = vars.get(name).and_then(|v| v.last());
match ident_value {
Some(IdentValue::Var(v)) => Ok(*v),
Some(IdentValue::Func { .. }) => Err("Expected function to be called".to_string()),
None => Err(format!("Undefined variable `{}`", name)),
}
}
Expr::Let { name, rhs, then } => {
let val = eval(rhs, vars)?;
bind_var(vars, name, IdentValue::Var(val));
let output = eval(then, vars);
unbind_var(vars, name);
output
}
Expr::Call(name, args) => {
let ident_value = vars.get(name).and_then(|v| v.last()).cloned();
match ident_value {
Some(IdentValue::Func { name, params, body }) => {
if params.len() != args.len() {
Err(format!(
"expected {} arguments but only {} were provided to {}",
params.len(),
args.len(),
name
))
} else {
let binds = params
.iter()
.zip(args)
.map(|(param, arg)| (param, eval(arg, vars)))
.collect::<Vec<_>>();
for (param, bind) in binds {
bind_var(vars, param, IdentValue::Var(bind.unwrap()));
}
let return_value = eval(&body, vars);
for param in params.iter() {
unbind_var(vars, param);
}
return_value
}
}
Some(IdentValue::Var(_)) => {
Err("Variable cannot be called like a function".to_string())
}
None => Err(format!("Undefined variable `{}`", name)),
}
}
Expr::Fn {
name,
args,
body,
then,
} => {
vars.entry(name)
.or_insert(Vec::new())
.push(IdentValue::Func {
name,
params: args.clone(),
body: body.clone(),
});
let output = eval(then, vars);
vars.get_mut(name).unwrap().pop();
output
}
}
}
fn create_parser<'src>() -> impl Parser<'src, &'src str, Expr<'src>> {
let ident = text::ascii::ident().padded();
let expr = recursive(|expr| {
let int = text::int(10)
.map(|s: &str| Expr::Num(s.parse().unwrap()))
.padded();
let call = ident
.then(
expr.clone()
.separated_by(just(','))
.allow_trailing()
.collect::<Vec<_>>()
.delimited_by(just('('), just(')')),
)
.map(|(f, args)| Expr::Call(f, args));
let atom = int
.or(expr.delimited_by(just('('), just(')')))
.or(call)
.or(ident.map(Expr::Var))
.padded();
let op = |c| just(c).padded();
let unary = op('-')
.repeated()
.foldr(atom, |_op, rhs| Expr::Neg(Box::new(rhs)));
let product = unary.clone().foldl(
choice((
op('*').to(Expr::Mul as fn(_, _) -> _),
op('/').to(Expr::Div as fn(_, _) -> _),
))
.then(unary)
.repeated(),
|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
);
let sum = product.clone().foldl(
choice((
op('+').to(Expr::Add as fn(_, _) -> _),
op('-').to(Expr::Sub as fn(_, _) -> _),
))
.then(product)
.repeated(),
|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
);
sum
});
let decl = recursive(|decl| {
// ex: let x = 5*2
let decl_let = text::ascii::keyword("let")
.ignore_then(ident)
.then_ignore(just('='))
.then(expr.clone())
.then_ignore(just(';'))
.then(decl.clone())
.map(|((name, rhs), then)| Expr::Let {
name,
rhs: Box::new(rhs),
then: Box::new(then),
});
// ex: fn f x y = x + y
let decl_fn = text::ascii::keyword("fn")
.ignore_then(ident)
// collect params
.then(ident.repeated().collect::<Vec<_>>())
.then_ignore(just('='))
.then(expr.clone())
.then_ignore(just(';'))
.then(decl)
.map(|(((name, params), body), then)| Expr::Fn {
name,
args: params,
body: Rc::new(body),
then: Box::new(then),
});
// must parse 'let' keyword first so it doesn't become and identifier
decl_fn.or(decl_let).or(expr).padded()
});
decl
}

6
test.jang Normal file
View file

@ -0,0 +1,6 @@
let five = 5;
let five = five + 3;
fn sum x y = x + y;
fn mul x y = x*y;
let six = sum(1,sum(mul(2,2),3));
five * 3 + six