From da82c775db5bce2b0b74feecaba8e92359d79389 Mon Sep 17 00:00:00 2001 From: Matthieu Jolimaitre Date: Thu, 24 Oct 2024 05:27:45 +0200 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 373 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 22 +++ README.md | 3 + example/repl.rs | 14 ++ rustfmt.toml | 2 + src/ast.rs | 454 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 188 ++++++++++++++++++++ src/util.rs | 9 + 9 files changed, 1066 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 example/repl.rs create mode 100644 rustfmt.toml create mode 100644 src/ast.rs create mode 100644 src/lib.rs create mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b573ce4 --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..33fb838 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5dc6ff --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# microlang + +Minimal, embeddable, toy scripting language. diff --git a/example/repl.rs b/example/repl.rs new file mode 100644 index 0000000..36ca92d --- /dev/null +++ b/example/repl.rs @@ -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(); + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..768ff5c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +hard_tabs = true +max_width = 120 diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..0593400 --- /dev/null +++ b/src/ast.rs @@ -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: Parser> + Clone + 'static {} +impl> + Clone + 'static, O> Parse 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 { + 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, + expression: Option>, +} + +impl Block { + pub fn parser(instr: impl Parse, expr: impl Parse) -> impl Parse { + 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 { + 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, 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, + pub body: Block, +} + +impl FunDef { + pub fn parser_def(instr: impl Parse, expr: impl Parse) -> 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, expr: impl Parse) -> impl Parse { + 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, Box), + Loop(Box), + FnDef(String, FunDef), + Short(ShortDef), +} + +// TODO : while, for, obj prop assign. + +impl Instr { + pub fn parser(expr: impl Parse) -> impl Parse { + 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, 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(value: T) -> Option { + 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) -> impl Parse { + (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 { + 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), + UniOp(UniOp, Box), + Var(String), + BinOps(Box, Vec<(Op, Expr)>), + Access(Box, String), + Call(Box, Vec), + Index(Box, Box), +} + +impl Expr { + pub fn parser(instr: impl Parse) -> impl Parse { + 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 { + 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 { + (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 { + 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 { + (lx("-").map(|_| Self::Neg)).or(lx("!").map(|_| Self::Inv)) + } + + pub fn apply(&self, value: &Value) -> Result { + 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), + Function(FunDef), +} + +impl Litteral { + pub fn parser(instr: impl Parse, expr: impl Parse) -> impl Parse { + 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::()) + .then(lx(".").or_not().map(Option::unwrap_or_default)) + .then(digit.repeated().map(|v| v.into_iter().collect::())) + .map(|((l, d), r)| Litteral::Num(format!("{l}{d}{r}").parse::().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 { + 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))) + } + }) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f134069 --- /dev/null +++ b/src/lib.rs @@ -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>, +} + +#[derive(Debug)] +pub enum Error { + ParsingErr(Vec>), + 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> { + self.frame.clone() + } +} + +#[derive(Debug, Clone)] +pub struct Scope { + pub parent: Option>>, + pub defs: HashMap, +} + +impl Scope { + pub fn new(parent: Option>>) -> Self { + let defs = HashMap::new(); + Self { defs, parent } + } + + pub fn child 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 O, O>(context: &mut Context, captures: Rc>, 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 { + 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>>), + Array(Rc>>), + Function(Rc>), +} + +pub enum InstrOutput { + Short(Short), + Value(Value), +} + +#[derive(Debug, Clone)] +pub struct Function { + pub capturing: Rc>, + pub def: FunImpl, +} + +#[derive(Clone)] +pub enum FunImpl { + Def(FunDef), + Built(Rc, &mut Context) -> Result>), +} + +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, context: &mut Context) -> Result { + 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 + +*/ diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..67cc692 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,9 @@ +use alloc::boxed::Box; + +pub trait Boxable: Sized { + fn to_box(self) -> Box { + Box::new(self) + } +} + +impl Boxable for T {}