feat: init

This commit is contained in:
2025-05-03 12:49:48 +08:00
commit 5e35da49ef
27 changed files with 3390 additions and 0 deletions

86
src/builtins/mod.rs Normal file
View File

@@ -0,0 +1,86 @@
use derive_more::Constructor;
use super::vm::Env;
use super::vm::Symbol;
use super::vm::Value;
use crate::bytecode::Const;
pub fn env() -> Env {
let mut env = Env::empty();
env.insert(Symbol::from("true"), Value::Const(Const::Bool(true)));
env.insert(Symbol::from("false"), Value::Const(Const::Bool(false)));
env.insert(Symbol::from("__add"), Value::PrimOp(PrimOp::new(2, |args| {
let [first, second]: [Value; 2] = args.try_into().unwrap();
first.add(second)
})));
env.insert(Symbol::from("__sub"), Value::PrimOp(PrimOp::new(2, |args| {
let [first, second]: [Value; 2] = args.try_into().unwrap();
first.add(second.neg())
})));
env.insert(Symbol::from("__mul"), Value::PrimOp(PrimOp::new(2, |args| {
let [first, second]: [Value; 2] = args.try_into().unwrap();
first.mul(second)
})));
env.insert(Symbol::from("__div"), Value::PrimOp(PrimOp::new(2, |args| {
let [first, second]: [Value; 2] = args.try_into().unwrap();
first.div(second)
})));
env.insert(Symbol::from("__lessThan"), Value::PrimOp(PrimOp::new(2, |args| {
let [first, second]: [Value; 2] = args.try_into().unwrap();
first.lt(second)
})));
env
}
#[derive(Debug, Clone, Constructor)]
pub struct PrimOp {
arity: u8,
func: fn(Vec<Value>) -> Value,
}
impl PartialEq for PrimOp {
fn eq(&self, _: &Self) -> bool {
false
}
}
impl PrimOp {
pub fn call(self, args: Vec<Value>) -> Value {
if (args.len() as u8) < self.arity {
Value::PartialPrimOp(PartialPrimOp { arity: self.arity - args.len() as u8, args, func: self.func })
} else if args.len() as u8 == self.arity {
(self.func)(args)
} else {
unimplemented!()
}
}
}
#[derive(Debug, Clone)]
pub struct PartialPrimOp {
arity: u8,
args: Vec<Value>,
func: fn(Vec<Value>) -> Value
}
impl PartialEq for PartialPrimOp {
fn eq(&self, _: &Self) -> bool {
false
}
}
impl PartialPrimOp {
pub fn call(mut self, args: Vec<Value>) -> Value {
let len = args.len() as u8;
self.args.extend(args);
if len < self.arity {
Value::PartialPrimOp(PartialPrimOp { arity: self.arity - len, args: self.args, func: self.func })
} else if len == self.arity {
(self.func)(self.args)
} else {
unimplemented!()
}
}
}

230
src/bytecode.rs Normal file
View File

@@ -0,0 +1,230 @@
use std::hash::{Hash, Hasher};
use ecow::EcoString;
use anyhow::Error;
use derive_more::{IsVariant, Unwrap};
use crate::slice::Slice;
use crate::value::Func;
pub type ThunkIdx = usize;
pub type ConstIdx = usize;
pub type SymIdx = usize;
pub type OpCodes = Slice<OpCode>;
pub type Consts = Slice<Const>;
pub type Thunks = Slice<Thunk>;
pub type Args = Slice<Arg>;
#[derive(Debug, Clone)]
pub struct Thunk {
pub opcodes: OpCodes,
}
#[derive(Debug, Clone, Hash)]
pub enum Arg {}
#[derive(Debug, Clone, IsVariant, Unwrap)]
pub enum Const {
Bool(bool),
Int(i64),
Float(f64),
String(EcoString),
Func(Func),
}
impl From<bool> for Const {
fn from(value: bool) -> Self {
Const::Bool(value)
}
}
impl From<i64> for Const {
fn from(value: i64) -> Self {
Const::Int(value)
}
}
impl From<f64> for Const {
fn from(value: f64) -> Self {
Const::Float(value)
}
}
impl From<EcoString> for Const {
fn from(value: EcoString) -> Self {
Const::String(value)
}
}
impl From<String> for Const {
fn from(value: String) -> Self {
Const::String(value.into())
}
}
impl From<&str> for Const {
fn from(value: &str) -> Self {
Const::String(value.into())
}
}
impl<'a> TryFrom<&'a Const> for &'a bool {
type Error = Error;
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
match value {
Const::Bool(b) => Ok(b),
_ => panic!(),
}
}
}
impl<'a> TryFrom<&'a Const> for &'a i64 {
type Error = Error;
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
match value {
Const::Int(int) => Ok(int),
_ => panic!(),
}
}
}
impl<'a> TryFrom<&'a Const> for &'a f64 {
type Error = Error;
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
match value {
Const::Float(float) => Ok(float),
_ => panic!(),
}
}
}
impl<'a> TryFrom<&'a Const> for &'a str {
type Error = Error;
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
match value {
Const::String(string) => Ok(string),
_ => panic!(),
}
}
}
impl PartialEq for Const {
fn eq(&self, other: &Self) -> bool {
use Const::*;
match (self, other) {
(Bool(a), Bool(b)) => a == b,
(Int(a), Int(b)) => a == b,
(Float(a), Float(b)) => a == b,
(String(a), String(b)) => a == b,
_ => false,
}
}
}
impl Eq for Const {}
impl Hash for Const {
fn hash<H: Hasher>(&self, state: &mut H) {
use Const::*;
match self {
Bool(b) => b.hash(state),
Int(int) => int.hash(state),
Float(float) => float.to_bits().hash(state),
String(string) => string.hash(state),
Func(func) => func.hash(state),
}
}
}
#[derive(Debug, Clone, Hash)]
pub enum OpCode {
/// load a constant onto stack
Const { value: Const },
/// load a dynamic var onto stack
LookUp { sym: EcoString },
/// load a thunk lazily onto stack
LoadThunk { idx: ThunkIdx },
/// load a thunk onto stack and force its value
LoadValue { idx: ThunkIdx },
/// force TOS to value
ForceValue,
/// [ ... func, args @ .. ] call func with `arity` numbers of arg
Call { arity: usize },
/// assert TOS is true then consume it
Assert,
/// jump forward
Jmp { step: usize },
/// [ ... cond ] if (cond) is true, then jump forward
JmpIfTrue { step: usize },
/// [ ... cond ] if (cond) is false, then jump forward
JmpIfFalse { step: usize },
/// push an empty attribute set onto stack
AttrSet,
/// push an empty recursive attribute set onto stack
RecAttrSet,
/// [ ... set, value ] push the static kv pair (name, (value)) into (set)
PushStaticAttr { name: EcoString },
/// [ ... set, name, value ] push the dynamic kv pair ((name), (value)) in to (set)
PushDynamicAttr,
/// push an empty list onto stack
List,
/// [ ... list, elem ] push (elem) into (list)
PushElem,
/// [ ... a, b ] perform a binary operation ((a) `op` (b))
BinOp { op: BinOp },
/// [ ... a ] perform a unary operation (`op` (a))
UnOp { op: UnOp },
/// TODO:
ConcatString,
/// TODO:
HasAttr { sym: EcoString },
/// TODO:
HasDynamicAttr,
// HasAttr { arity: usize },
/// TODO:
Select { sym: EcoString },
// Select { arity: usize },
/// TODO:
SelectDynamic,
// SelectDynamic { arity: usize },
/// TODO:
SelectOrEmpty { sym: EcoString },
/// TODO:
SelectDynamicOrEmpty,
/// TODO:
SelectWithDefault { sym: EcoString },
/// TODO:
SelectDynamicWithDefault,
/// enter the environment of the attribute set at TOS
EnterEnv,
/// exit the envrironment
LeaveEnv,
/// return a value
Ret,
/// no-op
NoOp,
}
#[derive(Debug, Clone, Copy, Hash)]
pub enum BinOp {
Add,
And,
Or,
Eq,
Con,
Upd,
}
#[derive(Debug, Clone, Copy, Hash)]
pub enum UnOp {
Not,
}
#[derive(Debug)]
pub struct Program {
pub top_level: OpCodes,
pub thunks: Thunks,
}

390
src/compile/compile.rs Normal file
View File

