Add a basic PRNG to the unit tests.

Exposes lots of flaws tbqh, and we need unsigned numbers to make it
really work properly.  But it does work!
6 files changed, 252 insertions(+), 113 deletions(-)

M src/bin/garnetfmt.rs
M src/builtins.rs
M src/format.rs
M src/passes.rs
M src/typeck.rs
A => tests/programs/test_pcg.gt
M src/bin/garnetfmt.rs +1 -2
@@ 5,7 5,6 @@ use std::path::PathBuf;
 
 use argh::FromArgs;
 
-
 /// Garnet formatter
 #[derive(Debug, FromArgs)]
 struct Opt {

          
@@ 46,7 45,7 @@ fn main() -> io::Result<()> {
         parser.parse()
     };
     if &ast != &formatted_ast {
-        // we want more info here 
+        // we want more info here
         eprintln!("Error, reformatted AST parses differently from original");
         eprintln!("BEFORE:\n{}", src);
         eprintln!("AST: {:#?}", &ast);

          
M src/builtins.rs +146 -100
@@ 1,5 1,8 @@ 
 //! A place for us to stick compiler builtins and the metadata they need,
 //! instead of having them scattered all over.
+//!
+//! TODO: We would really benefit from having a function that can take in a single
+//! number type and output all the math/binary/logic ops for that number.
 
 use std::collections::BTreeMap;
 

          
@@ 16,12 19,139 @@ pub struct Builtin {
     pub sig: Type,
     /// The implmentation of the builtin as raw code for each particular
     /// backend.
-    pub code: BTreeMap<Backend, &'static str>,
+    pub code: BTreeMap<Backend, String>,
 }
 
 pub static BUILTINS: Lazy<Vec<Builtin>> = Lazy::new(Builtin::all);
 
 impl Builtin {
+    /// Generate all appropriate methods for the given numeric type.
+    /// Right now we just stick em in the toplevel with constructed names,
+    /// but eventually they'll have to go into their own module/package/whatever.
+    /// That package will still contain these generated strings though, so oh well.
+    ///
+    /// I guess this is the start of Garnet's macro system, huh?
+    ///
+    /// TODO: rotates, arithmetic shift, wrapping stuff, other?
+    fn generate_numerics_for(name: &str, ty: Type) -> Vec<Builtin> {
+        // names come from luajit I suppose
+        // I'm not in love wtih 'em but also don't want to think about em.
+        let println = format!(
+            r#"
+fn __println_{name}(x: {name}) {{
+    println!("{{}}", x);
+}}"#
+        );
+        let band = format!(
+            r#"
+fn __band_{name}(x: {name}, y: {name}) -> {name} {{
+    x & y
+}}"#
+        );
+        let bor = format!(
+            r#"
+fn __bor_{name}(x: {name}, y: {name}) -> {name} {{
+    x | y
+}}"#
+        );
+        let bxor = format!(
+            r#"
+fn __bxor_{name}(x: {name}, y: {name}) -> {name} {{
+    x ^ y
+}}"#
+        );
+        let bnot = format!(
+            r#"
+fn __bnot_{name}(x: {name}) -> {name} {{
+    !x
+}}"#
+        );
+
+        let rshift = format!(
+            r#"
+fn __rshift_{name}(x: {name}, i: {name}) -> {name} {{
+    x >> i
+}}"#
+        );
+        let lshift = format!(
+            r#"
+fn __lshift_{name}(x: {name}, i: {name}) -> {name} {{
+    x << i
+}}"#
+        );
+        let rol = format!(
+            r#"
+fn __rol_{name}(x: {name}, i: i32) -> {name} {{
+    x.rotate_left(i as u32)
+}}"#
+        );
+        let ror = format!(
+            r#"
+fn __ror_{name}(x: {name}, i: i32) -> {name} {{
+    x.rotate_right(i as u32)
+}}"#
+        );
+
+        let cast_i32 = format!(
+            r#"
+fn __{name}_to_i32(x: {name}) -> i32 {{
+    x as i32
+}}"#
+        );
+
+        vec![
+            Builtin {
+                name: Sym::new(format!("__println_{name}")),
+                sig: Type::Func(vec![ty.clone()], Box::new(Type::unit()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, println)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__band_{name}")),
+                sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, band)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__bor_{name}")),
+                sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, bor)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__bxor_{name}")),
+                sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, bxor)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__bnot_{name}")),
+                sig: Type::Func(vec![ty.clone()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, bnot)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__rshift_{name}")),
+                sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, rshift)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__lshift_{name}")),
+                sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, lshift)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__rol_{name}")),
+                sig: Type::Func(vec![ty.clone(), Type::i32()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, rol)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__ror_{name}")),
+                sig: Type::Func(vec![ty.clone(), Type::i32()], Box::new(ty.clone()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, ror)]),
+            },
+            Builtin {
+                name: Sym::new(format!("__{name}_to_i32")),
+                sig: Type::Func(vec![ty.clone()], Box::new(Type::i32()), vec![]),
+                code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, cast_i32)]),
+            },
+        ]
+    }
     /// A function that returns all the compiler builtin info.  Just
     /// use the `BUILTINS` global instead, this is basically here
     /// to initialize it.

          
