"C with typeclasses and tagged unions"
GOOD IDEAS 11/13
- When converting a lambda to a dyn lambda, put its environments in the current allocator instead of on the stack
- language level hot reload support. TWEAK_FLOAT(f) thing. Explore this and find out if language support really helps or if it can just be solved by library
- Specialization solution (when types are known by the function)
-
A kind of *pattern* that checks the type and binds a variable of that type! What other thing for a feature that needs to _check_ and _bind_ than a pattern?!
More optimal final programs
- Represent payload-less
eithertypes as ints not structs (Actually might just add enum as separate thing from eithers) - Add 'switch' to bytecode; compile switches with no patterns or guards to LLVM switch
- Unit syntax of '()', as well as the name
unitby the way, makes no sense when we don't have tuples. What about{}
Non-major Ideas
- c"" string literals that are of type ptr (what about interpolation?)
- userland: CCompatString which is a valid c string with length in front of the allocation (ill call it antirez strings)
- User-defined implicit conversions: based on a special ability that integrates with type inference? (like Mojo's ImplicitlyIntable, etc): ImplicitAs?
- [design/flags_in_tags.k1]
- Dogfood idea: 'niched' integer abstraction (-1 as 'not found' but safely, vs using option and wasting space + adding more code)
impl Unwrap<Inner = u32> for { hidden: i64 } - Incorporate ffc.h for int and float parsing
- Inspired by fast_float, char to digit lookup table
Syntax/elegance
- replace
\with 'fn' for lambda notation, one more character and we can be similarly elegant,\x.xbecomesfn x.x - The dereference operator does the opposite of what it looks like: it unpointers things, where the star in the rest of the language makes pointers or keeps them
- Destructuring, (in)fallible patterns
- Default type args for abilities, or partially applied abilities (alias Unwrap[T] = Try[T, unit])
- Rename
viewtospan - Rename 'static' types to 'static value' types
- Need a way to write an interpolated string to a Writer that you already have Let's just call it 'fmt' and make it a special construct
- syntax sugar for the continuous collection types: array, view, buffer. something like
[N] T,[] T,[rw] T- Actually, I think this is bad. Came up with [] T, [mut] T, [+] T, and [N] T, but the names are better
- Lowercase most types, they look overly important, and move to kebab-case to avoid uppercase awkwardness
- Allow users to capitalize domain types, but types like List/Opt/Buffer/Result should sink into the background
- Replace the builtin for ... yield with a userspace function taking a lambda
- Eschew the name 'Unwrap'; twitter is right about that one. Good opportunity to produce a very strong name for this concept
- Consider a rename of 'uword/iword'; they do not feel good to use. What about
uandi.- Ok now I'm really thinking about
sizeand it being signed. - Also: do safe integer coercions automatically
- Ok now I'm really thinking about
- Allow omission of empty paren pair when type args are passed, getTypeName[T] vs getTypeNameT
Simple but missing
- decide if overflow traps or not (in debug and release, if those are even different)
- good backtraces (https://claude.ai/share/245cf54a-22cc-4fb1-8f17-3fd6b2c42812)
- support ability constraints on generics
- support explicit type args in AnonEnumConstructor syntax
- Allow scoped namespace defns;
namespace <ident>/<ident>/<ident> {}, great for metaprogramming to inject stuff currently you could easily justns <ident> { ns <ident> { ns <ident> _stuff_ } } } - META test: Can we build ArrayOfStructs using current metaprogramming?!
- Bindings generator;
rust-bindgenequivalent - implement iterator for array
- Defect: Allow pattern matching into recursive types (currently we just terminate)
- Defect: Generic (co)recursive types do not work
- Require that a blanket impl's params appear in the Self type
- [-] Limitation (ordering): ability impls have to be provided in dependency order, since their constraints can depend on each other. I think I have to do a 'skip and progress' style of pass for them to prevent that. It possibly not worth the complexity
-
Allow using expr-interpolated strings directly into a writer
-
Allow for named non-interpolated args into holes with a struct
-
Implement at least one format specifier (precision, pretty)
-
Return value binding, or named return values, for guaranteed RVO
-
Uninit in struct fields - just don't store there
- AbilitySignature as context variable kind in addition to Type (enables context Writer, context Mem if it ends up an ability)
- let context(impl Alloc) temp = mem/AllocMode.Arena;
- let context(impl Iterator[string]) temp = mem/AllocMode.Arena;
- Test on linux
- Link in lld? ugh. For now maybe just ship with it
- [ ]
- Function inlining
- Prune unreachable blocks
- Fix random jumping to function header
- Annotate U8s with boolean somehow where they are actually booleans
- Remove RecursiveReference; make visitors detect cycles
- add test with co-recursion and infinite recursion
- Support deeper pattern matching on recursive types
Add core and/or compiler support to allow block profiling of k1 programs
- Implement
unionas a thing - Represent enums as
{ tag, union }, notunion { tag, payload }, { tag, payload }, ... - Classify like structs for ABI handling; test with mirrored C types
- Rename
Enum->SumorDUnionin the code - Optimize no-payload enums into non-aggregates, at the typer level.
Primarily an execution target for the VM, but also would DRY up the significant duplication between the two current backends, LLVM and k1::vm.
- Try to compile a function to bytecode
- Draw the rest of the owl (done)
- [-] Real layouts, in a mem pool, to save roundtripping and increase locality -> Kinda done, we now re-use the values in a global 'static stack'
- We'll want to skip the id-based repr entirely for heavy heavy static data I think
- Thread-local globals
- bitcast function (for struct ABI workarounds right now)
- Collapse long runs of zero-only data into a single one in LLVM IR (e.g., mem/allocStack)
- static #for, special-case like IF. Can unroll the loop at comptime but the body is runtime
- VM "PermSpace" for caching converted static values in their VM representation
- Add StaticValue::Zero as an efficient special-case (generalization of the existing NullPointer, actually)
- 'Type predicate': functions taking only a single type could be invoked with a nice syntax like
type.sizeOf - 'Type predicate' functions as type bounds
- Unused var
- Unused type bound
- Footgun, warn on naked variable patterns in 'is'
if self.slots.get(probe_index) is None {
- Treat Unit and empty Struct as ZSTs
- Compile ZSTs to missing arguments, and LLVM void returns, and no-ops inside other types
- Treat statics as ZSTs
- Think about
neverin either variants: Is it a ZST? Does it make the variant unreachable? Result[T, never]
- Hover first pass
- Hover much better
- Hover no more markdown
- Go-to
- Completion
- Separate modules
- Introduce 'module' w/ kind (lib/bin/core), deps, and namespace+scope
- Add entire modules from TypedProgram
- Module manifests somewhere
- Library vs Binary
- Prevent modules using definitions from modules they dont depend on (implicit transitive dependency problem)
- Dependencies: local module
- Specify linked libraries in manifest (Eventually this will need to be more customizable)
- serialize typedprogram at each module completion (for incremental compilation)
- clang passthrough options, when do we 'link', in IR or as object files, ...
- we 'link' with k1 in the typer's modules system
- we link with other deps w/ the linker
It currently runs directly off the typed tree
- Rewrite llvm type generation in terms of PhysicalType
- Rewrite Dwarf type generation in terms of PhysicalType
- Implement every instruction
- Augment bytecode function metadata and signatures
- Add abi mapping step to function type generation
- Classify eightbytes for x86
- Add marshal/re-canonicalize steps in caller/callee code
- Commit a test in test suite
- Introduce an "uninitialized" specifier, similar to
zeroed() - Add 'zeroed()' static value special case for efficiency?
- provide a way to specify if globals are comptime available or just runtime globals? I accidentally wrapped a global defn in #static...
- vm: static execution
- Order-independence for globals used in static code
- Static Buffers (slices)
- vm -> static
- static -> vm
- LLVM gen
- reference to reference cast
- Introduce uword/iword types
- Switch to a single stack
- Move to intrinsic: system core memory functions
- Move to intrinsic: memcpy/memmove
- Allow 'write's from static code
- Move to intrinsic:
exit - Run global initializers before most bodies but after all other phases, treat it like 'body' code SINCE it'll end up using the user's types, and even impls!
- Define clear 'platform layer' (crash, assert, mem, other?). Then we could do an LLVM interp platform and a rust interpreter platform
- All tests passing in #static mode
- Allow upgrading static buffers to fixed-length Arrays (so cool actually)
- Change pointer syntax:
*<ty>,*<ty>,*mut <ty> - Rename 'Buffer' to ... View?
- Convert reference type syntax to prefix for better chaining
- Convert option to prefix syntax
- Add mutable/const bool to ReferenceType
- Update stdlib
Project: Metaprogramming system built on 'static': both string #insert and parsed code #insert, like Jai
- #meta First working version
- Multiline string literals,
- #code directive
- static type universe:
static T - A syntax for talking about a certain impl of an ability:
Show::bool/show(b: bool)or(Allocator for T)/supportsFree()
- Finish StructOfArrays builder
- Provide in-file 'meta' module for convenience (it gets pre-compiled as its own module so that you can write and use functions for metaprogramming)
- Provide a specialized StringBuilder and suite of helpers, CodeBuilder?
- Add fixed length array types:
Array[<type expr> x <int literal>]
- Defer
- Runtime type info story
- typeOf, typeId
- TypeSchema for all types
- Test 'Any' type
- Operator 'overloading' story. I think the story is just abilities. This will actually fix the really poor inference that binary ops currently have
- Start with Equals
- Do add
- Move all the binary operations to intrinsic calls; and remove BinaryOp from the Typed AST
- String pool for string values, not just identifiers (will dedupe in LLVM too)
- Adopt
ecow's EcoVec - reproduce shadow bug: only when statically run?!
buffer.slice:
let end = if end > self.len self.len else end; - Fix referencing match not 'eliminating' patterns on
struct*giving unhandled pattern.CustomHeap({ zalloc }*) -> { - Optimize lambdas that don't capture to become function pointers instead
- comptime #if needs to be a real node not a directive (can't parse if/else). More like
#const ifthan#if - string interp at end puts unnecessary empty string part:
putString(__sb_1001, ""); - Backend codegen cleanup
- avoid uses of aggregate values where we can: so routine uses of 'struct's and 'enum's
- Move allocas to entry block. "Doing this is actually quite easy as LLVM provides functions you can use to retrieve the entry block for a function and insert instructions into it." (handled by optimization passes for now)
- Upgrade to LLVM 18
- Context location params are not being propagated
- Test and fix named arguments
- 'never' needs to work in every expression position (got close enough, might add more if one comes up) -> Originally did the wrong way, plumbing special cases. Instead now we just only generate the crashy expr
- accumulate test errors and support inline test comment assertions when a line should produce a compiler error. - Probably one test for failing compilation and one passing one for each major language area
- Add simple int range in stdlib
- Fix enum codegen, read Inko llvm backend (its inkwell + rust and does ABI compatible stuff https://yorickpeterse.com/articles/the-mess-that-is-handling-structure-arguments-and-returns-in-llvm/)
- Conditional compile directive
- Support boolean operators in compile time expressions
- Change FieldAccess semantics to work on struct references, and copy only the field out This saves copying the entire aggregate first with a Dereference instruction
- ThreadLocal globals
- LLVM Codegen callstack is too deep due to codegen_function_or_get
- Switch VM stack to a single virtual allocation https://crates.io/crates/memmap2
- Improve LLVM opt pipeline https://www.reddit.com/r/Compilers/comments/1hqmd7x/recommended_llvm_passes/ https://llvm.org/docs/NewPassManager.html#just-tell-me-how-to-run-the-default-optimization-pipeline-with-the-new-pass-manager
- Stacktraces on crash (using libunwind and a little C program to call it:
rt/unwind.c) - [-] Write a 'validateTypedModule' procedure. This need is lessened by the VM which in a way typechecks the TAST This is basically an interpreter; what we have now with the vm solves this problem a bit But not entirely because it only checks the code that runs!
- Matching push
- boolean chains w/ binding ifs
- Don't codegen conditions for arms that don't run
- Remove 'statement conditions'
- Prevent shadowing
- Rewrite codegen for match to allow for better control flow
- 'if' guards on regular match
- Move pattern bindings for field access and enum payload back to variables to fully remove duplication (we can do this now that we have a place to put them that's per-arm)
- Look into converting 'matching if' to also compile to a TypedMatch
- Binding
while - Matching on references
- Match to get reference to each struct field, for example, use * for a dereferencing match
- Match to get reference to enum payload
- ASSERT FAILED: true != true at core.k1:23
- Real type inference
- True inference variables, instantiate function types, unification and consistency checks, aka make it work
- Make 'crash' work in no-std
- Make it pretty (de-coupled inference hole from type variable)
- Move enum constructor onto new inference infra
- Move ability resolution onto new inference infra
- Make it fast (Added better 'pool' to prepare for avoiding lots of allocations)
- Fix closure types / get static dispatch for functions taking a closure directly
- Specializing functions on their provided closures to allow inlining and static dispatch
- Run in Linux x86-64
-
requirestatements with matching and binding - Runtime-branching Allocator system (v2 is comptime branching)
- comptime enhancement to support this global initializer:
let* a: Arena* = { .. }; - Global pointers, to enable
- The problem with passing an allocator around is all code becomes generic, or casts a pointer.
- Comptime structs!
- alloca in loops fix
- Parameterize stdlib over the current allocator ()
- comptime enhancement to support this global initializer:
- Proper basic comptime
- Rename to 'static'
- Move to stack-based VM
- literals
- if/else
- Arith
- Struct construction
- Struct field access
- Enum construction
- Memory safety / solving the 'aliasing' problem, not because its unimportant but because I have other interests
- Tuples. I don't think you need them if you have anonymous structs. The lack of names always makes them easy to start using and very hard to maintain / read consumer code. "In the beginning all you want is an anonymous tuple and in the end all you want are named fields"
- Ability derivation (prefer a metaprogram solution)
- Replace 'unit' with an empty struct, encoded as
{}at the type level and{}at the value level. This would remove a whole base type This simplification comes at the cost of making empty struct quite special, so I think it's a sidegrade. Won't do - 'call' method syntax (Scala's 'apply' feature)
- 'join' types to form new enums/structs, statically.
switch {strict|dynamic} ...? If dynamic, I'll build a sum or product based on the branches' types - Might be very cool to have builtin syntax for anything implementing a 'Monad' ability
- (Monad ability would require closures and generic abilities, which we now have. Just need higher order type params
F[_]
- (Monad ability would require closures and generic abilities, which we now have. Just need higher order type params
- Require named fncall args by default; and allow anonymous w/ declaration like Jakt?
- Test handling of NaN and Infinity, other float edge cases
- Try to encode prefix strings, aka German/Umbra strings
- LLVM: avoid loading aggregate values directly
- Convert NamedType to a trait
- Use smallvec
- UTF8
- Test multi-byte characters (emoji, other)
- Intern ParsedBlock and ParsedStatement
- As values, of course.
- A simple stdlib enum?
- A '?' operator for early return? We could do an ability for it!
- Semi-auto, mostly arenas, perhaps 2 global ones, 'perm' and 'tmp' via thread-locals, standard library built around them
- Unmatched closing delim in namespace causes silent failure to parse rest of sources
- Parsing bug where first expr of block is namespaced with ::
- Parsing bug where
if rest.startsWith("one") .Some(1: u64)parses asif rest.startsWith("one").Some(1: u64) - ICE when assigning to struct member when struct is not a reference (self.module.types.get(field_access.base.get_type()).as_reference().is_some())
- Require indirection for recursive types; and make them actually really work
- Lexer cleanup > Am I crazy or is this just always tok_buf.len()?!?!?!
- Binary op inference improvements
- assert(sizeOfText == 16 + 32); rhs should infer to u64
- Precedence of dereference (and i guess unary ops in general) should be higher Kinda fixed by removing all unary ops except 'not'
- Get render loop working with access to module, ability to trigger compile, run
- Render namespaces, use recursion over namespace for all functionality?
- Search for a type?
- One day allow updating or adding a single definition
-
Fixing enums big time
- Rename Optional -> Opt
- Remove tag literals, make enum tags per-enum
- Parse type expr
Opt[T].Some - expr
Opt.None - expr
_root::Opt.None - expr
_root::Opt.None[i32] - expr
Opt.Some(5) - expr
Opt.Some[i32](5) - expr
_root::Opt.Some[i32](5) - deal with fncall syntax collision by checking for the enum first
- kill ParsedTypeExpression::AnonEnumVariant?
- CastType
EnumVariant: checks should not happen in codegen - Convert TypedIf.consequent and TypedIf.alternative to TypedExpr from TypedBlock
-
QoL before rest of pattern matching
- Codegen fail instead of panic
- Rename record to struct
- Start gui
- Remove ! for negation; switch to 'not'
-
Get LLVM interpreter working (for test suite)
-
'crash' w/ line no for option unwrap
-
'crash' w/ line no for bad enum cast
-
Match expected errors from test programs
-
'never' type
- typechecking / codegen
- if/else
- match
-
Pattern Matching
- Single-arm match evals to boolean
- Literals
- Variables
- Optional None
- Optional Some
- Make sure binary AND doesn't evaluate its rhs if lhs is false!
- Records
- Enums
- Multi-case
-
Enum types (enums / tagged unions)
- Syntax
- Construction/repr - [x] Later, pattern match on variants
- Make first-class types for each variant
- First, hard cast to variant (
as<.Tag>) - Built-in .as or panic
- syntax: optional pipe separator
- Enum methods
-
Type cleanup
- Order of eval (topology through defer/skip)
- named struct and enum fix
- Newtypes (opaque)
- Aliases
-
Generic structs and enums
-
Recursive types
-
TypedIf allow exprs instead of requiring blocks (did not do; instead improved handling of unit blocks)
-
Remove binding 'if' since we have pattern matching
-
Abilities
-
Ability impl decl order bugfix (ability impls need to be seen in the decl phase)
-
Type Ascriptions
-
Enforce unique function name in namespace!
-
Optional coalescing binary operator '?'
-
Raw Pointer
- from int64
- to int64
- from reference
- to reference
-
sizeOf
-
Rest of the int sizes
- 8
- 16
- 32
- 64
- reject invalid values
- implement infallible coercions
- implement cast
- hex literals?
- binary literals?
-
Reject too many function args
-
Use abilities to implement Bits namespace
-
return statement (control flow of blocks / statements: CanReturn, AlwaysReturns, NeverReturns (which is 'never')) ^ mixed feelings here as it breaks the fact that 'expr as T' always returns a T...
-
Use
ascasting syntax for rawpointer.asUnsafe -
Allow
ascasting syntax for enums to supply only the tag (result as .Okinstead of result asResult<T,E>.Ok) -
Ability constraints on functions
-
Remove custom size/align code and use LLVM's
-
More type expressions! (this was a big part of my original point)
- Intersect structs
- Union structs
- inner type of optional
- return type of function (fn.return)
- param types of function (fn.arg, fn.arg2)
-
Exhaustive pattern matching
-
Rework RawPointer to be a builtin and support 'multipointer' operations
-
Pipe operator (copy Elixir)
-
Rework builtin array to use new Pointer, Remove all array intrinsics and builtin type
- Add array bounds checking
- Fix array literal syntax
-
Rework builtin string to use new Pointer
-
Rework builtin optionals to be a generic enum
-
floating point (f32 and f64)
-
'Context' system; implicit stack arguments
-
Pass caller source location for assert
-
Function types (functions have types but there's no syntax for describing a function type yet)
-
Optional coalescing field accessor (x?.y)
-
function pointers (By taking the static address of a function as Pointer)
-
Finish/fix simple generic inference
-
Prevent function overloading in same namespace
-
Typecheck the binary ops
-
Bitwise ops
-
Bitwise ops using abilities
-
Closures
- Direct calls, no captures
- Support captures, but explicit only
- Support implicit captures
- Allow 0 args
- Allow specifying return type (without typing the closure itself)
- Return works
-
Change reference and dereference syntax to x.& and x.*
-
Fix context params when combined with generics
-
string interpolation
-
typeOf()
-
Remove coercion from the language (only dereference is left)
-
Rework Opaques
- Just use structs with private fields
-
Working with references push, specifically struct and either references
- A nice syntax for
referenceSet - New assignment operator, not
=(<-) - Make EnumGetPayload a valid lhs?
- Kill .& because so unsound, use let* for those cases
- Not the worst thing to use let* for the c-interop case of needing a pointer, that's what .& was doing anyway
- EnumGetPayload on a reference doesn't give a reference (and shouldn't always, what syntax to specify)
- FieldAccess on a reference doesn't give a reference (and shouldn't always, what syntax to specify)
- A nice syntax for
-
Array/Slice/string rework to Buffer/List, and eventually Array (fixed size at compile time)
-
Do away with k1lib.c?
-
Test Struct shorthand syntax
-
Imports
- types
- functions
- constants
- namespaces
-
Ability call resolution rework
- Remove restriction that first arg is Self by
- Reworking resolution to find a function by name first, then Solve for 'self' by unifying call and signature, then find the impl
-
Fully generic abilities with separate 'input' vs 'output' type parameters
-
Blanket ability implementations
-
nocompile() intrinsic! yields an actual runtime constant string for assertions?!
let result1 = compiler/nocompile(1 + "asdf"); assert(result1.startsWith("Type mismatch")) -
Migrate ? to use an ability
-
Add "or return error" operator based on an ability
-
Convert try to postfix: hello.try (hearkening to .await)
-
! operator should now just call Unwrap.unwrap()
-
Bug: if a blanket impl fails to typecheck, we should not use it. Currently we ice trying to instantiate it
-
Migrate
forloops to use a core Iterator ability -
Namespace stuff
- Imports via
use - Change keyword to
ns -
namespace <ident>;to namespace whole file - Allow namespace extension via simple multiple blocks of same name in same scope
- Imports via
-
Re-write signature specialization to be simpler.
-
return from while
-
break from while
-
Move tests into fewer files
-
Finish hashmap implementation
-
Handle escaped chars in string literals
-
Friendliness pass
- Replace 'enum' keyword with 'either', ensure the ambiguous cases have good errors (inside struct, inside param list)
- 'when' keyword is bad;
switchmaybe orcase, or resolve the ambiguity withwhen <x> is {} - Replace
typewithdeftype- It would be really nice not to take the keyword 'type'. Just a thought from using Rust/Scala
-
Remove tag literals, make enum tags per-enum
-
Ability-based iteration
-
Records
- Syntax decision ({} vs .{} vs Point {})
- Parsing
- Typechecking
- codegen static as LLVM structs
- Accessor syntax
- Accessor ir
- Accessor codegen
-
Identifier cleanup and interning
-
Actual scoping
-
Real core so we can more easily add runtime/stl functions (for array)
-
Heap memory (just using malloc)
-
Arrays (Fixed size but heap allocated)
-
Generic functions (no inference)
-
Remove / fix TokenIter type
-
parens around expressions
-
Binary infix operations
-
basic error reporting using spans
-
Array and Struct member assignment
-
While Loop
-
Assert (syscall exit? 'panic'?)
-
Replace .length on string and array with a MethodCall node (adding empty parens ())
-
Change Array repr to a struct w/ length so we have length
-
Strings
- String literals cant have spaces lol
- print(string)
- hardcoded via codegen
- string.length
- string[i] (int)
- char
- string[i] (char)
- add string.length function
- char.to_string()
-
Concatenate strings (in userland; slow)
-
Infer let types
-
Extern keyword, then
-
Link at build time not via LLVM module
-
Use ctx.const_string for printf format strings
-
Optionals
-
Improve printing output by fixing display functions
-
Implement builtin growable array
-
Use aggregates in codegen, not pointers, for optionals
-
Basic generic type inference in function calls
-
Pretty print scopes
-
Support multiple files
-
Fix namespaces to require full paths (currently busted must be unique names globally)
-
Allow namespaces inside namespaces
-
Some debug info in LLVM IR (source snippets or line numbers?)
-
uint type
-
Generic type inference
-
Type literals would be fun
-
Syntax Shed
- Function syntax change? (foo = fn (a, b, c): int { }
- from qualifier-focused to name -focused
- Parse trailing commas
-
[-] For iteration (we will just hardcode the iterable types)
- 'do' version
- 'yield' version on array
- 'yield' version on string
- Provide index var (it_index)
-
DEBUG info
- on or off
- flag types
- correct line nums
- correct spans (depends on multifile)
- correct file path
- Add lexical scopes for if and while
- Optional types
- None types
- None value
- Runtime repr for boolean
- Runtime repr for unit
- Runtime repr for int
- Runtime repr for char
- Runtime repr for string
- struct
- array of optional structs
- move some-wrapping into a function
- has value from userland (currently only happens by desugaring))
- unwrap from userland (I think this can just be a core function not intrinsic)
- test with optional array elements
- test with optional struct fields
- test with optional function args
- Explicit Some() expression
- I really need a way to write code in Zig or C and use it in my stl to move things along
- Zig binding of my Array
- Early return
- Implicit return of unit if no return statement
- string.indexOf
- charToString implemented in zig and linked
- Array.distinct in zig
- Not equal != operator
- Unary negation operator
- Equality binop
- Precedence of binops; parens?
- Recursion fix: process module decls first, then impls
- Negative integer literals
- PHI nested branch fix
- Fix line comments
- Maybe eventually actually free some memory? lol
- Rename IR to typed-ast, since it's a tree not instruction set. TAST?
- Make intrinsics like arrayIndex a real function in the LLVM IR?
- Fix line numbers to account for core
- Fix unnecessary load of function args
- Proper println implementation. Fine to use printf internally for now but we should define our own func around it
- Implement Display instead of relying on Debug
- Pretty-print AST
- Error Spans
- fancy output COLORS
- Type Params
- Implement typed IR
- Implement FnCall
- Implement Add
- Implement Multiply
- Use basicvalue not pointervalue as main IR type
- Add booleans
- Add boolean AND and OR
- Parse line comments
- implement "expected output" for test sources
- Add spans to AST
- Add spans to IR
- Implement IF expressions