@@ -0,0 +1,390 @@
use crate::bytecode::*;
use super::ir;
pub struct Compiler {
opcodes: Vec<OpCode>,
}
pub fn compile(downgraded: ir::Downgraded) -> Program {
Program {
top_level: Compiler::new().compile(downgraded.top_level),
thunks: downgraded
.thunks
.into_iter()
.map(|thunk| Thunk {
opcodes: Compiler::new().compile(thunk),
})
.collect(),
}
}
impl Compiler {
fn new() -> Self {
Self {
opcodes: Vec::new(),
}
}
fn compile(mut self, ir: ir::Ir) -> OpCodes {
ir.compile(&mut self);
self.opcodes()
}
fn push(&mut self, code: OpCode) {
self.opcodes.push(code);
}
fn idx(&self) -> usize {
self.opcodes.len()
}
fn modify(&mut self, idx: usize, code: OpCode) {
self.opcodes[idx] = code;
}
fn last(&self) -> Option<OpCode> {
self.opcodes.last().cloned()
}
fn pop(&mut self) -> Option<OpCode> {
self.opcodes.pop()
}
fn opcodes(self) -> OpCodes {
self.opcodes.into()
}
}
pub trait Compile {
fn compile(self, comp: &mut Compiler);
}
pub trait CompileWithLength {
fn compile_with_length(self, comp: &mut Compiler) -> usize;
}
impl<T: Compile> CompileWithLength for T {
fn compile_with_length(self, comp: &mut Compiler) -> usize {
let start = comp.idx();
self.compile(comp);
let end = comp.idx();
end - start
}
}
impl Compile for ir::Const {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::Const { value: self.value });
}
}
impl Compile for ir::Var {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::LookUp { sym: self.sym });
}
}
impl Compile for ir::Thunk {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::LoadThunk { idx: self.idx });
}
}
impl Compile for ir::Attrs {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::AttrSet);
for stc in self.stcs {
stc.1.compile(comp);
comp.push(OpCode::PushStaticAttr { name: stc.0 });
}
for dynamic in self.dyns {
dynamic.0.compile(comp);
dynamic.1.compile(comp);
comp.push(OpCode::PushDynamicAttr)
}
}
}
impl Compile for ir::RecAttrs {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::AttrSet);
for dynamic in self.dyns.clone() {
dynamic.0.compile(comp);
dynamic.1.compile(comp);
comp.push(OpCode::PushDynamicAttr)
}
comp.push(OpCode::EnterEnv);
comp.push(OpCode::AttrSet);
for stc in self.stcs {
stc.1.compile(comp);
comp.push(OpCode::PushStaticAttr { name: stc.0 });
}
for dynamic in self.dyns {
dynamic.0.compile(comp);
dynamic.1.compile(comp);
comp.push(OpCode::PushDynamicAttr)
}
comp.push(OpCode::LeaveEnv);
}
}
impl Compile for ir::List {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::List);
for item in self.items {
item.compile(comp);
comp.push(OpCode::PushElem);
}
}
}
impl Compile for ir::UnOp {
fn compile(self, comp: &mut Compiler) {
use ir::UnOpKind::*;
match self.kind {
Neg => {
comp.push(OpCode::LookUp { sym: "__sub".into() });
comp.push(OpCode::Const { value: Const::Int(0) });
self.rhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
}
Not => {
self.rhs.compile(comp);
comp.push(OpCode::UnOp { op: UnOp::Not });
}
}
}
}
impl Compile for ir::BinOp {
fn compile(self, comp: &mut Compiler) {
use ir::BinOpKind::*;
match self.kind {
Add => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Add });
}
Mul => {
comp.push(OpCode::LookUp { sym: "__mul".into() });
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
}
Div => {
comp.push(OpCode::LookUp { sym: "__div".into() });
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
}
And => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::And });
}
Or => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Or });
}
Eq => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Eq });
}
Lt => {
comp.push(OpCode::LookUp { sym: "__lessThan".into() });
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
}
Con => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Con });
}
Upd => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Upd });
}
Sub => {
comp.push(OpCode::LookUp { sym: "__sub".into() });
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
}
Impl => {
self.lhs.compile(comp);
comp.push(OpCode::UnOp { op: UnOp::Not });
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Or });
}
Neq => {
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::BinOp { op: BinOp::Eq });
comp.push(OpCode::UnOp { op: UnOp::Not });
}
Gt => {
comp.push(OpCode::LookUp { sym: "__lessThan".into() });
self.rhs.compile(comp);
self.lhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
}
Leq => {
comp.push(OpCode::LookUp { sym: "__lessThan".into() });
self.rhs.compile(comp);
self.lhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
comp.push(OpCode::UnOp { op: UnOp::Not });
}
Geq => {
comp.push(OpCode::LookUp { sym: "__lessThan".into() });
self.lhs.compile(comp);
self.rhs.compile(comp);
comp.push(OpCode::Call { arity: 2 });
comp.push(OpCode::UnOp { op: UnOp::Not });
}
}
}
}
impl Compile for ir::HasAttr {
fn compile(self, comp: &mut Compiler) {
self.lhs.compile(comp);
for attr in self.rhs {
match attr {
ir::Attr::Str(sym) => {
comp.push(OpCode::SelectOrEmpty { sym });
}
ir::Attr::Dynamic(dynamic) => {
dynamic.compile(comp);
comp.push(OpCode::SelectDynamicOrEmpty);
}
ir::Attr::Strs(string) => {
string.compile(comp);
comp.push(OpCode::SelectDynamicOrEmpty);
}
}
}
match comp.pop().unwrap() {
OpCode::SelectOrEmpty { sym } => comp.push(OpCode::HasAttr { sym }),
OpCode::SelectDynamicOrEmpty => comp.push(OpCode::HasDynamicAttr),
_ => unreachable!(),
}
}
}
impl Compile for ir::Select {
fn compile(self, comp: &mut Compiler) {
self.expr.compile(comp);
for attr in self.attrpath {
match attr {
ir::Attr::Str(sym) => comp.push(OpCode::SelectOrEmpty { sym }),
ir::Attr::Dynamic(dynamic) => {
dynamic.compile(comp);
comp.push(OpCode::SelectDynamicOrEmpty);
}
ir::Attr::Strs(string) => {
string.compile(comp);
comp.push(OpCode::SelectDynamicOrEmpty);
}
}
}
match self.default {
Some(default) => {
let last = comp.pop().unwrap();
default.compile(comp);
match last {
OpCode::SelectOrEmpty { sym } => comp.push(OpCode::SelectWithDefault { sym }),
OpCode::SelectDynamicOrEmpty => comp.push(OpCode::SelectDynamicWithDefault),
_ => unreachable!(),
}
}
None => match comp.pop().unwrap() {
OpCode::SelectOrEmpty { sym } => comp.push(OpCode::Select { sym }),
OpCode::SelectDynamicOrEmpty => comp.push(OpCode::SelectDynamic),
_ => unreachable!(),
},
}
}
}
impl Compile for ir::ConcatStrings {
fn compile(self, comp: &mut Compiler) {
let mut iter = self.parts.into_iter();
iter.next().unwrap().compile(comp);
for item in iter {
item.compile(comp);
comp.push(OpCode::ConcatString);
}
}
}
impl Compile for ir::If {
fn compile(self, comp: &mut Compiler) {
self.cond.compile(comp);
let idx_jmp_if_false = comp.idx();
// place holder
comp.push(OpCode::NoOp);
let consq_length = self.consq.compile_with_length(comp);
let idx_jmp = comp.idx();
// place holder
comp.push(OpCode::NoOp);
let alter_length = self.alter.compile_with_length(comp);
comp.modify(idx_jmp_if_false, OpCode::JmpIfFalse { step: consq_length });
comp.modify(idx_jmp, OpCode::Jmp { step: alter_length });
}
}
impl Compile for ir::Let {
fn compile(self, comp: &mut Compiler) {
self.attrs.compile(comp);
comp.push(OpCode::EnterEnv);
self.expr.compile(comp);
comp.push(OpCode::LeaveEnv);
}
}
impl Compile for ir::With {
fn compile(self, comp: &mut Compiler) {
self.namespace.compile(comp);
comp.push(OpCode::EnterEnv);
self.expr.compile(comp);
comp.push(OpCode::LeaveEnv);
}
}
impl Compile for ir::Assert {
fn compile(self, comp: &mut Compiler) {
self.assertion.compile(comp);
comp.push(OpCode::Assert);
self.expr.compile(comp);
}
}
impl Compile for ir::Func {
fn compile(self, comp: &mut Compiler) {
todo!()
}
}
impl Compile for ir::Call {
fn compile(self, comp: &mut Compiler) {
todo!()
}
}
impl Compile for ir::Path {
fn compile(self, comp: &mut Compiler) {
todo!()
}
}