@@ 34,112 164,28 @@ fn __println_bool(x: bool) {
     println!("{}", x);
 }"#;
 
-        let rust_println_i64 = r#"
-fn __println_i64(x: i64) {
-    println!("{}", x);
-}"#;
-        let rust_println_i16 = r#" 
-fn __println_i16(x: i16) {
-    println!("{}", x);
-}"#;
-
-        let band = r#"
-fn __band(x: i32, y: i32) -> i32 {
-    x & y
-}"#;
-        let bor = r#"
-fn __bor(x: i32, y: i32) -> i32 {
-    x | y
-}"#;
-        let bxor = r#"
-fn __bxor(x: i32, y: i32) -> i32 {
-    x ^ y
-}"#;
-        let bnot = r#"
-fn __bnot(x: i32) -> i32 {
-    !x
-}"#;
-
-        let rshift = r#"
-fn __rshift(x: i32, i: i32) -> i32 {
-    x >> i
-}"#;
-        let lshift = r#"
-fn __lshift(x: i32, i: i32) -> i32 {
-    x << i
-}"#;
-        vec![
+        let mut funcs = vec![
             Builtin {
                 name: Sym::new("__println"),
                 sig: Type::Func(vec![Type::i32()], Box::new(Type::unit()), vec![]),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println)]),
+                code: BTreeMap::from([
+                    (Backend::Null, "".into()),
+                    (Backend::Rust, rust_println.into()),
+                ]),
             },
             Builtin {
                 name: Sym::new("__println_bool"),
                 sig: Type::Func(vec![Type::bool()], Box::new(Type::unit()), vec![]),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println_bool)]),
-            },
-            Builtin {
-                name: Sym::new("__println_i64"),
-                sig: Type::Func(vec![Type::i64()], Box::new(Type::unit()), vec![]),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println_i64)]),
-            },
-            Builtin {
-                name: Sym::new("__println_i16"),
-                sig: Type::Func(vec![Type::i16()], Box::new(Type::unit()), vec![]),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println_i16)]),
-            },
-            // names come from luajit I suppose
-            Builtin {
-                name: Sym::new("__band"),
-                sig: Type::Func(
-                    vec![Type::i32(), Type::i32()],
-                    Box::new(Type::i32()),
-                    vec![],
-                ),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, band)]),
-            },
-            Builtin {
-                name: Sym::new("__bor"),
-                sig: Type::Func(
-                    vec![Type::i32(), Type::i32()],
-                    Box::new(Type::i32()),
-                    vec![],
-                ),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, bor)]),
+                code: BTreeMap::from([
+                    (Backend::Null, "".into()),
+                    (Backend::Rust, rust_println_bool.into()),
+                ]),
             },
