Add operators, math, time, string, collection, and HOF builtins to El
This commit is contained in:
@@ -265,6 +265,7 @@ fn extract_calls_from_expr(expr: &Expr, in_loop: bool, out: &mut Vec<CallInfo>)
|
||||
extract_calls_from_stmt(s, in_loop, out);
|
||||
}
|
||||
}
|
||||
Expr::UnaryBitNot(inner) => extract_calls_from_expr(inner, in_loop, out),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +387,7 @@ fn extract_activate_types_expr(expr: &Expr, in_loop: bool, out: &mut Vec<String>
|
||||
extract_activate_types_stmt(s, in_loop, out);
|
||||
}
|
||||
}
|
||||
Expr::UnaryBitNot(inner) => extract_activate_types_expr(inner, in_loop, out),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,6 +451,7 @@ fn has_sealed_in_loop_expr(expr: &Expr, in_loop: bool) -> bool {
|
||||
Expr::Reason { .. } => false,
|
||||
Expr::Parallel { entries } => entries.iter().any(|(_, e)| has_sealed_in_loop_expr(e, in_loop)),
|
||||
Expr::Trace { body, .. } => body.iter().any(|s| has_sealed_in_loop_stmt(s, in_loop)),
|
||||
Expr::UnaryBitNot(inner) => has_sealed_in_loop_expr(inner, in_loop),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,13 @@ pub enum Bytecode {
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
BitAnd,
|
||||
BitOr,
|
||||
BitXor,
|
||||
BitNot,
|
||||
Shl,
|
||||
Shr,
|
||||
|
||||
// ── Comparison ────────────────────────────────────────────────────────────
|
||||
Eq,
|
||||
@@ -155,6 +162,13 @@ impl std::fmt::Display for Bytecode {
|
||||
Bytecode::Sub => write!(f, "SUB"),
|
||||
Bytecode::Mul => write!(f, "MUL"),
|
||||
Bytecode::Div => write!(f, "DIV"),
|
||||
Bytecode::Mod => write!(f, "MOD"),
|
||||
Bytecode::BitAnd => write!(f, "BITAND"),
|
||||
Bytecode::BitOr => write!(f, "BITOR"),
|
||||
Bytecode::BitXor => write!(f, "BITXOR"),
|
||||
Bytecode::BitNot => write!(f, "BITNOT"),
|
||||
Bytecode::Shl => write!(f, "SHL"),
|
||||
Bytecode::Shr => write!(f, "SHR"),
|
||||
Bytecode::Eq => write!(f, "EQ"),
|
||||
Bytecode::NotEq => write!(f, "NEQ"),
|
||||
Bytecode::Lt => write!(f, "LT"),
|
||||
|
||||
@@ -273,6 +273,12 @@ impl Codegen {
|
||||
BinOp::GtEq => Bytecode::GtEq,
|
||||
BinOp::And => Bytecode::And,
|
||||
BinOp::Or => Bytecode::Or,
|
||||
BinOp::Mod => Bytecode::Mod,
|
||||
BinOp::BitAnd => Bytecode::BitAnd,
|
||||
BinOp::BitOr => Bytecode::BitOr,
|
||||
BinOp::BitXor => Bytecode::BitXor,
|
||||
BinOp::Shl => Bytecode::Shl,
|
||||
BinOp::Shr => Bytecode::Shr,
|
||||
};
|
||||
self.emit(instr);
|
||||
}
|
||||
@@ -280,6 +286,10 @@ impl Codegen {
|
||||
self.gen_expr(inner)?;
|
||||
self.emit(Bytecode::Not);
|
||||
}
|
||||
Expr::UnaryBitNot(inner) => {
|
||||
self.gen_expr(inner)?;
|
||||
self.emit(Bytecode::BitNot);
|
||||
}
|
||||
Expr::Call { func, args } => {
|
||||
// Push arguments left-to-right
|
||||
for arg in args {
|
||||
@@ -326,8 +336,12 @@ impl Codegen {
|
||||
let after_else = self.current_idx();
|
||||
self.patch_jump(jump_end, after_else);
|
||||
} else {
|
||||
let after_then = self.current_idx();
|
||||
self.patch_jump(jump_false, after_then);
|
||||
// No else branch: if-without-else is a statement, not a value
|
||||
// expression. Discard the then-block's pushed value so both
|
||||
// paths leave the stack at the same height (net zero change).
|
||||
self.emit(Bytecode::Pop);
|
||||
let after_pop = self.current_idx();
|
||||
self.patch_jump(jump_false, after_pop); // false path skips Pop too
|
||||
}
|
||||
}
|
||||
Expr::Match { subject, arms } => {
|
||||
@@ -451,6 +465,15 @@ impl Codegen {
|
||||
fields: field_names,
|
||||
});
|
||||
}
|
||||
Expr::MapLiteral(pairs) => {
|
||||
// Push each key-value pair, then collect with BuildMap.
|
||||
let n = pairs.len() as u32;
|
||||
for (key_expr, val_expr) in pairs {
|
||||
self.gen_expr(key_expr)?;
|
||||
self.gen_expr(val_expr)?;
|
||||
}
|
||||
self.emit(Bytecode::BuildMap(n));
|
||||
}
|
||||
Expr::With { base, updates } => {
|
||||
// Generate base struct clone then apply updates
|
||||
self.gen_expr(base)?;
|
||||
|
||||
@@ -248,6 +248,11 @@ impl Formatter {
|
||||
self.fmt_expr(out, inner, depth);
|
||||
}
|
||||
|
||||
Expr::UnaryBitNot(inner) => {
|
||||
out.push('~');
|
||||
self.fmt_expr(out, inner, depth);
|
||||
}
|
||||
|
||||
Expr::Try(inner) => {
|
||||
self.fmt_expr(out, inner, depth);
|
||||
out.push('?');
|
||||
@@ -427,6 +432,12 @@ impl Formatter {
|
||||
BinOp::GtEq => ">=",
|
||||
BinOp::And => "&&",
|
||||
BinOp::Or => "||",
|
||||
BinOp::Mod => "%",
|
||||
BinOp::BitAnd => "&",
|
||||
BinOp::BitOr => "|",
|
||||
BinOp::BitXor => "^",
|
||||
BinOp::Shl => "<<",
|
||||
BinOp::Shr => ">>",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,9 @@ impl<'src> Lexer<'src> {
|
||||
// ── Operators that may be multi-char ────────────────────────────
|
||||
'+' => Token::Plus,
|
||||
'*' => Token::Star,
|
||||
'%' => Token::Percent,
|
||||
'^' => Token::Caret,
|
||||
'~' => Token::Tilde,
|
||||
'-' => {
|
||||
if self.eat('>') {
|
||||
Token::Arrow
|
||||
@@ -164,14 +167,18 @@ impl<'src> Lexer<'src> {
|
||||
}
|
||||
}
|
||||
'<' => {
|
||||
if self.eat('=') {
|
||||
if self.eat('<') {
|
||||
Token::Shl
|
||||
} else if self.eat('=') {
|
||||
Token::LtEq
|
||||
} else {
|
||||
Token::Lt
|
||||
}
|
||||
}
|
||||
'>' => {
|
||||
if self.eat('=') {
|
||||
if self.eat('>') {
|
||||
Token::Shr
|
||||
} else if self.eat('=') {
|
||||
Token::GtEq
|
||||
} else {
|
||||
Token::Gt
|
||||
@@ -181,10 +188,7 @@ impl<'src> Lexer<'src> {
|
||||
if self.eat('&') {
|
||||
Token::And
|
||||
} else {
|
||||
return Err(LexError::new(
|
||||
LexErrorKind::UnexpectedChar('&'),
|
||||
self.span_from(start),
|
||||
));
|
||||
Token::Ampersand
|
||||
}
|
||||
}
|
||||
'|' => {
|
||||
|
||||
@@ -194,6 +194,18 @@ pub enum Token {
|
||||
Pipe,
|
||||
/// `?` — used both for Optional types and the Try operator
|
||||
QuestionMark,
|
||||
/// `%` — modulo
|
||||
Percent,
|
||||
/// `&` — bitwise AND
|
||||
Ampersand,
|
||||
/// `^` — bitwise XOR
|
||||
Caret,
|
||||
/// `~` — bitwise NOT
|
||||
Tilde,
|
||||
/// `<<` — left shift
|
||||
Shl,
|
||||
/// `>>` — right shift
|
||||
Shr,
|
||||
|
||||
// ── Special ───────────────────────────────────────────────────────────────
|
||||
Eof,
|
||||
@@ -240,6 +252,12 @@ impl std::fmt::Display for Token {
|
||||
Token::At => write!(f, "@"),
|
||||
Token::Pipe => write!(f, "|"),
|
||||
Token::QuestionMark => write!(f, "?"),
|
||||
Token::Percent => write!(f, "%"),
|
||||
Token::Ampersand => write!(f, "&"),
|
||||
Token::Caret => write!(f, "^"),
|
||||
Token::Tilde => write!(f, "~"),
|
||||
Token::Shl => write!(f, "<<"),
|
||||
Token::Shr => write!(f, ">>"),
|
||||
Token::BoolLiteral(b) => write!(f, "{b}"),
|
||||
Token::IntLiteral(n) => write!(f, "{n}"),
|
||||
Token::FloatLiteral(n) => write!(f, "{n}"),
|
||||
|
||||
@@ -84,6 +84,21 @@ pub enum BinOp {
|
||||
Add, Sub, Mul, Div,
|
||||
Eq, NotEq, Lt, Gt, LtEq, GtEq,
|
||||
And, Or,
|
||||
Mod, // %
|
||||
BitAnd, // &
|
||||
BitOr, // | (single pipe)
|
||||
BitXor, // ^
|
||||
Shl, // <<
|
||||
Shr, // >>
|
||||
}
|
||||
|
||||
// ── Unary operators ───────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UnaryOp {
|
||||
Neg, // - (unary minus)
|
||||
Not, // ! (logical not)
|
||||
BitNot, // ~
|
||||
}
|
||||
|
||||
// ── Expressions ───────────────────────────────────────────────────────────────
|
||||
@@ -94,6 +109,7 @@ pub enum Expr {
|
||||
Ident(String),
|
||||
BinOp { op: BinOp, left: Box<Expr>, right: Box<Expr> },
|
||||
UnaryNot(Box<Expr>),
|
||||
UnaryBitNot(Box<Expr>),
|
||||
Call { func: Box<Expr>, args: Vec<Expr> },
|
||||
Block(Vec<Stmt>),
|
||||
Match { subject: Box<Expr>, arms: Vec<MatchArm> },
|
||||
|
||||
@@ -11,7 +11,7 @@ mod error;
|
||||
mod parser;
|
||||
|
||||
pub use ast::{
|
||||
BinOp, Decorator, Expr, Field, Literal, MatchArm, Param, Pattern, Program, ProtocolMethod,
|
||||
BinOp, UnaryOp, Decorator, Expr, Field, Literal, MatchArm, Param, Pattern, Program, ProtocolMethod,
|
||||
SeedStmt, Stmt, TestTarget, TypeExpr, Variant,
|
||||
};
|
||||
pub use error::{ParseError, ParseErrorKind};
|
||||
|
||||
@@ -717,6 +717,48 @@ impl Parser {
|
||||
self.parse_pipe_expr()
|
||||
}
|
||||
|
||||
fn parse_bitwise_or_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_bitwise_xor_expr()?;
|
||||
while self.eat(&Token::Pipe) {
|
||||
let right = self.parse_bitwise_xor_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::BitOr, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_bitwise_xor_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_bitwise_and_expr()?;
|
||||
while self.eat(&Token::Caret) {
|
||||
let right = self.parse_bitwise_and_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::BitXor, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_bitwise_and_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_shift_expr()?;
|
||||
while self.eat(&Token::Ampersand) {
|
||||
let right = self.parse_shift_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::BitAnd, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_shift_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_additive()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Shl => BinOp::Shl,
|
||||
Token::Shr => BinOp::Shr,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_additive()?;
|
||||
left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
/// pipe_expr = or_expr (|> ident)*
|
||||
/// `a |> f` desugars to `Call(f, [a])`
|
||||
fn parse_pipe_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
@@ -766,7 +808,7 @@ impl Parser {
|
||||
}
|
||||
|
||||
fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_additive()?;
|
||||
let mut left = self.parse_bitwise_or_expr()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Lt => BinOp::Lt,
|
||||
@@ -803,6 +845,7 @@ impl Parser {
|
||||
let op = match self.peek() {
|
||||
Token::Star => BinOp::Mul,
|
||||
Token::Slash => BinOp::Div,
|
||||
Token::Percent => BinOp::Mod,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
@@ -817,6 +860,10 @@ impl Parser {
|
||||
let inner = self.parse_unary()?;
|
||||
return Ok(Expr::UnaryNot(Box::new(inner)));
|
||||
}
|
||||
if self.eat(&Token::Tilde) {
|
||||
let inner = self.parse_unary()?;
|
||||
return Ok(Expr::UnaryBitNot(Box::new(inner)));
|
||||
}
|
||||
self.parse_postfix()
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,28 @@ pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("array_last", fn_type(vec![arr_int.clone()], Type::Optional(Box::new(Type::Int))));
|
||||
// array_contains([String], String) -> Bool
|
||||
env.register_fn("array_contains", fn_type(vec![arr_str, Type::String], Type::Bool));
|
||||
// New list/stack/queue builtins
|
||||
env.register_fn("list_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
env.register_fn("list_pop", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("list_pop_front", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("list_peek_last", fn_type(vec![arr_unk.clone()], Type::Unknown));
|
||||
env.register_fn("list_range", fn_type(vec![Type::Int, Type::Int], arr_unk.clone()));
|
||||
env.register_fn("list_new", fn_type(vec![], arr_unk.clone()));
|
||||
env.register_fn("list_empty", fn_type(vec![arr_unk.clone()], Type::Bool));
|
||||
env.register_fn("list_map", fn_type(vec![arr_unk.clone(), Type::String], arr_unk.clone()));
|
||||
env.register_fn("list_filter", fn_type(vec![arr_unk.clone(), Type::String], arr_unk.clone()));
|
||||
env.register_fn("list_reduce", fn_type(vec![arr_unk.clone(), Type::Unknown, Type::String], Type::Unknown));
|
||||
env.register_fn("fn_ref", fn_type(vec![Type::String], Type::String));
|
||||
// Stack
|
||||
env.register_fn("stack_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
env.register_fn("stack_pop", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("stack_peek", fn_type(vec![arr_unk.clone()], Type::Unknown));
|
||||
env.register_fn("stack_new", fn_type(vec![], arr_unk.clone()));
|
||||
// Queue
|
||||
env.register_fn("queue_enqueue", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
env.register_fn("queue_dequeue", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("queue_peek", fn_type(vec![arr_unk.clone()], Type::Unknown));
|
||||
env.register_fn("queue_new", fn_type(vec![], arr_unk));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -16,6 +16,45 @@ pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("math_abs_int", fn_type(vec![Type::Int], Type::Int));
|
||||
env.register_fn("math_max_int", fn_type(vec![Type::Int, Type::Int], Type::Int));
|
||||
env.register_fn("math_min_int", fn_type(vec![Type::Int, Type::Int], Type::Int));
|
||||
// Trig
|
||||
env.register_fn("math_sin", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_cos", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_tan", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_asin", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_acos", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_atan2", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_exp", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_ln", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_log2", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_log10", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_mod", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_pi", fn_type(vec![], Type::Float));
|
||||
env.register_fn("math_e", fn_type(vec![], Type::Float));
|
||||
// Conversion
|
||||
env.register_fn("int_to_float", fn_type(vec![Type::Int], Type::Float));
|
||||
env.register_fn("float_to_int", fn_type(vec![Type::Float], Type::Int));
|
||||
env.register_fn("is_nil", fn_type(vec![Type::Unknown], Type::Bool));
|
||||
env.register_fn("unwrap_or", fn_type(vec![Type::Unknown, Type::Unknown], Type::Unknown));
|
||||
// Decimal
|
||||
env.register_fn("decimal_add", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_sub", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_mul", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_div", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_round", fn_type(vec![Type::Float, Type::Int], Type::Float));
|
||||
// Time
|
||||
env.register_fn("time_now_utc", fn_type(vec![], Type::Int));
|
||||
env.register_fn("time_to_parts", fn_type(vec![Type::Int], Type::Unknown));
|
||||
env.register_fn("time_from_parts", fn_type(vec![Type::Unknown], Type::Int));
|
||||
env.register_fn("time_format", fn_type(vec![Type::Int, Type::String], Type::String));
|
||||
env.register_fn("time_parse", fn_type(vec![Type::String], Type::Int));
|
||||
env.register_fn("time_add", fn_type(vec![Type::Int, Type::Int, Type::String], Type::Int));
|
||||
env.register_fn("time_diff", fn_type(vec![Type::Int, Type::Int, Type::String], Type::Int));
|
||||
env.register_fn("time_start_of", fn_type(vec![Type::Int, Type::String], Type::Int));
|
||||
env.register_fn("time_tz_offset", fn_type(vec![Type::String], Type::Int));
|
||||
env.register_fn("time_to_tz", fn_type(vec![Type::Int, Type::String], Type::Unknown));
|
||||
// Observer
|
||||
env.register_fn("observe", fn_type(vec![Type::String, Type::String], Type::Int));
|
||||
env.register_fn("unobserve", fn_type(vec![Type::Int], Type::Unknown));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -27,6 +27,14 @@ pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("string_from_float", fn_type(vec![Type::Float], Type::String));
|
||||
env.register_fn("string_is_empty", fn_type(vec![Type::String], Type::Bool));
|
||||
env.register_fn("string_concat", fn_type(vec![Type::String, Type::String], Type::String));
|
||||
// New string builtins
|
||||
env.register_fn("str_char_at", fn_type(vec![Type::String, Type::Int], Type::String));
|
||||
env.register_fn("str_char_code", fn_type(vec![Type::String, Type::Int], Type::Int));
|
||||
env.register_fn("str_from_char_code", fn_type(vec![Type::Int], Type::String));
|
||||
env.register_fn("str_pad_left", fn_type(vec![Type::String, Type::Int, Type::String], Type::String));
|
||||
env.register_fn("str_pad_right", fn_type(vec![Type::String, Type::Int, Type::String], Type::String));
|
||||
env.register_fn("format_float", fn_type(vec![Type::Float, Type::Int], Type::String));
|
||||
env.register_fn("str_format", fn_type(vec![Type::String, Type::Unknown], Type::String));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -354,6 +354,30 @@ impl<'g> Evaluator<'g> {
|
||||
BinOp::GtEq => self.compare_ord(lv, rv, |o| o != std::cmp::Ordering::Less),
|
||||
BinOp::And => Ok(EvalValue::Bool(lv.as_bool() && rv.as_bool())),
|
||||
BinOp::Or => Ok(EvalValue::Bool(lv.as_bool() || rv.as_bool())),
|
||||
BinOp::Mod => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) if b != 0 => Ok(EvalValue::Int(a % b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::BitAnd => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a & b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::BitOr => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a | b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::BitXor => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a ^ b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::Shl => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a << (b as u32))),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::Shr => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a >> (b as u32))),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +438,8 @@ pub fn expr_to_text(expr: &Expr) -> String {
|
||||
BinOp::Eq => "==", BinOp::NotEq => "!=",
|
||||
BinOp::Lt => "<", BinOp::Gt => ">", BinOp::LtEq => "<=", BinOp::GtEq => ">=",
|
||||
BinOp::And => "&&", BinOp::Or => "||",
|
||||
BinOp::Mod => "%", BinOp::BitAnd => "&", BinOp::BitOr => "|",
|
||||
BinOp::BitXor => "^", BinOp::Shl => "<<", BinOp::Shr => ">>",
|
||||
};
|
||||
format!("{} {op_str} {}", expr_to_text(left), expr_to_text(right))
|
||||
}
|
||||
|
||||
@@ -493,6 +493,10 @@ impl TypeChecker {
|
||||
Expr::Reason { .. } => Type::String,
|
||||
Expr::Parallel { .. } => Type::Unknown,
|
||||
Expr::Trace { .. } => Type::Unknown,
|
||||
Expr::UnaryBitNot(inner) => {
|
||||
self.infer_expr(inner);
|
||||
Type::Int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,6 +553,8 @@ impl TypeChecker {
|
||||
}
|
||||
Type::Bool
|
||||
}
|
||||
BinOp::Mod | BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor
|
||||
| BinOp::Shl | BinOp::Shr => Type::Int,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user