19
src/compile/env.rs Normal file
View File

@@ -0,0 +1,19 @@
use std::collections::HashMap;
use crate::bytecode::SymIdx;
use super::ir::Ir;
pub struct IrEnv {
pub stcs: HashMap<SymIdx, Ir>,
pub dyns: Vec<(Ir, Ir)>,
}
impl IrEnv {
pub fn new() -> IrEnv {
IrEnv {
stcs: HashMap::new(),
dyns: Vec::new(),
}
}
}

772
src/compile/ir.rs Normal file
View File

@@ -0,0 +1,772 @@
// TODO: Error Handling
use std::collections::HashMap;
use anyhow::{anyhow, Result};
use rnix::ast::{self, Expr};
use ecow::EcoString;
use crate::bytecode::{Const as ByteCodeConst, ConstIdx, Consts, ThunkIdx};
use crate::slice::Slice;
use super::compile::*;
use super::env::IrEnv;
use super::symtable::*;
pub fn downgrade(expr: Expr) -> Result<Downgraded> {
let mut state = DowngradeState::new();
let ir = expr.downgrade(&mut state)?;
Ok(Downgraded {
top_level: ir,
consts: state.consts.into(),
thunks: state.thunks.into(),
})
}
#[macro_export]
macro_rules! ir {
(
$(
$(#[$($x:tt)*])*
$ty:ident
=>
{$($name:ident : $elemtype:ty),*$(,)?}
)
,*$(,)?
) => {
use crate::downcast::Downcast;
#[derive(Clone, Debug)]
pub enum Ir {
$(
$ty($ty),
)*
}
impl Ir {
fn boxed(self) -> Box<Self> {
Box::new(self)
}
fn ok(self) -> Result<Self> {
Ok(self)
}
}
impl Compile for Ir {
fn compile(self, state: &mut Compiler) {
match self {
$(Ir::$ty(ir) => ir.compile(state),)*
}
}
}
$(
$(
#[$($x)*]
)*
#[derive(Clone, Debug)]
pub struct $ty {
$(
pub $name : $elemtype,
)*
}
impl $ty {
pub fn ir(self) -> Ir {
Ir::$ty(self)
}
}
impl TryFrom<Ir> for $ty {
type Error = anyhow::Error;
fn try_from(value: Ir) -> Result<Self> {
match value {
Ir::$ty(value) => Ok(value),
_ => Err(anyhow!("")),
}
}
}
impl Downcast<$ty> for Ir {
fn downcast_ref(&self) -> Option<&$ty> {
match self {
Ir::$ty(value) => Some(value),
_ => None,
}
}
fn downcast_mut(&mut self) -> Option<&mut $ty> {
match self {
Ir::$ty(value) => Some(value),
_ => None,
}
}
fn downcast(self) -> core::result::Result<$ty, Self> {
match self {
Ir::$ty(value) => Ok(value),
_ => Err(self),
}
}
}
)*
}
}
ir! {
Attrs => { stcs: HashMap<EcoString, Ir>, dyns: Vec<DynamicAttrPair> },
RecAttrs => { stcs: HashMap<EcoString, Ir>, dyns: Vec<DynamicAttrPair> },
List => { items: Vec<Ir> },
HasAttr => { lhs: Box<Ir>, rhs: Vec<Attr> },
BinOp => { lhs: Box<Ir>, rhs: Box<Ir>, kind: BinOpKind },
UnOp => { rhs: Box<Ir>, kind: UnOpKind },
Select => { expr: Box<Ir>, attrpath: Vec<Attr>, default: Option<Box<Ir>> },
If => { cond: Box<Ir>, consq: Box<Ir>, alter: Box<Ir> },
Func => { args: Vec<Param>, body: Box<Ir> },
Call => { func: Box<Ir>, args: Vec<Ir> },
Let => { attrs: Attrs, expr: Box<Ir> },
With => { namespace: Box<Ir>, expr: Box<Ir> },
Assert => { assertion: Box<Ir>, expr: Box<Ir> },
ConcatStrings => { parts: Vec<Ir> },
Const => { value: ByteCodeConst },
Var => { sym: EcoString },
#[derive(Copy)]
Thunk => { idx: ThunkIdx },
Path => { expr: Box<Ir> },
}
#[derive(Clone, Debug)]
pub struct DynamicAttrPair(pub Ir, pub Ir);
enum Env {
Env(IrEnv),
With,
}
impl Env {
fn env(&self) -> &IrEnv {
match self {
Env::Env(env) => env,
_ => panic!(),
}
}
fn env_mut(&mut self) -> &mut IrEnv {
match self {
Env::Env(env) => env,
_ => panic!(),
}
}
}
#[derive(Debug)]
pub struct DowngradeError {
errno: u16,
text: String,
}
pub struct DowngradeState {
sym_table: SymTable,
envs: Vec<Env>,
thunks: Vec<Ir>,
consts: Vec<ByteCodeConst>,
consts_table: HashMap<ByteCodeConst, ConstIdx>,
}
pub struct Downgraded {
pub top_level: Ir,
pub consts: Consts,
pub thunks: Slice<Ir>,
}
impl DowngradeState {
fn new() -> DowngradeState {
DowngradeState {
sym_table: SymTable::new(),
envs: Vec::new(),
thunks: Vec::new(),
consts: Vec::new(),
consts_table: HashMap::new(),
}
}
fn new_thunk(&mut self, thunk: Ir) -> Thunk {
let idx = self.thunks.len();
self.thunks.push(thunk);
Thunk { idx }
}
fn lookup_thunk(&self, idx: ThunkIdx) -> &Ir {
self.thunks.get(idx).unwrap()
}
}
impl Attrs {
fn _insert(&mut self, mut path: std::vec::IntoIter<Attr>, name: Attr, value: Ir) -> Result<()> {
if let Some(attr) = path.next() {
match attr {
Attr::Str(ident) => {
if self.stcs.get(&ident).is_some() {
self.stcs
.get_mut(&ident)
.unwrap()
.downcast_mut()
.ok_or(anyhow!(r#""{ident}" already exsists in this set"#))
.and_then(|attrs: &mut Attrs| attrs._insert(path, name, value))
} else {
let mut attrs = Attrs {
stcs: HashMap::new(),
dyns: Vec::new(),
};
attrs._insert(path, name, value)?;
assert!(self.stcs.insert(ident, attrs.ir()).is_none());
Ok(())
}
}
Attr::Strs(string) => {
let mut attrs = Attrs {
stcs: HashMap::new(),
dyns: Vec::new(),
};
attrs._insert(path, name, value)?;
self.dyns.push(DynamicAttrPair(string.ir(), attrs.ir()));
Ok(())
}
Attr::Dynamic(dynamic) => {
let mut attrs = Attrs {
stcs: HashMap::new(),
dyns: Vec::new(),
};
attrs._insert(path, name, value)?;
self.dyns.push(DynamicAttrPair(dynamic, attrs.ir()));
Ok(())
}
}
} else {
match name {
Attr::Str(ident) => {
if self.stcs.get(&ident).is_some() {
return Err(anyhow!(r#""{ident}" already exsists in this set"#));
}
self.stcs.insert(ident, value);
}
Attr::Strs(string) => {
self.dyns.push(DynamicAttrPair(string.ir(), value));
}
Attr::Dynamic(dynamic) => {
self.dyns.push(DynamicAttrPair(dynamic, value));
}
}
Ok(())
}
}
pub fn insert(&mut self, path: Vec<Attr>, value: Ir) -> Result<()> {
let mut path = path.into_iter();
let name = path.next_back().unwrap();
self._insert(path, name, value)
}
fn _has_attr(&self, mut path: std::slice::Iter<Attr>, name: Attr) -> Option<bool> {
match path.next() {
Some(Attr::Str(ident)) => self
.stcs
.get(ident.as_str())
.and_then(|attrs| attrs.downcast_ref())
.map_or(Some(false), |attrs: &Attrs| attrs._has_attr(path, name)),
None => match name {
Attr::Str(ident) => Some(self.stcs.get(&ident).is_some()),
_ => None,
},
_ => None,
}
}
pub fn has_attr(&self, path: &[Attr]) -> Option<bool> {
let mut path = path.iter();
let name = path.next_back().unwrap().clone();
self._has_attr(path, name)
}
}
#[derive(Clone, Debug)]
pub enum Attr {
Dynamic(Ir),
Strs(ConcatStrings),
Str(EcoString),
}
#[derive(Clone, Debug)]
pub enum BinOpKind {
Add,
Sub,
Div,
Mul,
Eq,
Neq,
Lt,
Gt,
Leq,
Geq,
And,
Or,
Impl,
Con,
Upd,
}
impl From<ast::BinOpKind> for BinOpKind {
fn from(op: ast::BinOpKind) -> Self {
use ast::BinOpKind as astkind;
use BinOpKind::*;
match op {
astkind::Concat => Con,
astkind::Update => Upd,
astkind::Add => Add,
astkind::Sub => Sub,
astkind::Mul => Mul,
astkind::Div => Div,
astkind::And => And,
astkind::Equal => Eq,
astkind::Implication => Impl,
astkind::Less => Lt,
astkind::LessOrEq => Leq,
astkind::More => Gt,
astkind::MoreOrEq => Geq,
astkind::NotEqual => Neq,
astkind::Or => Or,
}
}
}
#[derive(Clone, Debug)]
pub enum UnOpKind {
Neg,
Not,
}
impl From<ast::UnaryOpKind> for UnOpKind {
fn from(value: ast::UnaryOpKind) -> Self {
match value {
ast::UnaryOpKind::Invert => UnOpKind::Not,
ast::UnaryOpKind::Negate => UnOpKind::Neg,
}
}
}
#[derive(Clone, Debug)]
pub enum Param {
Ident(EcoString),
Formals {
formals: Vec<(EcoString, Option<Ir>)>,
ellipsis: bool,
alias: Option<EcoString>,
},
}
trait Downgrade
where
Self: Sized
{
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir>;
}
impl Downgrade for Expr {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
match self {
Expr::Apply(apply) => apply.downgrade(state),
Expr::Assert(assert) => assert.downgrade(state),
Expr::Error(error) => return Err(anyhow!(error.to_string())),
Expr::IfElse(ifelse) => ifelse.downgrade(state),
Expr::Select(select) => select.downgrade(state),
Expr::Str(str) => str.downgrade(state),
Expr::Path(path) => path.downgrade(state),
Expr::Literal(lit) => lit.downgrade(state),
Expr::Lambda(lambda) => lambda.downgrade(state),
Expr::LegacyLet(let_) => let_.downgrade(state),
Expr::LetIn(letin) => letin.downgrade(state),
Expr::List(list) => list.downgrade(state),
Expr::BinOp(op) => op.downgrade(state),
Expr::Paren(paren) => paren.expr().unwrap().downgrade(state),
Expr::Root(root) => root.expr().unwrap().downgrade(state),
Expr::AttrSet(attrs) => attrs.downgrade(state),
Expr::UnaryOp(op) => op.downgrade(state),
Expr::Ident(ident) => ident.downgrade(state),
Expr::With(with) => with.downgrade(state),
Expr::HasAttr(has) => has.downgrade(state),
}
}
}
impl Downgrade for ast::Assert {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
Assert {
assertion: self.condition().unwrap().downgrade(state)?.boxed(),
expr: self.body().unwrap().downgrade(state)?.boxed(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::IfElse {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
If {
cond: self.condition().unwrap().downgrade(state)?.boxed(),
consq: self.body().unwrap().downgrade(state)?.boxed(),
alter: self.else_body().unwrap().downgrade(state)?.boxed(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::Path {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let parts = self
.parts()
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Const { value: lit.to_string().into() }
.ir()
.ok(),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(state)
}
})
.collect::<Result<Vec<_>>>()?;
if parts.len() == 1 {
Path {
expr: parts.into_iter().next().unwrap().boxed(),
}
} else {
Path {
expr: ConcatStrings { parts }.ir().boxed(),
}
}
.ir()
.ok()
}
}
impl Downgrade for ast::Str {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let parts = self
.normalized_parts()
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => {
Const { value: lit.into() }.ir().ok()
}
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(state)
}
})
.collect::<Result<Vec<_>>>()?;
if parts.len() == 1 {
Ok(parts.into_iter().next().unwrap())
} else {
ConcatStrings { parts }.ir().ok()
}
}
}
impl Downgrade for ast::Literal {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
match self.kind() {
ast::LiteralKind::Integer(int) => Const { value: int.value().unwrap().into() },
ast::LiteralKind::Float(float) => Const { value: float.value().unwrap().into() },
ast::LiteralKind::Uri(uri) => Const { value: uri.to_string().into() },
}
.ir()
.ok()
}
}
impl Downgrade for ast::Ident {
fn downgrade(self, _state: &mut DowngradeState) -> Result<Ir> {
Var {
sym: self.to_string().into()
}
.ir()
.ok()
}
}
impl Downgrade for ast::AttrSet {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let rec = self.rec_token().is_some();
downgrade_has_entry(self, rec, state).map(|attrs| attrs.ir())
}
}
impl Downgrade for ast::List {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let mut items = Vec::with_capacity(self.items().size_hint().0);
for item in self.items() {
items.push(item.downgrade(state)?)
}
List { items }.ir().ok()
}
}
impl Downgrade for ast::BinOp {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
BinOp {
lhs: self.lhs().unwrap().downgrade(state)?.boxed(),
rhs: self.rhs().unwrap().downgrade(state)?.boxed(),
kind: self.operator().unwrap().into(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::HasAttr {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let attrs = self.expr().unwrap().downgrade(state)?;
let path = downgrade_attrpath(self.attrpath().unwrap(), state)?;
if let Some(attrs) = Downcast::<Attrs>::downcast_ref(&attrs) {
if let Some(res) = attrs.has_attr(&path) {
return Const { value: res.into() }.ir().ok();
}
}
HasAttr {
lhs: attrs.boxed(),
rhs: path,
}
.ir()
.ok()
}
}
impl Downgrade for ast::UnaryOp {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
UnOp {
rhs: self.expr().unwrap().downgrade(state)?.boxed(),
kind: self.operator().unwrap().into(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::Select {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
Select {
expr: self.expr().unwrap().downgrade(state)?.boxed(),
attrpath: downgrade_attrpath(self.attrpath().unwrap(), state)?,
default: match self.default_expr() {
Some(default) => Some(default.downgrade(state)?.boxed()),
None => None,
},
}
.ir()
.ok()
}
}
impl Downgrade for ast::LegacyLet {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let attrs = downgrade_has_entry(self, true, state)?;
Select {
expr: attrs.ir().boxed(),
attrpath: vec![Attr::Str("body".to_string().into())],
default: None,
}
.ir()
.ok()
}
}
impl Downgrade for ast::LetIn {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let body = self.body().unwrap();
let attrs = downgrade_has_entry(self, true, state)?;
let expr = body.downgrade(state)?.boxed();
Let {
attrs,
expr
}
.ir()
.ok()
}
}
impl Downgrade for ast::With {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let namespace = self.namespace().unwrap().downgrade(state)?;
if let Ir::Attrs(attrs) = namespace {
let expr = self.body().unwrap().downgrade(state)?.boxed();
Let { attrs, expr }.ir().ok()
} else {
let namespace = namespace.boxed();
let expr = self.body().unwrap().downgrade(state)?.boxed();
With { namespace, expr }.ir().ok()
}
}
}
impl Downgrade for ast::Lambda {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let mut body = self.body().unwrap();
let mut args = vec![downgrade_param(self.param().unwrap(), state)?];
while let ast::Expr::Lambda(func) = body {
body = func.body().unwrap();
args.push(downgrade_param(func.param().unwrap(), state)?);
}
let body = body.downgrade(state)?.boxed();
Func { args, body }.ir().ok()
}
}
impl Downgrade for ast::Apply {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let mut args = vec![self.argument().unwrap().downgrade(state)?];
let mut func = self.lambda().unwrap();
while let ast::Expr::Apply(call) = func {
func = call.lambda().unwrap();
args.push(call.argument().unwrap().downgrade(state)?);
}
let func = func.downgrade(state)?.boxed();
args.reverse();
Call { func, args }.ir().ok()
}
}
fn downgrade_param(param: ast::Param, state: &mut DowngradeState) -> Result<Param> {
match param {
ast::Param::IdentParam(ident) => {
Ok(Param::Ident(ident.to_string().into()))
}
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, state),
}
}
fn downgrade_pattern(pattern: ast::Pattern, state: &mut DowngradeState) -> Result<Param> {
let formals = pattern
.pat_entries()
.map(|entry| {
let ident = entry.ident().unwrap().to_string().into();
if entry.default().is_none() {
Ok((ident, None))
} else {
entry
.default()
.unwrap()
.downgrade(state)
.map(|ok| (ident, Some(ok)))
}
})
.collect::<Result<Vec<_>>>()?;
let ellipsis = pattern.ellipsis_token().is_some();
let alias = pattern
.pat_bind()
.map(|alias| alias.ident().unwrap().to_string().into());
Ok(Param::Formals {
formals,
ellipsis,
alias,
})
}
fn downgrade_has_entry(
has_entry: impl ast::HasEntry,
rec: bool,
state: &mut DowngradeState,
) -> Result<Attrs> {
let entires = has_entry.entries();
let mut attrs = Attrs {
stcs: HashMap::new(),
dyns: Vec::new(),
};
for entry in entires {
match entry {
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, state)?,
ast::Entry::AttrpathValue(value) => {
downgrade_attrpathvalue(value, &mut attrs, state)?
}
}
}
Ok(attrs)
}
fn downgrade_inherit(
inherit: ast::Inherit,
stcs: &mut HashMap<EcoString, Ir>,
state: &mut DowngradeState,
) -> Result<()> {
let from = if let Some(from) = inherit.from() {
let from = from.expr().unwrap().downgrade(state)?;
Some(state.new_thunk(from))
} else {
None
};
for attr in inherit.attrs() {
let ident: EcoString = match downgrade_attr(attr, state)? {
Attr::Str(ident) => ident.to_string().into(),
_ => return Err(anyhow!("dynamic attributes not allowed in inherit")),
};
let expr = from.map_or_else(
|| Var { sym: ident.clone() }.ir().ok(),
|from| {
Ok(Select {
expr: from.ir().boxed(),
attrpath: vec![Attr::Str(ident.clone())],
default: None,
}
.ir())
},
)?;
stcs.insert(ident, expr).unwrap();
}
Ok(())
}
fn downgrade_attr(attr: ast::Attr, state: &mut DowngradeState) -> Result<Attr> {
match attr {
ast::Attr::Ident(ident) => Ok(Attr::Str(ident.to_string().into())),
ast::Attr::Str(string) => {
let parts = string.normalized_parts();
if parts.len() == 1 {
let ast::InterpolPart::Literal(ident) = parts.into_iter().next().unwrap() else {
unreachable!()
};
Ok(Attr::Str(ident.into()))
} else {
let parts = parts
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => {
Const { value: lit.into() }.ir().ok()
}
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(state)
}
})
.collect::<Result<Vec<_>>>()?;
Ok(Attr::Strs(ConcatStrings { parts }))
}
}
ast::Attr::Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(state)?)),
}
}
fn downgrade_attrpath(attrpath: ast::Attrpath, state: &mut DowngradeState) -> Result<Vec<Attr>> {
attrpath
.attrs()
.map(|attr| downgrade_attr(attr, state))
.collect::<Result<Vec<_>>>()
}
fn downgrade_attrpathvalue(
value: ast::AttrpathValue,
attrs: &mut Attrs,
state: &mut DowngradeState,
) -> Result<()> {
let path = downgrade_attrpath(value.attrpath().unwrap(), state)?;
let value = value.value().unwrap().downgrade(state)?;
attrs.insert(path, value)
}