-            Builtin {
-                name: Sym::new("__bxor"),
-                sig: Type::Func(
-                    vec![Type::i32(), Type::i32()],
-                    Box::new(Type::i32()),
-                    vec![],
-                ),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, bxor)]),
-            },
-            Builtin {
-                name: Sym::new("__bnot"),
-                sig: Type::Func(vec![Type::i32()], Box::new(Type::i32()), vec![]),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, bnot)]),
-            },
-            Builtin {
-                name: Sym::new("__rshift"),
-                sig: Type::Func(
-                    vec![Type::i32(), Type::i32()],
-                    Box::new(Type::i32()),
-                    vec![],
-                ),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rshift)]),
-            },
-            Builtin {
-                name: Sym::new("__lshift"),
-                sig: Type::Func(
-                    vec![Type::i32(), Type::i32()],
-                    Box::new(Type::i32()),
-                    vec![],
-                ),
-                code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, lshift)]),
-            },
-        ]
+        ];
+        funcs.extend(Self::generate_numerics_for("i8", Type::i8()));
+        funcs.extend(Self::generate_numerics_for("i16", Type::i16()));
+        funcs.extend(Self::generate_numerics_for("i32", Type::i32()));
+        funcs.extend(Self::generate_numerics_for("i64", Type::i64()));
+        funcs
     }
 }

          
M src/format.rs +10 -6
@@ 33,16 33,21 @@ fn unparse_decl(d: &Decl, out: &mut dyn 
             doc_comment,
         } => {
             for line in doc_comment.iter() {
-                write!(out, "--- {}", line)?;
+                write!(out, "---{}", line)?;
             }
             let name = name.val();
             let tname = typename.get_name();
             write!(out, "const {} {} = ", name, tname)?;
             unparse_expr(init, 0, out)
         }
