Begin adding support for more built-in properties
This commit is contained in:
parent
8c5ac0b89e
commit
6983d282d8
@ -1,4 +1,4 @@
|
|||||||
use crate::{Identifier, Span, Value};
|
use crate::{Identifier, ReservedIdentifier, Span, Value};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
@ -29,4 +29,5 @@ pub enum Statement {
|
|||||||
// Hard-coded values
|
// Hard-coded values
|
||||||
Constant(Value),
|
Constant(Value),
|
||||||
Identifier(Identifier),
|
Identifier(Identifier),
|
||||||
|
ReservedIdentifier(ReservedIdentifier),
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ impl Analyzer {
|
|||||||
|
|
||||||
self.analyze_node(&right)?;
|
self.analyze_node(&right)?;
|
||||||
}
|
}
|
||||||
|
Statement::ReservedIdentifier(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
//! - [`Lexer`], which lexes the input a token at a time
|
//! - [`Lexer`], which lexes the input a token at a time
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
|
|
||||||
use crate::{Identifier, Span, Token};
|
use crate::{Identifier, ReservedIdentifier, Span, Token};
|
||||||
|
|
||||||
/// Lex the input and return a vector of tokens and their positions.
|
/// Lex the input and return a vector of tokens and their positions.
|
||||||
pub fn lex(input: &str) -> Result<Vec<(Token, Span)>, LexError> {
|
pub fn lex(input: &str) -> Result<Vec<(Token, Span)>, LexError> {
|
||||||
@ -169,8 +169,11 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let identifier = &self.source[start_pos..self.position];
|
let string = &self.source[start_pos..self.position];
|
||||||
let token = Token::Identifier(Identifier::new(identifier));
|
let token = match string {
|
||||||
|
"length" => Token::ReservedIdentifier(ReservedIdentifier::Length),
|
||||||
|
_ => Token::Identifier(Identifier::new(string)),
|
||||||
|
};
|
||||||
|
|
||||||
Ok((token, (start_pos, self.position)))
|
Ok((token, (start_pos, self.position)))
|
||||||
}
|
}
|
||||||
@ -198,6 +201,22 @@ impl From<ParseIntError> for LexError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reserved_identifier() {
|
||||||
|
let input = "length";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
lex(input),
|
||||||
|
Ok(vec![
|
||||||
|
(
|
||||||
|
Token::ReservedIdentifier(ReservedIdentifier::Length),
|
||||||
|
(0, 6)
|
||||||
|
),
|
||||||
|
(Token::Eof, (6, 6)),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn square_braces() {
|
fn square_braces() {
|
||||||
let input = "[]";
|
let input = "[]";
|
||||||
|
@ -21,7 +21,7 @@ pub use identifier::Identifier;
|
|||||||
pub use lex::{lex, LexError, Lexer};
|
pub use lex::{lex, LexError, Lexer};
|
||||||
pub use parse::{parse, ParseError, Parser};
|
pub use parse::{parse, ParseError, Parser};
|
||||||
pub use r#type::Type;
|
pub use r#type::Type;
|
||||||
pub use token::Token;
|
pub use token::{ReservedIdentifier, Token};
|
||||||
pub use value::{Value, ValueError};
|
pub use value::{Value, ValueError};
|
||||||
pub use vm::{run, Vm, VmError};
|
pub use vm::{run, Vm, VmError};
|
||||||
|
|
||||||
|
@ -170,6 +170,14 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(Token::ReservedIdentifier(reserved), _) => {
|
||||||
|
self.next_token()?;
|
||||||
|
|
||||||
|
Ok(Node::new(
|
||||||
|
Statement::ReservedIdentifier(reserved),
|
||||||
|
self.current.1,
|
||||||
|
))
|
||||||
|
}
|
||||||
_ => Err(ParseError::UnexpectedToken(self.current.0.clone())),
|
_ => Err(ParseError::UnexpectedToken(self.current.0.clone())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ pub enum Token {
|
|||||||
Eof,
|
Eof,
|
||||||
Equal,
|
Equal,
|
||||||
Identifier(Identifier),
|
Identifier(Identifier),
|
||||||
|
ReservedIdentifier(ReservedIdentifier),
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
Plus,
|
Plus,
|
||||||
Star,
|
Star,
|
||||||
@ -16,3 +17,10 @@ pub enum Token {
|
|||||||
RightSquareBrace,
|
RightSquareBrace,
|
||||||
Float(f64),
|
Float(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum ReservedIdentifier {
|
||||||
|
IsEven,
|
||||||
|
IsOdd,
|
||||||
|
Length,
|
||||||
|
}
|
||||||
|
@ -88,60 +88,6 @@ impl Value {
|
|||||||
_ => Err(ValueError::CannotAdd(self.clone(), other.clone())),
|
_ => Err(ValueError::CannotAdd(self.clone(), other.clone())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn property_access(&self, property: &Identifier) -> Result<Value, ValueError> {
|
|
||||||
match self.inner().as_ref() {
|
|
||||||
ValueInner::Map(map) => {
|
|
||||||
if let Some(value) = map.get(property) {
|
|
||||||
Ok(value.clone())
|
|
||||||
} else {
|
|
||||||
Err(ValueError::PropertyNotFound {
|
|
||||||
value: self.clone(),
|
|
||||||
property: property.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ValueInner::Integer(integer) => match property.as_str() {
|
|
||||||
"is_even" => Ok(Value::boolean(integer % 2 == 0)),
|
|
||||||
"to_string" => Ok(Value::string(integer.to_string())),
|
|
||||||
_ => Err(ValueError::PropertyNotFound {
|
|
||||||
value: self.clone(),
|
|
||||||
property: property.clone(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
ValueInner::List(values) => match property.as_str() {
|
|
||||||
"length" => Ok(Value::integer(values.len() as i64)),
|
|
||||||
_ => Err(ValueError::PropertyNotFound {
|
|
||||||
value: self.clone(),
|
|
||||||
property: property.clone(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_access(&self, index: i64) -> Result<Value, ValueError> {
|
|
||||||
match self.inner().as_ref() {
|
|
||||||
ValueInner::List(list) => {
|
|
||||||
if index < 0 {
|
|
||||||
return Err(ValueError::IndexOutOfBounds {
|
|
||||||
value: self.clone(),
|
|
||||||
index,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(value) = list.get(index as usize) {
|
|
||||||
Ok(value.clone())
|
|
||||||
} else {
|
|
||||||
Err(ValueError::IndexOutOfBounds {
|
|
||||||
value: self.clone(),
|
|
||||||
index,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ValueError::ExpectedList(self.clone())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Value {
|
impl Display for Value {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
use crate::{parse, Identifier, Node, ParseError, Span, Statement, Value, ValueError};
|
use crate::{
|
||||||
|
parse, Identifier, Node, ParseError, ReservedIdentifier, Span, Statement, Value, ValueError,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
input: &str,
|
input: &str,
|
||||||
@ -40,6 +42,9 @@ impl Vm {
|
|||||||
variables: &mut HashMap<Identifier, Value>,
|
variables: &mut HashMap<Identifier, Value>,
|
||||||
) -> Result<Option<Value>, VmError> {
|
) -> Result<Option<Value>, VmError> {
|
||||||
match node.statement {
|
match node.statement {
|
||||||
|
Statement::Constant(value) => Ok(Some(value.clone())),
|
||||||
|
Statement::Identifier(_) => Ok(None),
|
||||||
|
Statement::ReservedIdentifier(_) => Ok(None),
|
||||||
Statement::Add(left, right) => {
|
Statement::Add(left, right) => {
|
||||||
let left_span = left.span;
|
let left_span = left.span;
|
||||||
let left = if let Some(value) = self.run_node(*left, variables)? {
|
let left = if let Some(value) = self.run_node(*left, variables)? {
|
||||||
@ -82,8 +87,6 @@ impl Vm {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Statement::Constant(value) => Ok(Some(value.clone())),
|
|
||||||
Statement::Identifier(_) => Ok(None),
|
|
||||||
Statement::List(nodes) => {
|
Statement::List(nodes) => {
|
||||||
let values = nodes
|
let values = nodes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -111,17 +114,44 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
let right_span = right.span;
|
let right_span = right.span;
|
||||||
|
|
||||||
if let Statement::Identifier(identifier) = &right.statement {
|
if let Statement::ReservedIdentifier(reserved) = &right.statement {
|
||||||
let value = left.property_access(identifier)?;
|
match reserved {
|
||||||
|
ReservedIdentifier::IsEven => {
|
||||||
return Ok(Some(value));
|
if let Some(integer) = left.as_integer() {
|
||||||
|
return Ok(Some(Value::boolean(integer % 2 == 0)));
|
||||||
|
} else {
|
||||||
|
return Err(VmError::ExpectedInteger {
|
||||||
|
position: right_span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReservedIdentifier::IsOdd => {
|
||||||
|
if let Some(integer) = left.as_integer() {
|
||||||
|
return Ok(Some(Value::boolean(integer % 2 != 0)));
|
||||||
|
} else {
|
||||||
|
return Err(VmError::ExpectedInteger {
|
||||||
|
position: right_span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReservedIdentifier::Length => {
|
||||||
|
if let Some(list) = left.as_list() {
|
||||||
|
return Ok(Some(Value::integer(list.len() as i64)));
|
||||||
|
} else {
|
||||||
|
return Err(VmError::ExpectedList {
|
||||||
|
position: right_span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Statement::Constant(value) = &right.statement {
|
if let (Some(list), Statement::Constant(value)) = (left.as_list(), &right.statement)
|
||||||
|
{
|
||||||
if let Some(index) = value.as_integer() {
|
if let Some(index) = value.as_integer() {
|
||||||
let value = left.list_access(index)?;
|
let value = list.get(index as usize).cloned();
|
||||||
|
|
||||||
return Ok(Some(value));
|
return Ok(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +171,9 @@ pub enum VmError {
|
|||||||
// Anaylsis Failures
|
// Anaylsis Failures
|
||||||
// These should be prevented by running the analyzer before the VM
|
// These should be prevented by running the analyzer before the VM
|
||||||
ExpectedValue { position: Span },
|
ExpectedValue { position: Span },
|
||||||
ExpectedIdentifierOrInteger { position: (usize, usize) },
|
ExpectedIdentifierOrInteger { position: Span },
|
||||||
|
ExpectedList { position: Span },
|
||||||
|
ExpectedInteger { position: Span },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseError> for VmError {
|
impl From<ParseError> for VmError {
|
||||||
@ -160,9 +192,29 @@ impl From<ValueError> for VmError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_even() {
|
||||||
|
let input = "42.is_even";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
run(input, &mut HashMap::new()),
|
||||||
|
Ok(Some(Value::boolean(true)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_odd() {
|
||||||
|
let input = "42.is_odd";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
run(input, &mut HashMap::new()),
|
||||||
|
Ok(Some(Value::boolean(false)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_access() {
|
fn list_access() {
|
||||||
let input = "[1, 2, 3][1]";
|
let input = "[1, 2, 3].1";
|
||||||
|
|
||||||
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(2))));
|
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(2))));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user