10
src/compile/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
mod compile;
mod env;
mod ir;
mod symtable;
pub fn compile(expr: &str) -> anyhow::Result<crate::bytecode::Program> {
let expr = rnix::Root::parse(expr).tree().expr().unwrap();
let ir = ir::downgrade(expr)?;
Ok(compile::compile(ir))
}

35
src/compile/symtable.rs Normal file
View File

@@ -0,0 +1,35 @@
use std::collections::HashMap;
use crate::bytecode::SymIdx;
use crate::slice::Slice;
// FIXME: don't store syms twice to make it more memory efficient?
pub struct SymTable {
syms: Vec<String>,
syms_table: HashMap<String, SymIdx>,
}
impl SymTable {
pub fn new() -> SymTable {
SymTable {
syms: Vec::new(),
syms_table: HashMap::new(),
}
}
pub fn lookup(&mut self, name: String) -> SymIdx {
if let Some(sym) = self.syms_table.get(&name) {
*sym
} else {
let sym = self.syms.len();
self.syms.push(name.clone());
self.syms_table.insert(name, sym);
sym
}
}
pub fn syms(self) -> Slice<String> {
self.syms.into()
}
}

8
src/downcast.rs Normal file
View File

@@ -0,0 +1,8 @@
pub trait Downcast<T: Sized>
where
Self: Sized,
{
fn downcast_ref(&self) -> Option<&T>;
fn downcast_mut(&mut self) -> Option<&mut T>;
fn downcast(self) -> Result<T, Self>;
}