-        Decl::TypeDef { name, params, typedecl, doc_comment } => {
+        Decl::TypeDef {
+            name,
+            params,
+            typedecl,
+            doc_comment,
+        } => {
             for line in doc_comment.iter() {
-                write!(out, "--- {}", line)?;
+                write!(out, "---{}", line)?;
             }
             let name = name.val();
             let tname = typedecl.get_name();

          
@@ 63,7 68,6 @@ fn unparse_decl(d: &Decl, out: &mut dyn 
                 writeln!(out, "type {}({}) = {}", name, paramstr, tname)?;
             }
             writeln!(out)
-            
         }
         Decl::Import { name, rename } => {
             if let Some(re) = rename {

          
@@ 89,9 93,9 @@ fn unparse_sig(sig: &Signature, out: &mu
             }
             write!(out, "{}", name.get_name())?;
         }
-        write!(out, "| ")?; 
+        write!(out, "| ")?;
     }
-    
+
     // Write (foo I32, bar I16)
     // not (foo I32, bar I16, )
     let mut first = true;

          
M src/passes.rs +1 -1
@@ 260,7 260,7 @@ fn exprs_map_pre(exprs: Vec<ExprNode>, f
     exprs_map(exprs, f, &mut id)
 }
 
-fn exprs_map_post(exprs: Vec<ExprNode>, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> Vec<ExprNode> {
+fn _exprs_map_post(exprs: Vec<ExprNode>, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> Vec<ExprNode> {
     exprs_map(exprs, &mut id, f)
 }
 

          
M src/typeck.rs +28 -4
@@ 128,7 128,12 @@ pub enum TypeError {
         got: Type,
         expected: Type,
     },
-    TypeListMismatch, // TODO
+    TypeListMismatch {
+        // We can't necessarily display TypeId's sensibly, so I guess
+        // we haven to turn them into strings
+        expected: String,
+        got: String,
+    },
     AmbiguousType {
         expr_name: Cow<'static, str>,
     },

          
@@ 229,8 234,8 @@ impl TypeError {
                 expected.get_name(),
                 got.get_name()
             ),
-            TypeError::TypeListMismatch => {
-                "Type list mismatch, make better error message".to_string()
+            TypeError::TypeListMismatch { expected, got } => {
+                format!("Type list mismatch\nExpected {}, got {}", expected, got)
             }
             TypeError::AmbiguousType { expr_name } => {
                 format!("Ambiguous/unknown type for expression '{}'", expr_name)

          
@@ 557,7 562,24 @@ impl Tck {
             }
             Ok(())
         } else {
-            Err(TypeError::TypeListMismatch)
+            // gramble gramble no `Iterator::intersperse()`` in stable
+            let mut expected_str = String::from("[");
+            for e in a {
+                expected_str += &TypeInfo::Ref(*e).get_name(self);
+                expected_str += " ";
+            }
+            expected_str += "]";
+
+            let mut got_str = String::from("[");
+            for e in b {
+                got_str += &TypeInfo::Ref(*e).get_name(self);
+                got_str += " ";
+            }
+            got_str += "]";
+            Err(TypeError::TypeListMismatch {
+                expected: expected_str,
+                got: got_str,
+            })
         }
     }
 

          
@@ 961,6 983,8 @@ fn is_mutable_lvalue(symtbl: &Symtbl, ex
             mutable
         }
         StructRef { expr, .. } => is_mutable_lvalue(symtbl, expr),
+        TupleRef { expr, .. } => is_mutable_lvalue(symtbl, expr),
+        TypeUnwrap { expr } => is_mutable_lvalue(symtbl, expr),
         _ => false,
     }
 }

          
A => tests/programs/test_pcg.gt +66 -0
@@ 0,0 1,66 @@ 
+-- Format:
+--   status: success
+-- Compile:
+--   status: success
+-- Run:
+--   status: success
+--   stdout:
+--     -1403893721
+--     222523091
+--     -1045347539
+
+
+
+
+--- A test implementation of the PCG PRNG from https://sr.ht/~icefox/oorandom
+--- Fails to give perfect results after the first 3 numbers due to signed/unsigned 
+--- arithmetic nonsense, but it's a start.
+type Rand32 = struct
+    state: I64,
+    inc: I64,
+end
+
+const RAND32_DEFAULT_INC I64 = 1442695040888963407
+const RAND32_MULTIPLIER I64 = 6364136223846793005
+
+fn rand32_new_inc(seed I64, inc I64) Rand32 =
+    let rng = Rand32({
+        .state = 0,
+        .inc = __bor_i64(__lshift_i64(inc, 1), 1),
+    })
+    let mut rng2 = rand32_u32(rng).0
+    rng2$.state = rng2$.state + seed
+    let rng3 = rand32_u32(rng2).0
+    rng3
+end
+
+fn rand32_new(seed I64) Rand32 =
+    rand32_new_inc(seed, RAND32_DEFAULT_INC)
+end
+
+fn rand32_u32(rand Rand32) {Rand32, I32} =
+    let oldstate = rand$.state
+    let mut newrng = rand
+    newrng$.state = oldstate * RAND32_MULTIPLIER + rand$.inc
+    -- ok maybe binary op operators are an ok idea
+    let xorshifted = __i64_to_i32( __rshift_i64( __bxor_i64( __rshift_i64(oldstate, 18),  oldstate),  27))
+    let rot = __i64_to_i32(__rshift_i64(oldstate, 59))
+    {newrng, __ror_i32(xorshifted, rot)} 
+end
+
+
+
+
+fn main() {} =
+    let mut rng = rand32_new(54321);
+    let mut i I32 = 0
+    loop
+        if i == 3 then break end
+        i = i + 1
+        
+        let res = rand32_u32(rng)
+        rng = res.0
+        let out I32 = res.1
+        __println(out)
+    end
+end