Add modulo support

This commit is contained in:
Jeff 2024-08-09 07:02:55 -04:00
parent 2cf580d111
commit 24a2642f17
13 changed files with 96 additions and 32 deletions

View File

@ -36,7 +36,7 @@ count_slowly = fn (
async {
count_slowly(200) # Finishes last
count_slowly(100) # Finishes seconds
count_slowly(100) # Finishes second
count_slowly(50) # Finishes first
}
```

View File

@ -233,6 +233,7 @@ pub enum BinaryOperator {
// Math
Add,
Divide,
Modulo,
Multiply,
Subtract,
@ -257,6 +258,7 @@ impl Display for BinaryOperator {
BinaryOperator::GreaterOrEqual => write!(f, ">="),
BinaryOperator::Less => write!(f, "<"),
BinaryOperator::LessOrEqual => write!(f, "<="),
BinaryOperator::Modulo => write!(f, "%"),
BinaryOperator::Multiply => write!(f, "*"),
BinaryOperator::Or => write!(f, "||"),
BinaryOperator::Subtract => write!(f, "-"),

View File

@ -119,7 +119,7 @@ impl Lexer {
(Token::Minus, (self.position - 1, self.position))
}
}
'a'..='z' | 'A'..='Z' => self.lex_alphabetical(source)?,
'a'..='z' | 'A'..='Z' => self.lex_alphanumeric(source)?,
'"' => self.lex_string('"', source)?,
'\'' => self.lex_string('\'', source)?,
'+' => {
@ -204,6 +204,11 @@ impl Lexer {
(Token::Slash, (self.position - 1, self.position))
}
'%' => {
self.position += 1;
(Token::Percent, (self.position - 1, self.position))
}
_ => {
self.position += 1;
@ -320,14 +325,14 @@ impl Lexer {
}
/// Lex an identifier token.
fn lex_alphabetical<'src>(
fn lex_alphanumeric<'src>(
&mut self,
source: &'src str,
) -> Result<(Token<'src>, Span), LexError> {
let start_pos = self.position;
while let Some(c) = self.peek_char(source) {
if c.is_ascii_alphabetic() || c == '_' {
if c.is_ascii_alphanumeric() || c == '_' {
self.next_char(source);
} else {
break;
@ -430,6 +435,21 @@ impl From<ParseIntError> for LexError {
mod tests {
use super::*;
#[test]
fn modulo() {
let input = "42 % 2";
assert_eq!(
lex(input),
Ok(vec![
(Token::Integer(42), (0, 2)),
(Token::Percent, (3, 4)),
(Token::Integer(2), (5, 6)),
(Token::Eof, (6, 6)),
])
)
}
#[test]
fn divide() {
let input = "42 / 2";

View File

@ -302,6 +302,23 @@ impl<'src> Parser<'src> {
(left_start, right_end),
));
}
(Token::Percent, _) => {
let operator = Node::new(BinaryOperator::Modulo, self.current.1);
self.next_token()?;
let right_node = self.parse_node(self.current_precedence())?;
let right_end = right_node.position.1;
return Ok(Node::new(
Statement::BinaryOperation {
left: Box::new(left_node),
operator,
right: Box::new(right_node),
},
(left_start, right_end),
));
}
_ => {}
}
}
@ -524,6 +541,7 @@ impl<'src> Parser<'src> {
match self.current.0 {
Token::Greater | Token::GreaterEqual | Token::Less | Token::LessEqual => 5,
Token::Dot => 4,
Token::Percent => 3,
Token::Star => 2,
Token::Slash => 2,
Token::Plus => 1,
@ -595,6 +613,26 @@ mod tests {
use super::*;
#[test]
fn modulo() {
let input = "42 % 2";
assert_eq!(
parse(input),
Ok(AbstractSyntaxTree {
nodes: [Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(Statement::Constant(Value::integer(42)), (0, 2))),
operator: Node::new(BinaryOperator::Modulo, (3, 4)),
right: Box::new(Node::new(Statement::Constant(Value::integer(2)), (5, 6)))
},
(0, 6)
)]
.into()
})
);
}
#[test]
fn divide() {
let input = "42 / 2";

View File

@ -37,6 +37,7 @@ pub enum Token<'src> {
Less,
LessEqual,
Minus,
Percent,
Plus,
RightCurlyBrace,
RightParenthesis,
@ -69,6 +70,7 @@ impl<'src> Token<'src> {
Token::Less => TokenOwned::Less,
Token::LessEqual => TokenOwned::LessOrEqual,
Token::Minus => TokenOwned::Minus,
Token::Percent => TokenOwned::Percent,
Token::Plus => TokenOwned::Plus,
Token::ReadLine => TokenOwned::ReadLine,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
@ -104,6 +106,7 @@ impl<'src> Token<'src> {
Token::Less => "<",
Token::LessEqual => "<=",
Token::Minus => "-",
Token::Percent => "%",
Token::Plus => "+",
Token::ReadLine => "read_line",
Token::RightCurlyBrace => "}",
@ -148,6 +151,7 @@ impl<'src> PartialEq for Token<'src> {
(Token::Less, Token::Less) => true,
(Token::LessEqual, Token::LessEqual) => true,
(Token::Minus, Token::Minus) => true,
(Token::Percent, Token::Percent) => true,
(Token::Plus, Token::Plus) => true,
(Token::ReadLine, Token::ReadLine) => true,
(Token::RightCurlyBrace, Token::RightCurlyBrace) => true,
@ -198,6 +202,7 @@ pub enum TokenOwned {
Less,
LessOrEqual,
Minus,
Percent,
Plus,
RightCurlyBrace,
RightParenthesis,
@ -230,6 +235,7 @@ impl Display for TokenOwned {
TokenOwned::Less => Token::Less.fmt(f),
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
TokenOwned::Minus => Token::Minus.fmt(f),
TokenOwned::Percent => Token::Percent.fmt(f),
TokenOwned::Plus => Token::Plus.fmt(f),
TokenOwned::ReadLine => Token::ReadLine.fmt(f),
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),

View File

@ -178,6 +178,20 @@ impl Value {
}
}
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
match (self.inner().as_ref(), other.inner().as_ref()) {
(ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::float(left % right)),
(ValueInner::Integer(left), ValueInner::Integer(right)) => {
if right == &0 {
Err(ValueError::DivisionByZero)
} else {
Ok(Value::integer(left % right))
}
}
_ => Err(ValueError::CannotModulo(self.clone(), other.clone())),
}
}
pub fn less_than(&self, other: &Value) -> Result<Value, ValueError> {
match (self.inner().as_ref(), other.inner().as_ref()) {
(ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::boolean(left < right)),
@ -778,6 +792,7 @@ pub enum ValueError {
CannotGreaterThanOrEqual(Value, Value),
CannotLessThan(Value, Value),
CannotLessThanOrEqual(Value, Value),
CannotModulo(Value, Value),
CannotMultiply(Value, Value),
CannotSubtract(Value, Value),
CannotOr(Value, Value),
@ -800,6 +815,9 @@ impl Display for ValueError {
ValueError::CannotDivide(left, right) => {
write!(f, "Cannot divide {} by {}", left, right)
}
ValueError::CannotModulo(left, right) => {
write!(f, "Cannot modulo {} by {}", left, right)
}
ValueError::CannotMultiply(left, right) => {
write!(f, "Cannot multiply {} and {}", left, right)
}

View File

@ -102,6 +102,7 @@ impl Vm {
}
BinaryOperator::Less => left_value.less_than(&right_value),
BinaryOperator::LessOrEqual => left_value.less_than_or_equal(&right_value),
BinaryOperator::Modulo => left_value.modulo(&right_value),
BinaryOperator::Multiply => left_value.multiply(&right_value),
BinaryOperator::Or => left_value.or(&right_value),
BinaryOperator::Subtract => left_value.subtract(&right_value),
@ -432,6 +433,13 @@ impl Display for VmError {
mod tests {
use super::*;
#[test]
fn modulo() {
let input = "42 % 2";
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(0))));
}
#[test]
fn divide() {
let input = "42 / 2";

View File

@ -1 +0,0 @@
nightly

View File

@ -1,3 +0,0 @@
length = fn (list: [any]) -> int {
__LENGTH__(list)
}

View File

@ -1,5 +0,0 @@
fs = {
read_file = fn (path: str) -> str {
__READ_FILE__(path)
}
}

View File

@ -1,9 +0,0 @@
io = {
read_line = fn () -> str {
__READ_LINE__()
}
write_line = fn (output: any) {
__WRITE_LINE__(output)
}
}

View File

@ -1,5 +0,0 @@
json = {
parse = fn <T> (input: str) -> T {
__JSON_PARSE__::<T>(input)
}
}

View File

@ -1,5 +0,0 @@
thread = {
sleep = fn (milliseconds: int) {
__SLEEP__(milliseconds)
}
}