9
src/lib.rs Normal file
View File

@@ -0,0 +1,9 @@
#![allow(dead_code)]
mod builtins;
mod bytecode;
mod compile;
mod downcast;
mod slice;
mod value;
mod vm;

2
src/slice.rs Normal file
View File

@@ -0,0 +1,2 @@
pub type Slice<T> = Box<[T]>;

108
src/value/mod.rs Normal file
View File

@@ -0,0 +1,108 @@
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ops::Deref;
use std::sync::Arc;
use derive_more::{Constructor, IsVariant, Unwrap};
use ecow::EcoString;
use rpds::{HashTrieMapSync, VectorSync};
use crate::bytecode::{Args, Const, OpCodes};
#[derive(Debug, Clone, Hash, PartialEq, Eq, Constructor)]
pub struct Symbol(EcoString);
impl<T: Into<EcoString>> From<T> for Symbol {
fn from(value: T) -> Self {
Symbol(value.into())
}
}
impl Deref for Symbol {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, Hash)]
pub struct Func {
pub args: Args,
pub opcodes: OpCodes,
}
impl PartialEq for Func {
fn eq(&self, _: &Self) -> bool {
false
}
}
/* #[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
pub enum Const {
Int(i64),
Float(f64),
Bool(bool),
String(EcoString),
Func(Arc<Func>),
}
impl Display for Const {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "Const::")?;
match self {
Const::Int(int) => write!(f, "Int@{}", int),
Const::Float(float) => write!(f, "Float@{}", float),
Const::Bool(bool) => write!(f, "Bool@{}", bool),
Const::String(string) => write!(f, r#"String@"{}""#, string.as_ref()),
Const::Func(func) => write!(f, "Func@{:?}", func.as_ref() as *const Func),
}
}
}
impl From<ByteCodeConst> for Const {
fn from(value: ByteCodeConst) -> Self {
use ByteCodeConst::*;
match value {
Int(int) => Const::Int(int),
Float(float) => Const::Float(float),
Bool(bool) => Const::Bool(bool),
String(string) => Const::String(EcoString::from(string)),
Func(func) => Const::Func(Arc::new(func)),
}
}
} */
#[derive(Constructor, Clone, PartialEq)]
pub struct AttrSet {
data: HashTrieMapSync<Symbol, Value>,
}
impl Debug for AttrSet {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{{ ")?;
for (k, v) in self.data.iter() {
write!(f, "{k:?} = {v:?}; ")?;
}
write!(f, "}}")
}
}
#[derive(Constructor, Clone, Debug, PartialEq)]
pub struct List {
data: VectorSync<Value>,
}
#[derive(Clone, Debug, PartialEq, Constructor)]
pub struct Catchable {
msg: Option<String>
}
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
pub enum Value {
Const(Const),
AttrSet(AttrSet),
List(List),
Catchable(Catchable),
Thunk,
PrimOp,
PartialPrimOp,
}

47
src/vm/env.rs Normal file
View File

@@ -0,0 +1,47 @@
use rpds::HashTrieMapSync;
use super::value::{Symbol, VmValue};
pub struct Env {
last: Option<*mut Env>,
map: HashTrieMapSync<Symbol, VmValue>,
}
impl Env {
pub fn empty() -> Env {
Env {
last: None,
map: HashTrieMapSync::new_sync(),
}
}
pub fn lookup(&self, symbol: Symbol) -> VmValue {
if let Some(value) = self.map.get(&symbol) {
value.clone()
} else {
let last = unsafe { &*self.last.unwrap() };
last.lookup(symbol)
}
}
pub fn insert(&mut self, symbol: Symbol, value: VmValue) {
self.map.insert_mut(symbol, value);
}
pub fn enter(&mut self, map: HashTrieMapSync<Symbol, VmValue>) {
let last = std::mem::replace(
self,
Env {
last: None,
map,
},
);
self.last = Some(Box::leak(Box::new(last)) as *mut Env);
}
pub fn leave(&mut self) {
let last = unsafe { &*self.last.unwrap() };
self.last = last.last;
self.map = last.map.clone();
}
}

12
src/vm/mod.rs Normal file
View File

@@ -0,0 +1,12 @@
mod env;
mod stack;
mod value;
mod vm;
mod vmthunk;
#[cfg(test)]
mod test;
pub use env::Env;
pub use value::Symbol;
pub use value::VmValue as Value;

93
src/vm/stack.rs Normal file
View File

@@ -0,0 +1,93 @@
use std::mem::{size_of, transmute, MaybeUninit};
use std::ops::Deref;
use anyhow::{anyhow, Result};
use super::value::VmValue;
pub const STACK_SIZE: usize = 8 * 1024 / size_of::<VmValue>();
pub struct Stack<const CAP: usize> {
items: Box<[MaybeUninit<VmValue>; CAP]>,
top: usize,
}
impl<const CAP: usize> Stack<CAP> {
pub fn new() -> Self {
Stack {
items: (0..CAP)
.map(|_| MaybeUninit::uninit())
.collect::<Box<_>>()
.try_into()
.unwrap(),
top: 0,
}
}
pub fn push(&mut self, item: VmValue) -> Result<()> {
self.items
.get_mut(self.top)
.map_or(Err(anyhow!("stack overflow")), |ok| Ok(ok))?
.write(item);
self.top += 1;
Ok(())
}
pub fn pop(&mut self) -> Result<VmValue> {
self.top -= 1;
let item = self
.items
.get_mut(self.top)
.map_or(Err(anyhow!("stack empty")), |ok| Ok(ok))?;
unsafe { Ok(std::mem::replace(item, MaybeUninit::uninit()).assume_init()) }
}
pub fn tos(&self) -> Result<&VmValue> {
if self.top == 0 {
Err(anyhow!(""))
} else {
unsafe { Ok(transmute(self.items.get(self.top - 1).unwrap())) }
}
}
pub fn tos_mut(&mut self) -> Result<&mut VmValue> {
if self.top == 0 {
Err(anyhow!(""))
} else {
unsafe { Ok(transmute(self.items.get_mut(self.top - 1).unwrap())) }
}
}
pub fn with_tos(&self, func: impl Fn(&VmValue)) -> Result<()> {
if self.top != 0 {
Err(anyhow!(""))
} else {
unsafe { func(transmute(self.items.get(self.top - 1).unwrap())) }
Ok(())
}
}
pub fn with_tos_mut(&mut self, func: impl Fn(&mut VmValue)) -> Result<()> {
if self.top != 0 {
Err(anyhow!(""))
} else {
unsafe { func(transmute(self.items.get_mut(self.top - 1).unwrap())) }
Ok(())
}
}
}
impl<const CAP: usize> Deref for Stack<CAP> {
type Target = [VmValue];
fn deref(&self) -> &Self::Target {
unsafe { transmute(&self.items[0..self.top]) }
}
}
impl<const CAP: usize> Drop for Stack<CAP> {
fn drop(&mut self) {
self.items.as_mut_slice()[0..self.top]
.iter_mut()
.map(|item| unsafe { item.assume_init_drop() })
.for_each(drop)
}
}

169
src/vm/test.rs Normal file
View File

@@ -0,0 +1,169 @@
use ecow::EcoString;
use rpds::{ht_map_sync, vector_sync};
use crate::compile::compile;
use crate::value::*;
use crate::bytecode::Const;
use super::vm::run;
#[inline]
fn test_expr(expr: &str, expected: Value) {
let prog = compile(expr).unwrap();
dbg!(&prog);
assert_eq!(run(prog).unwrap(), expected);
}
macro_rules! int {
($e:expr) => {
Value::Const(Const::Int($e))
};
}
macro_rules! float {
($e:expr) => {
Value::Const(Const::Float($e as f64))
};
}
macro_rules! boolean {
($e:expr) => {
Value::Const(Const::Bool($e))
};
}
macro_rules! string {
($e:expr) => {
Value::Const(Const::String(EcoString::from($e)))
};
}
macro_rules! symbol {
($e:expr) => {
Symbol::from($e.to_string())
};
}
macro_rules! list {
($($x:tt)*) => (
Value::List(List::new(vector_sync![$($x)*]))
);
}
macro_rules! attrs {
($($x:tt)*) => (
Value::AttrSet(AttrSet::new(ht_map_sync!{$($x)*}))
)
}
#[test]
fn test_arith() {
test_expr("1", int!(1));
test_expr("1.", float!(1));
test_expr("-1", int!(-1));
test_expr("-1.", float!(-1));
test_expr("1 + 1", int!(2));
test_expr("1 + 1.", float!(2));
test_expr("1. + 1", float!(2));
test_expr("1. + 1.", float!(2));
test_expr("1 - 1", int!(0));
test_expr("1 - 1.", float!(0));
test_expr("1. - 1", float!(0));
test_expr("1. - 1.", float!(0));
test_expr("1 * 1", int!(1));
test_expr("1 * 1.", float!(1));
test_expr("1. * 1", float!(1));
test_expr("1. * 1.", float!(1));
test_expr("1 / 1", int!(1));
test_expr("1 / 1.", float!(1));
test_expr("1. / 1", float!(1));
test_expr("1. / 1.", float!(1));
}
#[test]
fn test_cmp() {
test_expr("1 < 2", boolean!(true));
test_expr("1 < 1", boolean!(false));
test_expr("1 > 0", boolean!(true));
test_expr("1 > 1", boolean!(false));
test_expr("1 <= 1", boolean!(true));
test_expr("1 <= 0", boolean!(false));
test_expr("1 >= 1", boolean!(true));
test_expr("1 >= 2", boolean!(false));
}
#[test]
fn test_string() {
test_expr(r#""test""#, string!("test"));
test_expr(r#""hello" + " world""#, string!("hello world"));
}
#[test]
fn test_bool() {
test_expr("true", boolean!(true));
test_expr("false", boolean!(false));
test_expr("!false", boolean!(true));
test_expr("true && false", boolean!(false));
test_expr("true || false", boolean!(true));
test_expr("true -> false", boolean!(false));
}
#[test]
fn test_list() {
test_expr(
"[ 1 2 3 true ]",
list![int!(1), int!(2), int!(3), boolean!(true)],
);
test_expr(
"[ 1 2 ] ++ [ 3 4 ]",
list![int!(1), int!(2), int!(3), int!(4)],
);
}
#[test]
fn test_attrs() {
test_expr(
"{ a = 1; }",
attrs! {
symbol!("a") => int!(1)
},
);
test_expr("{ a = 1; }.a", int!(1));
test_expr("{ a = 1; }.b or 1", int!(1));
test_expr(
"{ a = { a = 1; }; }.a",
attrs! {
symbol!("a") => int!(1)
},
);
test_expr("{ a.b = 1; }.a.b", int!(1));
test_expr(
"{ a.b = 1; a.c = 2; }",
attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("c") => int!(2) } },
);
test_expr("{ a.b = 1; } ? a.b", boolean!(true));
test_expr(
"{ a.b = 1; } // { a.c = 2 }",
attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("c") => int!(2) } },
);
}
#[test]
fn test_if() {
test_expr("if true || false then 1 else 2", int!(1));
}
#[test]
fn test_with() {
test_expr(r#"with { a = 1; }; a"#, int!(1));
}
#[test]
fn test_let() {
test_expr(r#"let a = 1; in a"#, int!(1));
test_expr(r#"let a = { a = 1; }; b = "a"; in a.${b}"#, int!(1));
test_expr(
r#"let b = "c"; in { a.b = 1; } // { a."a${b}" = 2 }"}"#,
attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("ac") => int!(2) } },
);
}

53
src/vm/value/attrset.rs Normal file
View File

@@ -0,0 +1,53 @@
use derive_more::Constructor;
use rpds::HashTrieMapSync;
use crate::value::{self, Value};
use super::super::vm::VM;
use super::{Symbol, ToValue, VmValue};
#[derive(Debug, Constructor, Clone, PartialEq)]
pub struct AttrSet {
data: HashTrieMapSync<Symbol, VmValue>,
}
impl AttrSet {
pub fn push_attr(&mut self, sym: Symbol, val: VmValue) {
self.data.insert_mut(sym, val);
}
pub fn select(&self, sym: Symbol) -> Option<VmValue> {
self.data.get(&sym).cloned()
}
pub fn has_attr(&self, sym: Symbol) -> bool {
self.data.get(&sym).is_some()
}
pub fn update(mut self, other: AttrSet) -> AttrSet {
for (k, v) in other.data.iter() {
if let Some(attr) = self.data.get(k) {
let new_attr = attr.clone().update(v.clone());
self.data.insert_mut(k.clone(), new_attr);
} else {
self.push_attr(k.clone(), v.clone())
}
}
self
}
pub fn to_data(self) -> HashTrieMapSync<Symbol, VmValue> {
self.data
}
}
impl ToValue for AttrSet {
fn to_value(self, vm: &VM) -> Value {
Value::AttrSet(value::AttrSet::new(
self.data
.iter()
.map(|(sym, value)| (value::Symbol::new(sym.0.clone()), value.clone().to_value(vm)))
.collect(),
))
}
}

36
src/vm/value/list.rs Normal file
View File

@@ -0,0 +1,36 @@
use derive_more::Constructor;
use rpds::VectorSync;
use crate::value::{self, Value};
use super::super::vm::VM;
use super::{ToValue, VmValue};
#[derive(Debug, Constructor, Clone, PartialEq)]
pub struct List {
data: VectorSync<VmValue>,
}
impl List {
pub fn push(&mut self, elem: VmValue) {
self.data.push_back_mut(elem);
}
pub fn concat(mut self, other: List) -> List {
for elem in other.data.iter() {
self.data.push_back_mut(elem.clone());
}
self
}
}
impl ToValue for List {
fn to_value(self, vm: &VM) -> Value {
Value::List(value::List::new(
self.data
.iter()
.map(|value| value.clone().to_value(vm))
.collect(),
))
}
}

251
src/vm/value/mod.rs Normal file
View File

@@ -0,0 +1,251 @@
use derive_more::{Constructor, IsVariant, Unwrap};
use anyhow::Result;
use ecow::EcoString;
use crate::value::*;
use crate::bytecode::Const;
use super::vm::VM;
use super::env::Env;
mod attrset;
mod list;
mod string;
pub use attrset::AttrSet;
pub use list::List;
pub use string::ContextfulString;
pub trait ToValue {
fn to_value(self, vm: &VM) -> Value;
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Constructor)]
pub struct Symbol(EcoString);
impl<T: Into<EcoString>> From<T> for Symbol {
fn from(value: T) -> Self {
Symbol(value.into())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Constructor)]
pub struct Thunk(usize);
#[derive(Debug, IsVariant, Unwrap, Clone, PartialEq)]
pub enum VmValue {
Const(Const),
Thunk(Thunk),
AttrSet(AttrSet),
List(List),
Catchable(crate::value::Catchable),
PrimOp(crate::builtins::PrimOp),
PartialPrimOp(crate::builtins::PartialPrimOp)
}
use VmValue::Const as VmConst;
impl VmValue {
pub fn call(self, args: Vec<VmValue>) -> VmValue {
match self {
VmValue::PrimOp(func) => func.call(args),
VmValue::PartialPrimOp(func) => func.call(args),
_ => todo!()
}
}
pub fn not(self) -> VmValue {
use Const::*;
match self {
VmConst(Bool(bool)) => VmConst(Bool(!bool)),
_ => todo!(),
}
}
pub fn and(self, other: VmValue) -> VmValue {
use Const::*;
match (self, other) {
(VmConst(Bool(a)), VmConst(Bool(b))) => VmConst(Bool(a && b)),
_ => todo!(),
}
}
pub fn or(self, other: VmValue) -> VmValue {
use Const::*;
match (self, other) {
(VmConst(Bool(a)), VmConst(Bool(b))) => VmConst(Bool(a || b)),
_ => todo!(),
}
}
pub fn eq(self, other: VmValue) -> VmValue {
use Const::Bool;
VmConst(Bool(self == other))
}
pub fn lt(self, other: VmValue) -> VmValue {
use Const::*;
VmConst(Bool(match (self, other) {
(VmConst(Int(a)), VmConst(Int(b))) => a < b,
(VmConst(Int(a)), VmConst(Float(b))) => (a as f64) < b,
(VmConst(Float(a)), VmConst(Int(b))) => a < b as f64,
(VmConst(Float(a)), VmConst(Float(b))) => a < b,
(VmConst(String(a)), VmConst(String(b))) => a < b,
_ => todo!()
}))
}
pub fn neg(self) -> VmValue {
use Const::*;
match self {
VmConst(Int(int)) => VmConst(Int(-int)),
VmConst(Float(float)) => VmConst(Float(-float)),
_ => todo!(),
}
}
pub fn add(self, other: VmValue) -> VmValue {
use Const::*;
match (self, other) {
(VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a + b)),
(VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 + b)),
(VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a + b as f64)),
(VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a + b)),
(VmConst(String(a)), VmConst(String(b))) => {
let mut string = a.clone();
string.push_str(b.as_str());
VmConst(String(string))
}
_ => todo!(),
}
}
pub fn mul(self, other: VmValue) -> VmValue {
use Const::*;
match (self, other) {
(VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a * b)),
(VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 * b)),
(VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a * b as f64)),
(VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a * b)),
_ => todo!(),
}
}
pub fn div(self, other: VmValue) -> VmValue {
use Const::*;
match (self, other) {
(_, VmConst(Int(0))) => todo!(),
(_, VmConst(Float(0.))) => todo!(),
(VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a / b)),
(VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 / b)),
(VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a / b as f64)),
(VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a / b)),
_ => todo!(),
}
}
pub fn concat_string(&mut self, mut other: VmValue) -> &mut Self {
if let (VmConst(Const::String(a)), VmConst(Const::String(b))) = (self.coerce_to_string(), other.coerce_to_string()) {
a.push_str(b.as_str());
} else {
todo!()
}
self
}
pub fn push(&mut self, elem: VmValue) -> &mut Self {
if let VmValue::List(list) = self {
list.push(elem);
} else {
todo!()
}
self
}
pub fn concat(self, other: VmValue) -> VmValue {
if let (VmValue::List(a), VmValue::List(b)) = (self, other) {
VmValue::List(a.concat(b))
} else {
todo!()
}
}
pub fn push_attr(&mut self, sym: Symbol, val: VmValue) -> &mut Self {
if let VmValue::AttrSet(attrs) = self {
attrs.push_attr(sym, val)
} else {
todo!()
}
self
}
pub fn update(self, other: VmValue) -> VmValue {
if let (VmValue::AttrSet(a), VmValue::AttrSet(b)) = (self, other) {
VmValue::AttrSet(a.update(b))
} else {
todo!()
}
}
pub fn select(&mut self, sym: Symbol) -> &mut Self {
if let VmValue::AttrSet(attrs) = self {
let val = attrs
.select(sym.clone())
.unwrap_or(VmValue::Catchable(Catchable::new(Some(format!("{sym:?} not found")))));
*self = val;
} else {
todo!()
}
self
}
pub fn select_with_default(&mut self, sym: Symbol, default: VmValue) -> &mut Self {
if let VmValue::AttrSet(attrs) = self {
let val = attrs.select(sym).unwrap_or(default);
*self = val;
} else {
todo!()
}
self
}
pub fn has_attr(&mut self, sym: Symbol) -> &mut Self {
if let VmValue::AttrSet(attrs) = self {
let val = VmConst(Const::Bool(attrs.has_attr(sym)));
*self = val;
} else {
*self = VmConst(Const::Bool(false));
}
self
}
pub fn coerce_to_string(&mut self) -> &mut Self {
if let VmConst(Const::String(_)) = self {
()
} else {
todo!()
}
self
}
pub fn force(&mut self, vm: &VM, env: &mut Env) -> Result<&mut Self> {
if let VmValue::Thunk(thunk) = self {
let value = vm.get_thunk_value(thunk.0, env)?;
*self = value
}
Ok(self)
}
}
impl ToValue for VmValue {
fn to_value(self, vm: &VM) -> Value {
match self {
VmValue::AttrSet(attrs) => attrs.to_value(vm),
VmValue::List(list) => list.to_value(vm),
VmValue::Catchable(catchable) => Value::Catchable(catchable),
VmValue::Const(cnst) => Value::Const(cnst),
VmValue::Thunk(_) => Value::Thunk,
VmValue::PrimOp(_) => Value::PrimOp,
VmValue::PartialPrimOp(_) => Value::PartialPrimOp,
}
}
}

