diff options
| author | Edvard Thörnros <edvard.thornros@gmail.com> | 2021-02-02 20:22:02 +0100 |
|---|---|---|
| committer | Edvard Thörnros <edvard.thornros@gmail.com> | 2021-02-02 20:22:02 +0100 |
| commit | 09bc674a9126d0481834163afddfceb18a4e3acd (patch) | |
| tree | 2760ee16d958ea6ace5001845fc498f09e32e1a2 | |
| parent | 5b9de776ba7de654fa92bee7c38fb30d7929405d (diff) | |
| download | sylt-09bc674a9126d0481834163afddfceb18a4e3acd.tar.gz | |
reorder some stuff
| -rw-r--r-- | src/lib.rs | 915 |
1 files changed, 456 insertions, 459 deletions
@@ -57,302 +57,86 @@ pub fn run(tokens: TokenStream, path: &Path, print: bool, functions: Vec<(String } } -#[cfg(test)] -mod tests { - use std::path::Path; - - use crate::error::ErrorKind; - - use super::{run_file, run_string}; +pub type RustFunction = fn(&[Value], bool) -> Result<Value, ErrorKind>; - #[macro_export] - macro_rules! assert_errs { - ($result:expr, [ $( $kind:pat ),* ]) => { - eprintln!("{} => {:?}", stringify!($result), $result); - assert!(matches!( - $result.unwrap_err().as_slice(), - &[$($crate::error::Error { - kind: $kind, - file: _, - line: _, - message: _, - }, - )*] - )) - }; - } +#[derive(Debug, Clone)] +pub enum Type { + Void, + UnknownType, + Int, + Float, + Bool, + String, + Tuple(Vec<Type>), + Function(Vec<Type>, Box<Type>), + Blob(usize), + BlobInstance(usize), +} - #[macro_export] - macro_rules! test_string { - ($fn:ident, $prog:literal) => { - #[test] - fn $fn() { - $crate::run_string($prog, true, Vec::new()).unwrap(); - } - }; - ($fn:ident, $prog:literal, $errs:tt) => { - #[test] - fn $fn() { - $crate::assert_errs!($crate::run_string($prog, true, Vec::new()), $errs); +impl PartialEq for Type { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Type::Void, Type::Void) => true, + (Type::BlobInstance(a), Type::BlobInstance(b)) => a == b, + (Type::Blob(a), Type::Blob(b)) => a == b, + (Type::Int, Type::Int) => true, + (Type::Float, Type::Float) => true, + (Type::Bool, Type::Bool) => true, + (Type::String, Type::String) => true, + (Type::Tuple(a), Type::Tuple(b)) => { + a.iter().zip(b.iter()).all(|(a, b)| a == b) } + (Type::Function(a_args, a_ret), Type::Function(b_args, b_ret)) => + a_args == b_args && a_ret == b_ret, + _ => false, } } +} - #[macro_export] - macro_rules! test_file { - ($fn:ident, $path:literal) => { - #[test] - fn $fn() { - let file = Path::new($path); - run_file(&file, true, Vec::new()).unwrap(); +impl From<&Value> for Type { + fn from(value: &Value) -> Type { + match value { + Value::BlobInstance(i, _) => Type::BlobInstance(*i), + Value::Blob(i) => Type::Blob(*i), + Value::Tuple(v) => { + Type::Tuple(v.iter().map(|x| Type::from(x)).collect()) } - }; + Value::Int(_) => Type::Int, + Value::Float(_) => Type::Float, + Value::Bool(_) => Type::Bool, + Value::String(_) => Type::String, + Value::Function(_, block) => block.borrow().ty.clone(), + _ => Type::Void, + } } +} - #[test] - fn unreachable_token() { - assert_errs!(run_string("<!>\n", true, Vec::new()), [ErrorKind::Unreachable]); +impl Type { + pub fn is_unkown(&self) -> bool { + match self { + Type::UnknownType => true, + _ => false, + } } - macro_rules! test_multiple { - ($mod:ident, $( $fn:ident : $prog:literal ),+ $( , )? ) => { - mod $mod { - $( test_string!($fn, $prog); )+ + pub fn as_value(&self) -> Value { + match self { + Type::Void => Value::Nil, + Type::Blob(i) => Value::Blob(*i), + Type::BlobInstance(i) => Value::BlobInstance(*i, Rc::new(RefCell::new(Vec::new()))), + Type::Tuple(fields) => { + Value::Tuple(Rc::new(fields.iter().map(|x| x.as_value()).collect())) } + Type::UnknownType => Value::Unkown, + Type::Int => Value::Int(1), + Type::Float => Value::Float(1.0), + Type::Bool => Value::Bool(true), + Type::String => Value::String(Rc::new("".to_string())), + Type::Function(_, _) => Value::Function( + Vec::new(), + Rc::new(RefCell::new(Block::from_type(self)))), } } - - test_multiple!( - order_of_operations, - terms_and_factors: "1 + 1 * 2 <=> 3 - 1 * 2 + 3 <=> 5", - in_rhs: "5 <=> 1 * 2 + 3", - parenthesis: "(1 + 2) * 3 <=> 9", - negation: "-1 <=> 0 - 1 - -1 + 2 <=> 1 - -(1 + 2) <=> -3 - 1 + -1 <=> 0 - 2 * -1 <=> -2", - ); - - test_multiple!( - variables, - single_variable: "a := 1 - a <=> 1", - two_variables: "a := 1 - b := 2 - a <=> 1 - b <=> 2", - stack_ordering: "a := 1 - b := 2 - b <=> 2 - a <=> 1", - assignment: "a := 1 - b := 2 - a = b - a <=> 2 - b <=> 2", - ); - - test_multiple!( - if_, - compare_constants_equality: "if 1 == 2 { - <!> - }", - compare_constants_unequality: "if 1 != 1 { - <!> - }", - compare_variable: "a := 1 - if a == 0 { - <!> - } - if a != 1 { - <!> - }", - else_: "a := 1 - res := 0 - if a == 0 { - <!> - } else { - res = 1 - } - res <=> 1", - else_if: "a := 1 - res := 0 - if a == 0 { - <!> - } else if a == 1 { - res = 1 - } else { - <!> - } - res <=> 1", - ); - - test_multiple!( - fun, - simplest: "f := fn {} - f()", - param_1: "f := fn a: int {} - f(1)", - return_1: "f := fn -> int { - ret 1 - } - f() <=> 1", - param_and_return: "f := fn a: int -> int { - ret a * 2 - } - f(1) <=> 2 - f(5) <=> 10", - param_2: "add := fn a: int, b: int -> int { - ret a + b - } - add(1, 1) <=> 2 - add(10, 20) <=> 30", - calls_inside_calls: "one := fn -> int { - ret 1 - } - add := fn a: int, b: int -> int { - ret a + b - } - add(one(), one()) <=> 2 - add(add(one(), one()), one()) <=> 3 - add(one(), add(one(), one())) <=> 3", - passing_functions: "g := fn -> int { - ret 1 - } - f := fn inner: fn -> int -> int { - ret inner() - } - f(g) <=> 1", - passing_functions_mixed: "g := fn a: int -> int { - ret a * 2 - } - f := fn inner: fn int -> int, a: int -> int { - ret inner(a) - } - f(g, 2) <=> 4", - multiple_returns: "f := fn a: int -> int { - if a == 1 { - ret 2 - } else { - ret 3 - } - } - f(0) <=> 3 - f(1) <=> 2 - f(2) <=> 3", - precedence: "f := fn a: int, b: int -> int { - ret a + b - } - 1 + f(2, 3) <=> 6 - 2 * f(2, 3) <=> 10 - f(2, 3) - (2 + 3) <=> 0", - factorial: "factorial : fn int -> int = fn n: int -> int { - if n <= 1 { - ret 1 - } - ret n * factorial(n - 1) - } - factorial(5) <=> 120 - factorial(6) <=> 720 - factorial(12) <=> 479001600", - - returning_closures: " -f : fn -> fn -> int = fn -> fn -> int { - x : int = 0 - f := fn -> int { - x = x + 1 - ret x - } - f() <=> 1 - ret f -} - -a := f() -b := f() - -a() <=> 2 -a() <=> 3 - -b() <=> 2 -b() <=> 3 - -a() <=> 4 -" - - //TODO this tests doesn't terminate in proper time if we print blocks and ops - /* - fibonacci: "fibonacci : fn int -> int = fn n: int -> int { - if n == 0 { - ret 0 - } else if n == 1 { - ret 1 - } else if n < 0 { - <!> - } - ret fibonacci(n - 1) + fibonacci(n - 2) - } - fibonacci(10) <=> 55 - fibonacci(20) <=> 6765" - */ - ); - - test_multiple!( - blob, - simple: "blob A {}", - instantiate: "blob A {} - a := A()", - field: "blob A { a: int }", - field_assign: "blob A { a: int } - a := A() - a.a = 2", - field_get: "blob A { a: int } - a := A() - a.a = 2 - a.a <=> 2 - 2 <=> a.a", - multiple_fields: "blob A { - a: int - b: int - } - a := A() - a.a = 2 - a.b = 3 - a.a + a.b <=> 5 - 5 <=> a.a + a.b" - ); - - test_multiple!(tuples, - add: "(1, 2, 3, 4) + (4, 3, 2, 1) <=> (5, 5, 5, 5)", - sub: "(1, -2, 3, -4) - (4, 3, -2, -1) <=> (-3, 1, 1, -5)", - mul: "(0, 1, 2) * (2, 3, 4) <=> (0, 3, 8)", - types: "a: (int, float, int) = (1, 1., 1)", - more_types: "a: (str, bool, int) = (\"abc\", true, 1)", - ); - - test_file!(scoping, "progs/tests/scoping.sy"); - test_file!(for_, "progs/tests/for.sy"); - - test_multiple!( - op_assign, - add: "a := 1\na += 1\na <=> 2", - sub: "a := 2\na -= 1\na <=> 1", - mul: "a := 2\na *= 2\na <=> 4", - div: "a := 2\na /= 2\na <=> 1", - cluster: " -blob A { a: int } -a := A() -a.a = 0 -a.a += 1 -a.a <=> 1 -a.a *= 2 -a.a <=> 2 -a.a /= 2 -a.a <=> 1 -a.a -= 1 -a.a <=> 0" - ); } #[derive(Clone)] @@ -370,6 +154,46 @@ pub enum Value { Nil, } +impl Debug for Value { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Blob(i) => write!(fmt, "(blob {})", i), + Value::BlobInstance(i, v) => write!(fmt, "(inst {} {:?})", i, v), + Value::Float(f) => write!(fmt, "(float {})", f), + Value::Int(i) => write!(fmt, "(int {})", i), + Value::Bool(b) => write!(fmt, "(bool {})", b), + Value::String(s) => write!(fmt, "(string \"{}\")", s), + Value::Function(_, block) => write!(fmt, "(fn {}: {:?})", block.borrow().name, block.borrow().ty), + Value::ExternFunction(slot) => write!(fmt, "(extern fn {})", slot), + Value::Unkown => write!(fmt, "(unkown)"), + Value::Nil => write!(fmt, "(nil)"), + Value::Tuple(v) => write!(fmt, "({:?})", v), + } + } +} + +impl Value { + fn identity(self) -> Self { + match self { + Value::Float(_) => Value::Float(1.0), + Value::Int(_) => Value::Int(1), + Value::Bool(_) => Value::Bool(true), + a => a, + } + } + + fn is_nil(&self) -> bool { + match self { + Value::Nil => true, + _ => false, + } + } + + fn as_type(&self) -> Type { + Type::from(self) + } +} + #[derive(Clone, Debug)] pub struct UpValue { slot: usize, @@ -411,24 +235,83 @@ impl UpValue { } } -impl Debug for Value { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Blob(i) => write!(fmt, "(blob {})", i), - Value::BlobInstance(i, v) => write!(fmt, "(inst {} {:?})", i, v), - Value::Float(f) => write!(fmt, "(float {})", f), - Value::Int(i) => write!(fmt, "(int {})", i), - Value::Bool(b) => write!(fmt, "(bool {})", b), - Value::String(s) => write!(fmt, "(string \"{}\")", s), - Value::Function(_, block) => write!(fmt, "(fn {}: {:?})", block.borrow().name, block.borrow().ty), - Value::ExternFunction(slot) => write!(fmt, "(extern fn {})", slot), - Value::Unkown => write!(fmt, "(unkown)"), - Value::Nil => write!(fmt, "(nil)"), - Value::Tuple(v) => write!(fmt, "({:?})", v), +#[derive(Debug, Clone)] +pub struct Blob { + pub name: String, + + pub name_to_field: HashMap<String, (usize, Type)>, +} + +impl Blob { + pub fn new(name: &str) -> Self { + Self { + name: String::from(name), + name_to_field: HashMap::new(), + } + } + + pub fn add_field(&mut self, name: &str, ty: Type) -> Result<(), ()> { + let size = self.name_to_field.len(); + let entry = self.name_to_field.entry(String::from(name)); + if matches!(entry, Entry::Occupied(_)) { + Err(()) + } else { + entry.or_insert((size, ty)); + Ok(()) } } } +#[derive(Debug, Clone)] +pub enum Op { + Illegal, + + Pop, + PopUpvalue, + Copy, + Constant(Value), + Tuple(usize), + + Index, + Get(String), + Set(String), + + Add, + Sub, + Mul, + Div, + Neg, + + And, + Or, + Not, + + Jmp(usize), + JmpFalse(usize), + + Equal, // == + Less, // < + Greater, // > + + Assert, + Unreachable, + + ReadLocal(usize), + AssignLocal(usize), + + ReadUpvalue(usize), + AssignUpvalue(usize), + + Define(Type), + + Call(usize), + + Print, + + Return, + Yield, +} + mod op { use super::Value; use std::rc::Rc; @@ -528,80 +411,6 @@ mod op { _ => Value::Nil, } } - -} - - -impl Value { - fn identity(self) -> Self { - match self { - Value::Float(_) => Value::Float(1.0), - Value::Int(_) => Value::Int(1), - Value::Bool(_) => Value::Bool(true), - a => a, - } - } - - fn is_nil(&self) -> bool { - match self { - Value::Nil => true, - _ => false, - } - } - - fn as_type(&self) -> Type { - Type::from(self) - } -} - -#[derive(Debug, Clone)] -pub enum Op { - Illegal, - - Pop, - PopUpvalue, - Copy, - Constant(Value), - Tuple(usize), - - Index, - Get(String), - Set(String), - - Add, - Sub, - Mul, - Div, - Neg, - - And, - Or, - Not, - - Jmp(usize), - JmpFalse(usize), - - Equal, // == - Less, // < - Greater, // > - - Assert, - Unreachable, - - ReadLocal(usize), - AssignLocal(usize), - - ReadUpvalue(usize), - AssignUpvalue(usize), - - Define(Type), - - Call(usize), - - Print, - - Return, - Yield, } #[derive(Debug)] @@ -717,7 +526,6 @@ impl Block { } } - #[derive(Clone)] pub struct Prog { pub blocks: Vec<Rc<RefCell<Block>>>, @@ -725,111 +533,300 @@ pub struct Prog { pub functions: Vec<RustFunction>, } -#[derive(Debug, Clone)] -pub enum Type { - Void, - UnknownType, - Int, - Float, - Bool, - String, - Tuple(Vec<Type>), - Function(Vec<Type>, Box<Type>), - Blob(usize), - BlobInstance(usize), -} +#[cfg(test)] +mod tests { + use std::path::Path; -impl PartialEq for Type { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Type::Void, Type::Void) => true, - (Type::BlobInstance(a), Type::BlobInstance(b)) => a == b, - (Type::Blob(a), Type::Blob(b)) => a == b, - (Type::Int, Type::Int) => true, - (Type::Float, Type::Float) => true, - (Type::Bool, Type::Bool) => true, - (Type::String, Type::String) => true, - (Type::Tuple(a), Type::Tuple(b)) => { - a.iter().zip(b.iter()).all(|(a, b)| a == b) + use crate::error::ErrorKind; + + use super::{run_file, run_string}; + + #[macro_export] + macro_rules! assert_errs { + ($result:expr, [ $( $kind:pat ),* ]) => { + eprintln!("{} => {:?}", stringify!($result), $result); + assert!(matches!( + $result.unwrap_err().as_slice(), + &[$($crate::error::Error { + kind: $kind, + file: _, + line: _, + message: _, + }, + )*] + )) + }; + } + + #[macro_export] + macro_rules! test_string { + ($fn:ident, $prog:literal) => { + #[test] + fn $fn() { + $crate::run_string($prog, true, Vec::new()).unwrap(); + } + }; + ($fn:ident, $prog:literal, $errs:tt) => { + #[test] + fn $fn() { + $crate::assert_errs!($crate::run_string($prog, true, Vec::new()), $errs); } - (Type::Function(a_args, a_ret), Type::Function(b_args, b_ret)) => - a_args == b_args && a_ret == b_ret, - _ => false, } } -} -impl From<&Value> for Type { - fn from(value: &Value) -> Type { - match value { - Value::BlobInstance(i, _) => Type::BlobInstance(*i), - Value::Blob(i) => Type::Blob(*i), - Value::Tuple(v) => { - Type::Tuple(v.iter().map(|x| Type::from(x)).collect()) + #[macro_export] + macro_rules! test_file { + ($fn:ident, $path:literal) => { + #[test] + fn $fn() { + let file = Path::new($path); + run_file(&file, true, Vec::new()).unwrap(); } - Value::Int(_) => Type::Int, - Value::Float(_) => Type::Float, - Value::Bool(_) => Type::Bool, - Value::String(_) => Type::String, - Value::Function(_, block) => block.borrow().ty.clone(), - _ => Type::Void, - } + }; } -} -impl Type { - pub fn is_unkown(&self) -> bool { - match self { - Type::UnknownType => true, - _ => false, - } + #[test] + fn unreachable_token() { + assert_errs!(run_string("<!>\n", true, Vec::new()), [ErrorKind::Unreachable]); } - pub fn as_value(&self) -> Value { - match self { - Type::Void => Value::Nil, - Type::Blob(i) => Value::Blob(*i), - Type::BlobInstance(i) => Value::BlobInstance(*i, Rc::new(RefCell::new(Vec::new()))), - Type::Tuple(fields) => { - Value::Tuple(Rc::new(fields.iter().map(|x| x.as_value()).collect())) + macro_rules! test_multiple { + ($mod:ident, $( $fn:ident : $prog:literal ),+ $( , )? ) => { + mod $mod { + $( test_string!($fn, $prog); )+ } - Type::UnknownType => Value::Unkown, - Type::Int => Value::Int(1), - Type::Float => Value::Float(1.0), - Type::Bool => Value::Bool(true), - Type::String => Value::String(Rc::new("".to_string())), - Type::Function(_, _) => Value::Function( - Vec::new(), - Rc::new(RefCell::new(Block::from_type(self)))), } } -} -pub type RustFunction = fn(&[Value], bool) -> Result<Value, ErrorKind>; + test_multiple!( + order_of_operations, + terms_and_factors: "1 + 1 * 2 <=> 3 + 1 * 2 + 3 <=> 5", + in_rhs: "5 <=> 1 * 2 + 3", + parenthesis: "(1 + 2) * 3 <=> 9", + negation: "-1 <=> 0 - 1 + -1 + 2 <=> 1 + -(1 + 2) <=> -3 + 1 + -1 <=> 0 + 2 * -1 <=> -2", + ); -#[derive(Debug, Clone)] -pub struct Blob { - pub name: String, + test_multiple!( + variables, + single_variable: "a := 1 + a <=> 1", + two_variables: "a := 1 + b := 2 + a <=> 1 + b <=> 2", + stack_ordering: "a := 1 + b := 2 + b <=> 2 + a <=> 1", + assignment: "a := 1 + b := 2 + a = b + a <=> 2 + b <=> 2", + ); - pub name_to_field: HashMap<String, (usize, Type)>, -} + test_multiple!( + if_, + compare_constants_equality: "if 1 == 2 { + <!> + }", + compare_constants_unequality: "if 1 != 1 { + <!> + }", + compare_variable: "a := 1 + if a == 0 { + <!> + } + if a != 1 { + <!> + }", + else_: "a := 1 + res := 0 + if a == 0 { + <!> + } else { + res = 1 + } + res <=> 1", + else_if: "a := 1 + res := 0 + if a == 0 { + <!> + } else if a == 1 { + res = 1 + } else { + <!> + } + res <=> 1", + ); -impl Blob { - pub fn new(name: &str) -> Self { - Self { - name: String::from(name), - name_to_field: HashMap::new(), - } - } + test_multiple!( + fun, + simplest: "f := fn {} + f()", + param_1: "f := fn a: int {} + f(1)", + return_1: "f := fn -> int { + ret 1 + } + f() <=> 1", + param_and_return: "f := fn a: int -> int { + ret a * 2 + } + f(1) <=> 2 + f(5) <=> 10", + param_2: "add := fn a: int, b: int -> int { + ret a + b + } + add(1, 1) <=> 2 + add(10, 20) <=> 30", + calls_inside_calls: "one := fn -> int { + ret 1 + } + add := fn a: int, b: int -> int { + ret a + b + } + add(one(), one()) <=> 2 + add(add(one(), one()), one()) <=> 3 + add(one(), add(one(), one())) <=> 3", + passing_functions: "g := fn -> int { + ret 1 + } + f := fn inner: fn -> int -> int { + ret inner() + } + f(g) <=> 1", + passing_functions_mixed: "g := fn a: int -> int { + ret a * 2 + } + f := fn inner: fn int -> int, a: int -> int { + ret inner(a) + } + f(g, 2) <=> 4", + multiple_returns: "f := fn a: int -> int { + if a == 1 { + ret 2 + } else { + ret 3 + } + } + f(0) <=> 3 + f(1) <=> 2 + f(2) <=> 3", + precedence: "f := fn a: int, b: int -> int { + ret a + b + } + 1 + f(2, 3) <=> 6 + 2 * f(2, 3) <=> 10 + f(2, 3) - (2 + 3) <=> 0", + factorial: "factorial : fn int -> int = fn n: int -> int { + if n <= 1 { + ret 1 + } + ret n * factorial(n - 1) + } + factorial(5) <=> 120 + factorial(6) <=> 720 + factorial(12) <=> 479001600", - pub fn add_field(&mut self, name: &str, ty: Type) -> Result<(), ()> { - let size = self.name_to_field.len(); - let entry = self.name_to_field.entry(String::from(name)); - if matches!(entry, Entry::Occupied(_)) { - Err(()) - } else { - entry.or_insert((size, ty)); - Ok(()) - } + returning_closures: " +f : fn -> fn -> int = fn -> fn -> int { + x : int = 0 + f := fn -> int { + x = x + 1 + ret x } + f() <=> 1 + ret f +} + +a := f() +b := f() + +a() <=> 2 +a() <=> 3 + +b() <=> 2 +b() <=> 3 + +a() <=> 4 +" + + //TODO this tests doesn't terminate in proper time if we print blocks and ops + /* + fibonacci: "fibonacci : fn int -> int = fn n: int -> int { + if n == 0 { + ret 0 + } else if n == 1 { + ret 1 + } else if n < 0 { + <!> + } + ret fibonacci(n - 1) + fibonacci(n - 2) + } + fibonacci(10) <=> 55 + fibonacci(20) <=> 6765" + */ + ); + + test_multiple!( + blob, + simple: "blob A {}", + instantiate: "blob A {} + a := A()", + field: "blob A { a: int }", + field_assign: "blob A { a: int } + a := A() + a.a = 2", + field_get: "blob A { a: int } + a := A() + a.a = 2 + a.a <=> 2 + 2 <=> a.a", + multiple_fields: "blob A { + a: int + b: int + } + a := A() + a.a = 2 + a.b = 3 + a.a + a.b <=> 5 + 5 <=> a.a + a.b" + ); + + test_multiple!(tuples, + add: "(1, 2, 3, 4) + (4, 3, 2, 1) <=> (5, 5, 5, 5)", + sub: "(1, -2, 3, -4) - (4, 3, -2, -1) <=> (-3, 1, 1, -5)", + mul: "(0, 1, 2) * (2, 3, 4) <=> (0, 3, 8)", + types: "a: (int, float, int) = (1, 1., 1)", + more_types: "a: (str, bool, int) = (\"abc\", true, 1)", + ); + + test_file!(scoping, "progs/tests/scoping.sy"); + test_file!(for_, "progs/tests/for.sy"); + + test_multiple!( + op_assign, + add: "a := 1\na += 1\na <=> 2", + sub: "a := 2\na -= 1\na <=> 1", + mul: "a := 2\na *= 2\na <=> 4", + div: "a := 2\na /= 2\na <=> 1", + cluster: " +blob A { a: int } +a := A() +a.a = 0 +a.a += 1 +a.a <=> 1 +a.a *= 2 +a.a <=> 2 +a.a /= 2 +a.a <=> 1 +a.a -= 1 +a.a <=> 0" + ); } |
