init
This commit is contained in:
commit
da82c775db
9 changed files with 1066 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
373
Cargo.lock
generated
Normal file
373
Cargo.lock
generated
Normal 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
22
Cargo.toml
Normal 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
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# microlang
|
||||
|
||||
Minimal, embeddable, toy scripting language.
|
14
example/repl.rs
Normal file
14
example/repl.rs
Normal 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
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
hard_tabs = true
|
||||
max_width = 120
|
454
src/ast.rs
Normal file
454
src/ast.rs
Normal 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
188
src/lib.rs
Normal 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
9
src/util.rs
Normal 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 {}
|
Loading…
Add table
Add a link
Reference in a new issue