30
src/vm/value/string.rs Normal file
View File

@@ -0,0 +1,30 @@
// TODO: Contextful String
use ecow::EcoString;
use rpds::List;
pub struct StringContext {
context: List<()>,
}
impl StringContext {
pub fn new() -> StringContext {
StringContext {
context: List::new(),
}
}
}
pub struct ContextfulString {
string: EcoString,
context: StringContext,
}
impl ContextfulString {
pub fn new(string: EcoString) -> ContextfulString {
ContextfulString {
string,
context: StringContext::new(),
}
}
}

188
src/vm/vm.rs Normal file
View File

@@ -0,0 +1,188 @@
use anyhow::{anyhow, Result};
use rpds::{HashTrieMap, HashTrieMapSync, Vector};
use crate::bytecode::{self, *};
use crate::slice::*;
use crate::value::{self, Value};
use crate::builtins::env;
use super::env::Env;
use super::stack::{Stack, STACK_SIZE};
use super::value::{self as vmValue, *};
use super::vmthunk::*;
pub fn run(prog: Program) -> Result<Value> {
let vm = VM::new(prog.thunks);
Ok(vm.eval(prog.top_level, &mut env())?.to_value(&vm))
}
pub struct VM {
thunks: Slice<VmThunk>,
}
impl VM {
fn new(thunks: Thunks) -> Self {
let thunks = thunks
.into_iter()
.map(|bytecode::Thunk { opcodes }| VmThunk::new(opcodes))
.collect();
VM {
thunks,
}
}
pub fn get_thunk_value(&self, idx: usize, env: &mut Env) -> Result<VmValue> {
self.thunks.get(idx).unwrap().force(self, env)
}
pub fn eval(&self, opcodes: OpCodes, env: &mut Env) -> Result<VmValue> {
let mut stack = Stack::<STACK_SIZE>::new();
let mut iter = opcodes.into_iter();
while let Some(opcode) = iter.next() {
let jmp = self.single_op(opcode, &mut stack, env)?;
for _ in 0..jmp {
iter.next().unwrap();
}
}
assert_eq!(stack.len(), 1);
stack.pop()
}
#[inline]
fn single_op<const CAP: usize>(
&self,
opcode: OpCode,
stack: &mut Stack<CAP>,
env: &mut Env,
) -> Result<usize> {
match opcode {
OpCode::NoOp => (),
OpCode::Const { value } => stack.push(VmValue::Const(value))?,
OpCode::LoadThunk { idx } => stack.push(VmValue::Thunk(vmValue::Thunk::new(idx)))?,
OpCode::LoadValue { idx } => {
stack.push(self.get_thunk_value(idx, env)?)?;
}
OpCode::ForceValue => {
stack.tos_mut()?.force(self, env)?;
}
OpCode::Jmp { step } => return Ok(step),
OpCode::JmpIfTrue { step } => {
if let VmValue::Const(Const::Bool(true)) = stack.pop()? {
return Ok(step);
}
}
OpCode::JmpIfFalse { step } => {
if let VmValue::Const(Const::Bool(false)) = stack.pop()? {
return Ok(step);
}
}
OpCode::Call { arity } => {
let mut args = Vec::with_capacity(arity);
for _ in 0..arity {
args.insert(0, stack.pop()?);
}
let func = stack.pop()?;
stack.push(func.call(args))?;
}
OpCode::UnOp { op } => {
use UnOp::*;
let value = stack.pop()?;
stack.push(match op {
Not => value.not(),
})?;
}
OpCode::BinOp { op } => {
use BinOp::*;
let rhs = stack.pop()?;
let lhs = stack.pop()?;
stack.push(match op {
Add => lhs.add(rhs),
And => lhs.and(rhs),
Or => lhs.or(rhs),
Eq => lhs.eq(rhs),
Con => lhs.concat(rhs),
Upd => lhs.update(rhs),
})?;
}
OpCode::ConcatString => {
let rhs = stack.pop()?;
stack.tos_mut()?.concat_string(rhs);
}
OpCode::List => {
stack.push(VmValue::List(List::new(Vector::new_sync())))?;
}
OpCode::PushElem => {
let elem = stack.pop()?;
stack.tos_mut()?.push(elem);
}
OpCode::AttrSet => {
stack.push(VmValue::AttrSet(AttrSet::new(HashTrieMap::new_sync())))?;
}
OpCode::PushStaticAttr { name } => {
let val = stack.pop()?;
stack.tos_mut()?.push_attr(Symbol::new(name), val);
}
OpCode::PushDynamicAttr => {
let val = stack.pop()?;
let mut sym = stack.pop().unwrap();
sym.coerce_to_string();
let sym = sym.unwrap_const().unwrap_string().into();
stack.tos_mut()?.push_attr(sym, val);
}
OpCode::Select { sym } => {
stack.tos_mut()?.select(Symbol::new(sym)).force(self, env)?;
}
OpCode::SelectWithDefault { sym } => {
let default = stack.pop()?;
stack
.tos_mut()?
.select_with_default(Symbol::new(sym), default.clone());
}
OpCode::SelectOrEmpty { sym } => {
stack
.tos_mut()?
.select_with_default(Symbol::new(sym), VmValue::AttrSet(AttrSet::new(HashTrieMapSync::new_sync())));
}
OpCode::SelectDynamic => {
let mut val = stack.pop().unwrap();
val.coerce_to_string();
let sym = val.unwrap_const().unwrap_string().into();
stack.tos_mut()?.select(sym);
}
OpCode::SelectDynamicWithDefault => {
let mut val = stack.pop().unwrap();
val.coerce_to_string();
let sym = val.unwrap_const().unwrap_string().into();
let default = stack.pop()?;
stack.tos_mut()?.select_with_default(sym, default.clone());
}
OpCode::SelectDynamicOrEmpty => {
let mut val = stack.pop().unwrap();
val.coerce_to_string();
let sym = val.unwrap_const().unwrap_string().into();
stack.tos_mut()?.select_with_default(sym, VmValue::AttrSet(AttrSet::new(HashTrieMapSync::new_sync())));
}
OpCode::HasAttr { sym } => {
stack.tos_mut()?.has_attr(Symbol::new(sym));
}
OpCode::HasDynamicAttr => {
let mut val = stack.pop().unwrap();
val.coerce_to_string();
let sym = val.unwrap_const().unwrap_string().into();
stack.tos_mut()?.has_attr(sym);
}
OpCode::LookUp { sym } => {
stack.push(env.lookup(Symbol::new(sym)))?;
}
OpCode::EnterEnv => {
env.enter(stack.pop()?.unwrap_attr_set().to_data());
}
OpCode::LeaveEnv => {
env.leave();
}
_ => todo!(),
}
Ok(0)
}
}

