Skip to content

Commit

Permalink
Add Records (#30)
Browse files Browse the repository at this point in the history
Adds Records (objects, hash maps, etc) to Stack.

1. Added a record type, `ExprKind::Record(HashMap<Symbol, Expr>)` 
2. Added intrinsic methods to interact with records
3. Added syntax for defining records (pretty simple at the moment)
4. Added tests for the record type and intrinsics

**TODO:**
- [x] Move the record module into intrinsics
- [x] Add a syntax for defining records using `{}`
- [x] Add tests
  • Loading branch information
Vandesm14 authored Apr 24, 2024
1 parent eeab40c commit ed6c22c
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 7 deletions.
3 changes: 2 additions & 1 deletion stack-core/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ impl Engine {
| ExprKind::Boolean(_)
| ExprKind::Integer(_)
| ExprKind::Float(_)
| ExprKind::String(_) => {
| ExprKind::String(_)
| ExprKind::Record(_) => {
context.stack_push(expr)?;
Ok(context)
}
Expand Down
30 changes: 30 additions & 0 deletions stack-core/src/expr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::{cmp::Ordering, fmt, hash::Hash, ops};
use std::collections::HashMap;

use compact_str::CompactString;
use internment::Intern;
Expand Down Expand Up @@ -50,6 +51,7 @@ pub enum ExprKind {

Lazy(Box<Expr>),
List(Vec<Expr>),
Record(HashMap<Symbol, Expr>),

Fn(FnIdent),
}
Expand Down Expand Up @@ -261,6 +263,22 @@ impl fmt::Display for ExprKind {

write!(f, "{}", ")".yellow())
}
Self::Record(x) => {
write!(f, "{{")?;

core::iter::once("")
.chain(core::iter::repeat(", "))
.zip(x.iter())
.try_for_each(|(sep, (key, value))| {
let key = Expr {
info: None,
kind: ExprKind::Symbol(*key),
};
write!(f, "{sep}{key:#}: {value:#}")
})?;

write!(f, "}}")
}

Self::Fn(x) => write!(f, "{}", x.to_string().yellow()),
}
Expand All @@ -287,6 +305,18 @@ impl fmt::Display for ExprKind {

write!(f, ")")
}
Self::Record(x) => {
write!(f, "{{")?;

core::iter::once("")
.chain(core::iter::repeat(", "))
.zip(x.iter())
.try_for_each(|(sep, (key, value))| {
write!(f, "{sep}{key}: {value}")
})?;

write!(f, "}}")
}

Self::Fn(x) => write!(f, "{x}"),
}
Expand Down
244 changes: 244 additions & 0 deletions stack-core/src/intrinsic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::{fmt, num::FpCategory, str::FromStr};
use std::collections::HashMap;

use compact_str::ToCompactString;
use unicode_segmentation::UnicodeSegmentation;
Expand Down Expand Up @@ -80,6 +81,13 @@ intrinsics! {
Push => "push",
Pop => "pop",

Insert => "insert",
Prop => "prop",
Has => "has",
Remove => "remove",
Keys => "keys",
Values => "values",

Cast => "cast",
Lazy => "lazy",

Expand Down Expand Up @@ -365,6 +373,10 @@ impl Intrinsic {
debug_assert!(len <= i64::MAX as usize);
ExprKind::Integer(len as i64)
}
ExprKind::Record(ref x) => {
debug_assert!(x.len() <= i64::MAX as usize);
ExprKind::Integer(x.len() as i64)
}
_ => ExprKind::Nil,
};

Expand Down Expand Up @@ -578,6 +590,205 @@ impl Intrinsic {
Ok(context)
}

// MARK: Insert
Self::Insert => {
let record = context.stack_pop(&expr)?;
let name = context.stack_pop(&expr)?;
let value = context.stack_pop(&expr)?;

match record.kind {
ExprKind::Record(ref record) => {
let symbol = match name.kind {
ExprKind::Symbol(s) => s,
ExprKind::String(s) => Symbol::from_ref(s.as_str()),
_ => {
return Err(RunError {
context,
expr,
reason: RunErrorReason::UnknownCall,
})
}
};

let mut new_record = record.clone();
new_record.insert(symbol, value);

context.stack_push(Expr {
kind: ExprKind::Record(new_record),
info: None,
})?;

Ok(())
}
_ => context.stack_push(Expr {
kind: ExprKind::Nil,
info: None,
}),
}
.map(|_| context)
}
// MARK: Prop
Self::Prop => {
let symbol = context.stack_pop(&expr)?;
let record = context.stack_pop(&expr)?;

match record.kind {
ExprKind::Record(ref r) => {
let symbol = match symbol.kind {
ExprKind::Symbol(s) => s,
ExprKind::String(s) => Symbol::from_ref(s.as_str()),
_ => {
return Err(RunError {
context,
expr,
reason: RunErrorReason::UnknownCall,
})
}
};

let result = r.get(&symbol).unwrap_or_else(|| &Expr {
info: None,
kind: ExprKind::Nil,
});

context.stack_push(record.clone())?;
context.stack_push(result.clone())?;

Ok(())
}
_ => context.stack_push(Expr {
kind: ExprKind::Nil,
info: None,
}),
}
.map(|_| context)
}
// MARK: Has
Self::Has => {
let symbol = context.stack_pop(&expr)?;
let record = context.stack_pop(&expr)?;

match record.kind {
ExprKind::Record(ref r) => {
let symbol = match symbol.kind {
ExprKind::Symbol(s) => s,
ExprKind::String(s) => Symbol::from_ref(s.as_str()),
_ => {
return Err(RunError {
context,
expr,
reason: RunErrorReason::UnknownCall,
})
}
};

let result = r.contains_key(&symbol);

context.stack_push(record.clone())?;
context.stack_push(Expr {
info: None,
kind: ExprKind::Boolean(result),
})?;

Ok(())
}
_ => context.stack_push(Expr {
kind: ExprKind::Nil,
info: None,
}),
}
.map(|_| context)
}
// MARK: Remove
Self::Remove => {
let name = context.stack_pop(&expr)?;
let record = context.stack_pop(&expr)?;

match record.kind {
ExprKind::Record(ref record) => {
let symbol = match name.kind {
ExprKind::Symbol(s) => s,
ExprKind::String(s) => Symbol::from_ref(s.as_str()),
_ => {
return Err(RunError {
context,
expr,
reason: RunErrorReason::UnknownCall,
})
}
};

let mut new_record = record.clone();
new_record.remove(&symbol);

context.stack_push(Expr {
kind: ExprKind::Record(new_record),
info: None,
})?;

Ok(())
}
_ => context.stack_push(Expr {
kind: ExprKind::Nil,
info: None,
}),
}
.map(|_| context)
}
// MARK: Keys
Self::Keys => {
let record = context.stack_pop(&expr)?;

match record.kind {
ExprKind::Record(ref r) => {
let result = r
.keys()
.copied()
.map(|s| Expr {
info: None,
kind: ExprKind::Symbol(s),
})
.collect::<Vec<_>>();

context.stack_push(record.clone())?;
context.stack_push(Expr {
info: None,
kind: ExprKind::List(result),
})?;

Ok(())
}
_ => context.stack_push(Expr {
kind: ExprKind::Nil,
info: None,
}),
}
.map(|_| context)
}
// MARK: Values
Self::Values => {
let record = context.stack_pop(&expr)?;

match record.kind {
ExprKind::Record(ref r) => {
let result = r.values().cloned().collect::<Vec<_>>();

context.stack_push(record.clone())?;
context.stack_push(Expr {
info: None,
kind: ExprKind::List(result),
})?;

Ok(())
}
_ => context.stack_push(Expr {
kind: ExprKind::Nil,
info: None,
}),
}
.map(|_| context)
}

// MARK: Cast
Self::Cast => {
let ty = context.stack_pop(&expr)?;
Expand Down Expand Up @@ -636,6 +847,39 @@ impl Intrinsic {
(ExprKind::String(x), "symbol") => ExprKind::Symbol(Symbol::new(x)),
(ExprKind::Symbol(x), "symbol") => ExprKind::Symbol(x),

(ExprKind::Record(x), "record") => ExprKind::Record(x),
(ExprKind::Record(x), "list") => {
let mut list: Vec<Expr> = Vec::new();
x.into_iter().for_each(|(key, value)| {
list.push(Expr {
info: None,
kind: ExprKind::List(vec![
Expr {
info: None,
kind: ExprKind::Symbol(key),
},
value,
]),
});
});

ExprKind::List(list)
}

(ExprKind::List(x), "record") => {
let mut record: HashMap<Symbol, Expr> = HashMap::new();
x.into_iter().for_each(|item| {
if let ExprKind::List(chunk) = item.kind {
let key =
Symbol::from_ref(chunk[0].kind.to_string().as_str());
let value = &chunk[1];
record.insert(key, value.clone());
}
});

ExprKind::Record(record)
}

_ => ExprKind::Nil,
},
_ => ExprKind::Nil,
Expand Down
31 changes: 31 additions & 0 deletions stack-core/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ pub enum TokenKind {
Apostrophe,
LeftParen,
RightParen,

// TODO: We should probably treat `,` as whitespace so that records can contain them:
// `{"key" "value", "foo" "bar"}`
//
// Or, instead, add stricter syntax for both `:` and `,` within records
LeftCurly,
RightCurly,
// LeftSquare,
// RightSquare,
Integer,
Expand All @@ -58,6 +65,8 @@ impl fmt::Display for TokenKind {
Self::Apostrophe => write!(f, "'"),
Self::LeftParen => write!(f, "("),
Self::RightParen => write!(f, ")"),
Self::LeftCurly => write!(f, "{{"),
Self::RightCurly => write!(f, "}}"),
// Self::LeftSquare => write!(f, "["),
// Self::RightSquare => write!(f, "]"),
Self::Integer => write!(f, "an integer literal"),
Expand Down Expand Up @@ -178,6 +187,28 @@ impl Lexer {
},
};
}
'{' => {
self.cursor += c_len;

break Token {
kind: TokenKind::LeftCurly,
span: Span {
start,
end: self.cursor,
},
};
}
'}' => {
self.cursor += c_len;

break Token {
kind: TokenKind::RightCurly,
span: Span {
start,
end: self.cursor,
},
};
}
// '[' => {
// self.cursor += c_len;

Expand Down
Loading

0 comments on commit ed6c22c

Please sign in to comment.