Skip to content

Commit

Permalink
anonymous structs, arrays, and advanced casting
Browse files Browse the repository at this point in the history
- structs literals can be made without a type. e.g.
  .{
    foo = 5,
    bar = "hello",
  }
- the same is true for arrays. e.g.
  .[1, 2, 3]
- casting is more advanced for both structs and arrays. here are some
  casts that were impossible before and are possible now:

  [3]i32 as [3]f64
  struct { a: u8, b: char } as struct { b: u128, a: f32 }

  the struct casting is based on the field names, so in the above
  example first.a -> second.a and first.b -> second.b, regardless of the
  new order or new types
- casting itself has also been optimized and hopefully there are less
  memcpys
- the fuzzer has been updated to support changes to the codebase that
  happened a while ago
- u128 literals now actually work
- few slight bug fixes
  • Loading branch information
NotAFlyingGoose committed Jun 17, 2024
1 parent 6801322 commit a6ceda8
Show file tree
Hide file tree
Showing 22 changed files with 2,128 additions and 408 deletions.
24 changes: 13 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,40 @@ Thank you for investing your time into contributing to Capy! All contributions a

There are four main points:

- The style guide is "don't make the code look ugly." The default rust formatter is plenty enough.
- The style guide is "don't make the code look ugly." The default rust formatter is plenty. Make sure to use 4 spaces.

- Be sure to use `cargo clippy --all-targets`.

- If you're fixing a bug, make sure to include tests for that bug. Most of the crates have good testing capabilities.

- Always review all your changes before you commit.

The last one is especially important. I've found plently of typos / things I've missed by applying it.
The last one is especially important. I've found plenty of typos / things I've missed by applying it.

But the most important part of contributing is understanding how the codebase works.

## The Structure of Capy's Codebase

As you can probably see pretty easily, Capy consists of several crates, each of which is responsible for an important part of the compilation process.
As you can probably see, Capy is a workspace that consists of several crates, each of which is responsible for an important part of the compilation process.

Actual compilation of source code starts in the `lexer` crate, which converts the code into tokens. Those tokens then get sent to the `parser` crate, which transforms them into an syntax tree.
Actual compilation of source code starts in the `lexer` crate, which converts the code into tokens. Those tokens then get sent to the `parser` crate, which transforms them into a syntax tree.

The `parser` crate works in conjuction with the `ast` (Abstract Syntax Tree) crate, which defines functions for working with the syntax tree.

After that, the tree gets processed by the `hir` (High-level Intermediate Representation) crate. Which handles indexing and lowering of global bodies.
After that, the tree gets processed by the `hir` (High-level Intermediate Representation) crate, which handles indexing and lowering of global bodies.

Indexing is essentially just finding out what all the globals are. Global bindings and global functions are discovered here.
Indexing is essentially just finding out what all the globals are. Global bindings are discovered here. This is done in `index.rs`.

Lowering is the process of converting the bodies of globals (which are AST expressions) into HIR expressions. HIR expressions can contain much more relevent information than a syntax tree, and are much easier to work with. Information we don't need is stripped, and the information we *do* need is kept track of neatly. This is in `bodies.rs`.
Lowering is the process of converting the bodies of globals from AST expressions to HIR expressions. HIR expressions contain much more relevant information than a syntax tree and are much easier to work with. Information we don't need is stripped, and the information we *do* need is kept track of neatly. This is done in `bodies.rs`.

All those new HIR expressions need to be typed, which is where the `hir_ty` crate comes in. All the crates before this one could've been ran in parallel, but now we have to combine all our work across all our different .capy files into one. This crate not only gives the indexed globals concrete types, but it also does type checking for all expressions everywhere.
All those new HIR expressions need to be typed, which is where the `hir_ty` crate comes in. All the crates before this one could've been run in parallel, but now we have to combine all our work across all our different .capy files into one. This crate not only gives the indexed globals concrete types, but it also does type checking for all expressions everywhere.

Once we've type checked everything, we can finally begin transforming our HIR into machine code with the `codegen` crate. This crate utilizes cranelift to generate actual machine code.
Once we've type-checked everything, we can finally begin transforming our HIR into machine code with the `codegen` crate. This crate utilizes cranelift to generate actual machine code.

The first thing this crate takes care of is evaluating all `comptime { .. }` blocks by JIT compiling them into functions and then calling those functions. The second thing this crate takes care of is generating the final executable. Under the hood, these two different tasks use the same code for converting HIR into machine instructions.

The central nervous system of the codebase is the `capy` crate, which is essentially a CLI crate. It is kind of complex though if you're trying to wrap your head around how all this fits together. So I'd recommend looking in the `check` function within the `lib.rs` of the `codegen` crate. It contains the most basic code for compiling multiple .capy files.
The central nervous system of the codebase is the `capy` crate, which is essentially a CLI crate. But this crate is kind of complex if you're just trying to learn the basics, so I'd recommend looking at the `check_impl` function within the `lib.rs` of the `codegen` crate. It contains the most basic code for compiling multiple .capy files into a binary.