65
src/vm/vmthunk.rs Normal file
View File

@@ -0,0 +1,65 @@
use std::cell::RefCell;
use std::sync::RwLock;
use anyhow::{anyhow, Result};
use derive_more::{IsVariant, Unwrap};
use crate::bytecode::OpCodes;
use super::vm::VM;
use super::env::Env;
use super::value::VmValue;
pub struct VmThunk {
thunk: RefCell<_VmThunk>,
lock: RwLock<()>
}
#[derive(IsVariant, Unwrap)]
enum _VmThunk {
Code(OpCodes),
SuspendedFrom(*const VmThunk),
Value(VmValue),
}
impl VmThunk {
pub fn new(opcodes: OpCodes) -> VmThunk {
VmThunk {
thunk: RefCell::new(_VmThunk::Code(opcodes)),
lock: RwLock::new(())
}
}
pub fn force(&self, vm: &VM, env: &mut Env) -> Result<VmValue> {
{
let _guard = self.lock.read().unwrap();
match &*self.thunk.borrow() {
_VmThunk::Value(value) => return Ok(value.clone()),
_VmThunk::SuspendedFrom(from) => {
return Err(anyhow!(
"already suspended from {from:p} (infinite recursion encountered)"
))
}
_VmThunk::Code(_) => (),
}
}
{
let _guard = self.lock.write().unwrap();
let opcodes = std::mem::replace(
&mut *self.thunk.borrow_mut(),
_VmThunk::SuspendedFrom(self as *const VmThunk),
).unwrap_code();
let value = vm.eval(opcodes, env).unwrap();
let _ = std::mem::replace(&mut *self.thunk.borrow_mut(), _VmThunk::Value(value.clone()));
Ok(value)
}
}
pub fn value(&self) -> Option<VmValue> {
let _guard = self.lock.read();
match &*self.thunk.borrow() {
_VmThunk::Value(value) => Some(value.clone()),
_ => None,
}
}
}