This commit is contained in:
Matthieu Jolimaitre 2024-10-24 05:27:45 +02:00
commit da82c775db
9 changed files with 1066 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

373
Cargo.lock generated Normal file
View file

@ -0,0 +1,373 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "backtrace-on-stack-overflow"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd2d70527f3737a1ad17355e260706c1badebabd1fa06a7a053407380df841b"
dependencies = [
"backtrace",
"libc",
"nix",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
dependencies = [
"hashbrown 0.14.5",
"stacker",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "foldhash"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "libc"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "microlang"
version = "0.1.0"
dependencies = [
"backtrace-on-stack-overflow",
"chumsky",
"hashbrown 0.15.0",
]
[[package]]
name = "miniz_oxide"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler2",
]
[[package]]
name = "nix"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "object"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "psm"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205"
dependencies = [
"cc",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[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.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01680f5d178a369f817f43f3d399650272873a8e7588a7872f7e90edc71d60a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[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"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

22
Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "microlang"
version = "0.1.0"
edition = "2021"
[dependencies]
chumsky = { version = "0.9.3", default-features = false, features = [
"ahash",
"spill-stack",
] }
hashbrown = { version = "0.15.0", default-features = true, features = [
# "core",
# "alloc",
# "inline-more",
# "equivalent",
# "default-hasher",
] }
backtrace-on-stack-overflow = "0.3.0"
[[bin]]
name = "repl"
path = "example/repl.rs"

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# microlang
Minimal, embeddable, toy scripting language.

14
example/repl.rs Normal file
View file

@ -0,0 +1,14 @@
use std::io::stdin;
use microlang::Context;
pub fn main() {
unsafe { backtrace_on_stack_overflow::enable() };
let mut context = Context::empty();
loop {
let mut line = String::new();
stdin().read_line(&mut line).unwrap();
let res = context.eval(line);
dbg!(res).ok();
}
}

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
hard_tabs = true
max_width = 120

454
src/ast.rs Normal file
View file

@ -0,0 +1,454 @@
use core::cell::RefCell;
use alloc::{boxed::Box, format, rc::Rc, string::String, vec::Vec};
use crate::{util::Boxable, Context, EvalError, FunImpl, Function, Value};
use chumsky::{
error::Simple,
prelude::{end, just, none_of, one_of, recursive, Recursive},
text::{whitespace, TextParser},
Parser,
};
use hashbrown::HashMap;
extern crate alloc;
pub trait Parse<O>: Parser<char, O, Error = Simple<char>> + Clone + 'static {}
impl<T: Parser<char, O, Error = Simple<char>> + Clone + 'static, O> Parse<O> for T {}
fn lx(word: &'static str) -> impl Parse<&'static str> {
whitespace()
.ignore_then(just(word))
.then_ignore(whitespace())
.labelled("token")
}
fn id() -> impl Parse<String> {
let allowed_first_chars = "_azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN";
let allowed_other_chars = "_azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN0123456789";
(one_of(allowed_first_chars).map(Some))
.then(one_of(allowed_other_chars).repeated())
.map(|(f, o)| f.iter().chain(o.iter()).collect())
.labelled("identifier")
}
#[derive(Debug, Clone)]
pub struct Block {
instructions: Vec<Instr>,
expression: Option<Box<Expr>>,
}
impl Block {
pub fn parser(instr: impl Parse<Instr>, expr: impl Parse<Expr>) -> impl Parse<Self> {
instr
.clone()
.then_ignore(lx(";"))
.repeated()
.then(expr.map(Boxable::to_box).or_not())
.map(|(instructions, expression)| Self {
expression,
instructions,
})
.labelled("block")
}
pub fn program_parser() -> impl Parse<Self> {
let mut expr_decl = Recursive::declare();
let mut instr_decl = Recursive::declare();
expr_decl.define(Expr::parser(instr_decl.clone()));
instr_decl.define(Instr::parser(expr_decl.clone()));
Self::parser(instr_decl, expr_decl)
.then_ignore(end())
.labelled("program")
}
pub fn eval(&self, context: &mut Context) -> Result<(Option<Short>, Value), EvalError> {
for instr in &self.instructions {
let short = instr.eval(context)?;
if short.is_some() {
return Ok((short, Value::None));
}
}
Ok(match self.expression.as_ref() {
Some(expr) => (None, expr.eval(context)?),
None => (None, Value::None),
})
}
}
#[derive(Debug, Clone)]
pub struct FunDef {
pub args: Vec<String>,
pub body: Block,
}
impl FunDef {
pub fn parser_def(instr: impl Parse<Instr>, expr: impl Parse<Expr>) -> impl Parse<(String, Self)> {
let arguments = id().separated_by(lx(","));
(lx("fn").ignore_then(id()))
.then(lx("(").ignore_then(arguments).then_ignore(lx(")")))
.then(lx("{").ignore_then(Block::parser(instr, expr)).then_ignore(lx("}")))
.map(|((id, args), body)| (id, Self { args, body }))
.labelled("function definition")
}
pub fn parser_lamb(instr: impl Parse<Instr>, expr: impl Parse<Expr>) -> impl Parse<Self> {
let arguments = id().separated_by(lx(","));
(lx("(").ignore_then(arguments).then_ignore(lx(")")))
.then(lx("{").ignore_then(Block::parser(instr, expr)).then_ignore(lx("}")))
.map(|(args, body)| Self { args, body })
.labelled("lambda")
}
}
#[derive(Debug, Clone)]
pub enum Instr {
Nop,
Block(Block),
Expr(Expr),
Init(String, Expr),
Assign(String, Expr),
Branch(Expr, Box<Instr>, Box<Instr>),
Loop(Box<Instr>),
FnDef(String, FunDef),
Short(ShortDef),
}
// TODO : while, for, obj prop assign.
impl Instr {
pub fn parser(expr: impl Parse<Expr>) -> impl Parse<Self> {
recursive(|instr: Recursive<'_, _, Self, _>| {
(lx("{")
.ignore_then(Block::parser(instr.clone(), expr.clone()))
.then_ignore(lx("}"))
.map(Self::Block))
.labelled("block instruction")
.or(lx("let")
.ignore_then(id())
.then_ignore(lx("="))
.then(expr.clone())
.map(|(id, ex)| Self::Init(id, ex)))
.labelled("variable initialization")
.or(lx("if")
.ignore_then(lx("(").ignore_then(expr.clone()).then_ignore(lx(")")))
.then((instr.clone()).then((lx("else").ignore_then(instr.clone())).or_not()))
.map(|(cond, (then, els))| Self::Branch(cond, then.to_box(), els.unwrap_or(Instr::Nop).to_box())))
.labelled("condition instruction")
.or(lx("loop")
.ignore_then(instr.clone())
.map(|body| Self::Loop(body.to_box())))
.labelled("loop instruction")
.or(ShortDef::parser(expr.clone())
.map(Self::Short)
.labelled("shortcut instruction"))
.or(FunDef::parser_def(instr, expr.clone())
.map(|(name, fun)| Self::FnDef(name, fun))
.labelled("function definition"))
.or(id()
.then_ignore(lx("="))
.then(expr.clone())
.map(|(id, ex)| Self::Assign(id, ex))
.labelled("variable assigniation."))
.or(expr.map(Self::Expr))
})
.labelled("instruction")
}
pub fn eval(&self, context: &mut Context) -> Result<Option<Short>, EvalError> {
Ok(match self {
Self::Nop => None,
Self::Block(block) => block.eval(context)?.0,
Self::Expr(expr) => no_short(expr.eval(context)?),
Self::Init(name, expr) => {
let value = expr.eval(context)?;
context.scope().borrow_mut().append(name.clone(), value);
no_short(())
}
Self::Assign(name, expr) => {
let value = expr.eval(context)?;
context.scope().borrow_mut().update(name, value)?;
None
}
Self::Branch(cond, t, e) => {
let condition = cond.eval(context)?;
match condition {
Value::Bool(true) => t.eval(context)?,
Value::Bool(false) => e.eval(context)?,
_ => Err(EvalError::WrongType)?,
}
}
Self::Loop(b) => loop {
match b.eval(context)? {
None => continue,
Some(Short::Break) => break None,
Some(Short::Continue) => continue,
Some(Short::Return(ret)) => break Some(Short::Return(ret)),
}
},
Self::Short(s) => return Ok(Some(s.eval(context)?)),
Self::FnDef(name, fun) => {
Self::Init(name.clone(), Expr::Litteral(Litteral::Function(fun.clone()))).eval(context)?
}
})
}
}
fn no_short<T>(value: T) -> Option<Short> {
drop(value);
None
}
#[derive(Debug, Clone)]
pub enum ShortDef {
Break,
Continue,
Return(Expr),
}
#[derive(Debug, Clone)]
pub enum Short {
Break,
Continue,
Return(Value),
}
impl ShortDef {
pub fn parser(expr: impl Parse<Expr>) -> impl Parse<Self> {
(lx("break").map(|_| Self::Break))
.or(lx("continue").map(|_| Self::Continue))
.or(lx("return").map(|_| Self::Return(Expr::Litteral(Litteral::None))))
.or(lx("return").ignore_then(expr).map(Self::Return))
}
pub fn eval(&self, context: &mut Context) -> Result<Short, EvalError> {
Ok(match self {
Self::Break => Short::Break,
Self::Continue => Short::Continue,
Self::Return(expr) => Short::Return(expr.eval(context)?),
})
}
}
#[derive(Debug, Clone)]
pub enum Expr {
Litteral(Litteral),
Parens(Box<Expr>),
UniOp(UniOp, Box<Expr>),
Var(String),
BinOps(Box<Expr>, Vec<(Op, Expr)>),
Access(Box<Expr>, String),
Call(Box<Expr>, Vec<Expr>),
Index(Box<Expr>, Box<Expr>),
}
impl Expr {
pub fn parser(instr: impl Parse<Instr>) -> impl Parse<Self> {
recursive(|expr: Recursive<'_, _, Self, _>| {
let left = (Litteral::parser(instr, expr.clone()).map(Self::Litteral))
.or((lx("(").ignore_then(expr.clone()).then_ignore(lx(")"))).map(|e| Self::Parens(e.to_box())))
.or((UniOp::parser().then(expr.clone())).map(|(o, e)| Self::UniOp(o, e.to_box())))
.or(id().map(Self::Var).padded());
(left
.clone()
.then(Op::parser().then(expr.clone()).repeated().at_least(1)))
.map(|(f, o)| Self::BinOps(f.to_box(), o))
.or((left.clone())
.then_ignore(lx("."))
.then(id())
.map(|(e, a)| Self::Access(e.to_box(), a)))
.or((left.clone())
.then_ignore(lx("("))
.then(expr.clone().then_ignore(lx(",")).repeated())
.then_ignore(lx(")"))
.map(|(e, a)| Self::Call(e.to_box(), a)))
.or((left.clone())
.then_ignore(lx("["))
.then(expr.clone())
.then_ignore(lx("]"))
.map(|(e, i)| Self::Index(e.to_box(), i.to_box())))
.or(left)
.labelled("expression")
})
}
pub fn eval(&self, context: &mut Context) -> Result<Value, EvalError> {
match self {
Self::Litteral(litt) => litt.eval(context),
Self::Parens(expr) => expr.eval(context),
Self::UniOp(op, expr) => op.apply(&expr.eval(context)?),
Self::Var(name) => context.scope().borrow().get(name),
Self::BinOps(first, rest) => {
let mut l = first.eval(context)?;
for (op, r) in rest {
let r = r.eval(context)?;
l = op.apply(&l, &r)?;
}
Ok(l)
}
Self::Access(e, p) => {
let index = Box::new(Expr::Litteral(Litteral::Str(p.clone())));
Self::Index(e.clone(), index).eval(context)
}
Self::Call(e, a) => {
let Value::Function(fun) = e.eval(context)? else {
return Err(EvalError::WrongType);
};
let mut args = Vec::new();
for arg in a {
let arg = arg.eval(context)?;
args.push(arg);
}
let fun = fun.borrow();
fun.call(args, context)
}
Self::Index(expr, index) => {
let value = expr.eval(context)?;
let index = index.eval(context)?;
match (value, index) {
(Value::Array(v), Value::Num(n)) => Ok(v
.borrow()
.get(n.floor().max(0.) as usize)
.cloned()
.unwrap_or(Value::None)),
(Value::Object(m), Value::Str(s)) => Ok(m.borrow().get(&s).cloned().unwrap_or(Value::None)),
_ => Err(EvalError::WrongType),
}
}
}
}
}
#[derive(Debug, Clone)]
pub enum Op {
Add,
Sub,
Div,
Prod,
Eq,
Neq,
Lt,
Gt,
Leq,
Geq,
}
impl Op {
pub fn parser() -> impl Parse<Self> {
(lx("+").map(|_| Self::Add))
.or(lx("-").map(|_| Self::Sub))
.or(lx("/").map(|_| Self::Div))
.or(lx("*").map(|_| Self::Prod))
.or(lx("==").map(|_| Self::Eq))
.or(lx("!=").map(|_| Self::Neq))
.or(lx("<").map(|_| Self::Lt))
.or(lx(">").map(|_| Self::Gt))
.or(lx("<=").map(|_| Self::Leq))
.or(lx(">=").map(|_| Self::Geq))
}
pub fn apply(&self, l: &Value, r: &Value) -> Result<Value, EvalError> {
Ok(match (self, l, r) {
(Self::Add, Value::Str(l), Value::Str(r)) => Value::Str(format!("{l}{r}")),
(Self::Add, Value::Num(l), Value::Num(r)) => Value::Num(l + r),
_ => Err(EvalError::WrongType)?,
})
}
}
#[derive(Debug, Clone)]
pub enum UniOp {
Neg,
Inv,
}
impl UniOp {
pub fn parser() -> impl Parse<Self> {
(lx("-").map(|_| Self::Neg)).or(lx("!").map(|_| Self::Inv))
}
pub fn apply(&self, value: &Value) -> Result<Value, EvalError> {
Ok(match (self, value) {
(Self::Inv, Value::Bool(b)) => Value::Bool(!b),
(Self::Neg, Value::Num(n)) => Value::Num(-n),
_ => Err(EvalError::WrongType)?,
})
}
}
#[derive(Debug, Clone)]
pub enum Litteral {
None,
Bool(bool),
Num(f64),
Str(String),
Object(Vec<(String, Expr)>),
Array(Vec<Expr>),
Function(FunDef),
}
impl Litteral {
pub fn parser(instr: impl Parse<Instr>, expr: impl Parse<Expr>) -> impl Parse<Self> {
let char = none_of('"')
.or(just("\\\\").map(|_| '\\'))
.or(just("\\\"").map(|_| '"'));
let digit = one_of("0123456789");
(just("\"")
.ignore_then(char.repeated())
.then_ignore(just("\""))
.map(|s| Self::Str(s.iter().collect())))
.or(just("true")
.map(|_| true)
.or(just("false").map(|_| false))
.map(Self::Bool))
.or((digit.clone().repeated().at_least(1))
.map(|v| v.into_iter().collect::<String>())
.then(lx(".").or_not().map(Option::unwrap_or_default))
.then(digit.repeated().map(|v| v.into_iter().collect::<String>()))
.map(|((l, d), r)| Litteral::Num(format!("{l}{d}{r}").parse::<f64>().unwrap())))
.or(lx("{")
.ignore_then(id().then_ignore(lx(":")).then(expr.clone()).separated_by(lx(",")))
.then_ignore(lx("}"))
.map(Self::Object))
.or(lx("[")
.ignore_then(expr.clone().separated_by(lx(",")))
.then_ignore(lx("]"))
.map(Self::Array))
.or(FunDef::parser_lamb(instr, expr).map(Self::Function))
.labelled("litteral")
}
pub fn eval(&self, context: &mut Context) -> Result<Value, EvalError> {
Ok(match self {
Self::None => Value::None,
Self::Bool(b) => Value::Bool(*b),
Self::Num(n) => Value::Num(*n),
Self::Str(s) => Value::Str(s.clone()),
Self::Object(keys) => {
let mut res = HashMap::new();
for (key, expr) in keys {
let value = expr.eval(context)?;
res.insert(key.clone(), value);
}
Value::Object(Rc::new(RefCell::new(res)))
}
Self::Array(items) => {
let mut res = Vec::new();
for expr in items {
let item = expr.eval(context)?;
res.push(item);
}
Value::Array(Rc::new(RefCell::new(res)))
}
Self::Function(def) => {
let capturing = context.scope().clone();
let def = FunImpl::Def(def.clone());
let function = Function { capturing, def };
Value::Function(Rc::new(RefCell::new(function)))
}
})
}
}

188
src/lib.rs Normal file
View file

@ -0,0 +1,188 @@
#![no_std]
mod util;
use core::{cell::RefCell, fmt::Debug, iter::repeat};
use alloc::{rc::Rc, string::String, vec::Vec};
use ast::{Block, FunDef, Short};
use chumsky::{error::Simple, Parser};
use hashbrown::HashMap;
extern crate alloc;
#[derive(Debug)]
pub struct Context {
pub frame: Rc<RefCell<Scope>>,
}
#[derive(Debug)]
pub enum Error {
ParsingErr(Vec<Simple<char>>),
EvalErr(EvalError),
}
#[derive(Debug)]
pub enum EvalError {
Exit,
NotFound(String),
IllegalShort(Short),
WrongType, // add type names ?
}
impl Context {
pub fn empty() -> Self {
let frame = Rc::new(RefCell::new(Scope::new(None)));
Self { frame }
}
pub fn eval(&mut self, script: String) -> Result<(Block, Value), Error> {
let parsed = match Block::program_parser().parse(script) {
Ok(parsed) => parsed,
Err(err) => Err(Error::ParsingErr(err))?,
};
let value = match parsed.eval(self) {
Ok((_short, value)) => value,
Err(err) => Err(Error::EvalErr(err))?,
};
Ok((parsed, value))
}
pub fn scope(&self) -> Rc<RefCell<Scope>> {
self.frame.clone()
}
}
#[derive(Debug, Clone)]
pub struct Scope {
pub parent: Option<Rc<RefCell<Self>>>,
pub defs: HashMap<String, Value>,
}
impl Scope {
pub fn new(parent: Option<Rc<RefCell<Self>>>) -> Self {
let defs = HashMap::new();
Self { defs, parent }
}
pub fn child<F: FnOnce(&mut Context) -> O, O>(context: &mut Context, op: F) -> O {
let parent = context.frame.clone();
let child = Rc::new(RefCell::new(Scope::new(Some(parent.clone()))));
context.frame = child;
let result = op(context);
context.frame = parent;
result
}
pub fn alt<F: FnOnce(&mut Context) -> O, O>(context: &mut Context, captures: Rc<RefCell<Self>>, op: F) -> O {
let parent = context.frame.clone();
let alt = Rc::new(RefCell::new(Scope::new(Some(captures.clone()))));
context.frame = alt;
let result = op(context);
context.frame = parent;
result
}
pub fn append(&mut self, name: String, value: Value) {
self.defs.insert(name, value);
}
pub fn update(&mut self, name: &str, value: Value) -> Result<(), EvalError> {
let Some(variable) = self.defs.get_mut(name) else {
if let Some(parent) = &self.parent {
return parent.borrow_mut().update(name, value);
}
return Err(EvalError::NotFound(name.into()));
};
*variable = value;
Ok(())
}
pub fn get(&self, name: &str) -> Result<Value, EvalError> {
let Some(value) = self.defs.get(name) else {
if let Some(parent) = &self.parent {
return parent.borrow_mut().get(name);
}
return Err(EvalError::NotFound(name.into()));
};
Ok(value.clone())
}
}
#[derive(Debug, Clone)]
pub enum Value {
None,
Bool(bool),
Num(f64),
Str(String),
Object(Rc<RefCell<HashMap<String, Value>>>),
Array(Rc<RefCell<Vec<Value>>>),
Function(Rc<RefCell<Function>>),
}
pub enum InstrOutput {
Short(Short),
Value(Value),
}
#[derive(Debug, Clone)]
pub struct Function {
pub capturing: Rc<RefCell<Scope>>,
pub def: FunImpl,
}
#[derive(Clone)]
pub enum FunImpl {
Def(FunDef),
Built(Rc<dyn Fn(Vec<Value>, &mut Context) -> Result<Value, EvalError>>),
}
impl Debug for FunImpl {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Built(_) => f.debug_struct("Built").finish(),
Self::Def(def) => f.debug_tuple("Def").field(def).finish(),
}
}
}
impl Function {
pub fn call(&self, args: Vec<Value>, context: &mut Context) -> Result<Value, EvalError> {
match &self.def {
FunImpl::Built(op) => op(args, context),
FunImpl::Def(def) => Scope::alt(context, self.capturing.clone(), |context| {
let names = def.args.iter().cloned();
for (name, value) in names.zip(args.into_iter().chain(repeat(Value::None))) {
context.scope().borrow_mut().append(name, value);
}
match def.body.eval(context) {
Err(err) => Err(err),
Ok((None, value)) => Ok(value),
Ok((Some(Short::Return(ret)), _)) => Ok(ret),
Ok((Some(s), _)) => Err(EvalError::IllegalShort(s)),
}
}),
}
}
}
pub mod ast;
/*
let a = 3;
fn f1() {
let a = 5;
fn f2() {
a = 3;
}
f2
}
let f2 = f1();
f2();
a = 3
*/

9
src/util.rs Normal file
View file

@ -0,0 +1,9 @@
use alloc::boxed::Box;
pub trait Boxable: Sized {
fn to_box(self) -> Box<Self> {
Box::new(self)
}
}
impl<T: Sized> Boxable for T {}