And that's it! Thank you for spending your time and effort to look into / contribute to this project! It is well appreciated :)
There are a few more helper crates but these aren't too important.

And that's it! Thank you for spending your time and effort to look into and/or contribute to this project! It is well appreciated :)
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ This allows you to run *any* code at compile-time, returning whatever data you w
math :: mod "core".math;

powers_of_two := comptime {
array := i32.[0, 0, 0];
// array with default value (all zeros)
array : [3]i32;

array[0] = math.pow(2, 1);
array[1] = math.pow(2, 2);
Expand Down Expand Up @@ -422,7 +423,7 @@ If you find any bugs in the compiler, please be sure to [make an issue](https://
Big shout out to [Luna Razzaghipour](https://github.com/lunacookies), the structure of this entire codebase is largely based on [gingerbread](https://github.com/gingerbread-lang/gingerbread) and [eldiro](https://github.com/lunacookies/eldiro).
Her help in teaching how programming languages really work is immeasurable and I'm very thankful.

Big shout out to [lenawanel](https://github.com/lenawanel), she's been an enormous help in finding bugs and testing the limits of the language. Due to her help we've managed to expand the language to new heights.
Big shout out to [lenawanel](https://github.com/lenawanel), she's been an enormous help in finding bugs and testing the limits of the language. Due to her help the language has really expanded to new heights.

Big shout out to [cranelift](https://cranelift.dev/). Trying to get LLVM on windows was just way too much effort for me and cranelift made all my dreams come true.

Expand Down
23 changes: 6 additions & 17 deletions core/src/mod.capy
Original file line number Diff line number Diff line change
Expand Up @@ -279,23 +279,6 @@ print :: (val: Any) {

// prints a type id as a human readable string
print_type_info :: (ty: type) {
_print_str :: (text: str) {
text := text as ^char;

i := 0;
loop {
ch := ptr.read(text, i) as char;
if ch == '\0' {
break;
}

libc.putchar(ch);

i = i + 1;
}
};


if meta.is_int(ty) {
info := meta.get_int_info(ty);

Expand Down Expand Up @@ -613,3 +596,9 @@ cast_into :: (value: Any, expected: type, into: ^mut any) -> Any {
ty = expected,
}
}

// todo: make this a comptime parameter
type_of :: (val: Any) -> type {
val.ty
}

52 changes: 51 additions & 1 deletion crates/ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2195,7 +2195,7 @@ mod tests {
}

#[test]
fn struct_literal_get_fields() {
fn struct_literal() {
let (tree, root) = parse(r#"Some_Record_Type.{ foo = 123, bar = "hello" };"#);
let statement = root.stmts(&tree).next().unwrap();
let expr = match statement {
Expand All @@ -2208,6 +2208,11 @@ mod tests {
_ => unreachable!(),
};

assert_eq!(
struct_lit.ty(&tree).unwrap().text(&tree),
"Some_Record_Type"
);

let mut fields = struct_lit.members(&tree);

let field = fields.next();
Expand All @@ -2229,6 +2234,51 @@ mod tests {
assert!(fields.next().is_none());
}

#[test]
fn anonymous_struct_literal() {
let (tree, root) = parse(r#".{ a = true, b = 0.0, c = 'z' };"#);
let statement = root.stmts(&tree).next().unwrap();
let expr = match statement {
Stmt::Expr(expr_stmt) => expr_stmt.expr(&tree),
_ => unreachable!(),
};

let struct_lit = match expr {
Some(Expr::StructLiteral(struct_lit)) => struct_lit,
_ => unreachable!(),
};

assert!(struct_lit.ty(&tree).is_none());

let mut fields = struct_lit.members(&tree);

let field = fields.next();
assert!(field.is_some());
assert_eq!(field.unwrap().name(&tree).unwrap().text(&tree), "a");
assert!(matches!(
field.unwrap().value(&tree),
Some(Expr::BoolLiteral(_))
));

let field = fields.next();
assert!(field.is_some());
assert_eq!(field.unwrap().name(&tree).unwrap().text(&tree), "b");
assert!(matches!(
field.unwrap().value(&tree),
Some(Expr::FloatLiteral(_))
));

let field = fields.next();
assert!(field.is_some());
assert_eq!(field.unwrap().name(&tree).unwrap().text(&tree), "c");
assert!(matches!(
field.unwrap().value(&tree),
Some(Expr::CharLiteral(_))
));

assert!(fields.next().is_none());
}

#[test]
fn import_get_file() {
let (tree, root) = parse(r#"foo :: import "foo.capy";"#);
Expand Down
1 change: 1 addition & 0 deletions crates/codegen/src/compiler/comptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ pub fn eval_comptime_blocks<'a>(
compiler_defined_functions: FxHashMap::default(),
data: FxHashMap::default(),
str_id_gen: UIDGenerator::default(),
i128_id_gen: UIDGenerator::default(),
comptime_results: results,
comptime_data: FxHashMap::default(),
ptr_ty: match target_pointer_bit_width {
Expand Down
Loading

0 comments on commit a6ceda8

Please sign in to comment.