{"id":13430317,"url":"https://github.com/rsms/compis","last_synced_at":"2025-04-10T05:10:52.059Z","repository":{"id":64181916,"uuid":"562305219","full_name":"rsms/compis","owner":"rsms","description":"Contemporary systems programming language in the spirit of C","archived":false,"fork":false,"pushed_at":"2024-08-17T15:14:01.000Z","size":11264,"stargazers_count":217,"open_issues_count":0,"forks_count":8,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-02T18:09:00.683Z","etag":null,"topics":["programming-language"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rsms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-05T23:14:34.000Z","updated_at":"2025-04-01T14:04:33.000Z","dependencies_parsed_at":"2025-01-18T02:23:20.375Z","dependency_job_id":null,"html_url":"https://github.com/rsms/compis","commit_stats":{"total_commits":618,"total_committers":1,"mean_commits":618.0,"dds":0.0,"last_synced_commit":"6e9921abfd5eb2836e54689de0a0386a57689d64"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fcompis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fcompis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fcompis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fcompis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rsms","download_url":"https://codeload.github.com/rsms/compis/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161274,"owners_count":21057555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["programming-language"],"created_at":"2024-07-31T02:00:52.136Z","updated_at":"2025-04-10T05:10:47.371Z","avatar_url":"https://github.com/rsms.png","language":"C","readme":"Compis is a contemporary systems programming language in the spirit of C.\n\nThe compiler supports writing programs in mixed Compis and C;\nyou can mix .c and .co source files. It accomplishes this by bundling some LLVM tools, like clang and lld into one executable. The LLVM dependency might go away someday.\n\n\"Compis\" is a variation on the Swedish word \"kompis\", meaning \"buddy\" or \"comrade.\" It's also [a nod to one of the first computers I used as a kid.](https://en.wikipedia.org/wiki/Compis)\n\n\u003e **Note:** Compis is under development\n\nUsage:\n\n```shell\n$ compis build -o foo main.co\n$ ./foo\n```\n\nSince Compis bundles clang \u0026 lld, you can use it as a C and C++ compiler\nwith cross-compilation capabilities:\n\n```shell\n$ compis cc hello.c -o hello\n$ ./hello\nHello world\n$ compis cc --target=aarch64-macos hello.c -o hello-mac\n$ file hello-mac\nhello-mac: Mach-O 64-bit executable arm64\n$ compis c++ --target=wasm32-wasi hello.cc -o hello.wasm\n$ wasmtime hello.wasm\nHello world\n```\n\n\n## Features\n\n- Memory safety via ownership `let x *Thing = thing // move, not copy`\n- Simple: primitive values, arrays, structs and functions.\n- Convention over configuration: Sensible defaults. One way to do something.\n- Compiler is a statically-linked, single executable.\n- Hackable pragmatic compiler (parse → type-check → static analysis → codegen → link)\n\n\n\n# Tour of the language\n\n\n## Clarity\n\nCompis attempts to make local reasoning about code as easy as possible.\nThere are no implicit type conversion, no side effects from language constructs\nand the lifetime constraints of a value is explicitly encoded in its type.\n\nHere's a parts list:\n\n- booleans `bool` (`true, false`)\n- machine-sized integers `int, uint` (`-123, 0xbeef, 0b1111011`)\n- size-specific integers `i8, u8, i16, u16, i32, u32, i64, u64`\n- size-specific floating-point numbers `f32, f64` (`1.2`, `1.e39`)\n- fixed-size arrays `[i32 3]` (`let a = [1, 2, 3]`)\n- runtime-sized arrays `[i32]` (`let a [i32] = [1, 2, 3]`)\n- unicode character literals `'A' == 0x0041, '↔' == 0x2194, '🆎' == 0x1F18E` (`u32`)\n- ASCII byte-sized character literals `u8('A')`\n- structure types `struct MyThing { field1, field2 type; field3 type; }`\n- functions `fun name(x, y int, z str) int` (`let s = name(1, 2, \"three\")`)\n- anonymous functions `fun (x, y t1, z t2) result`\n- type functions `fun MyThing.foo(this, x, y int) int` (`var x MyThing; x.foo(1,2)`)\n- \"optional\" types `var x MyThing?; if x { typeof(x) == MyThing }`\n- variables `var x, y, z int; var a = 123; a = 456`\n- read-only bindings `let a = [1, 2, 3]` (values are mutable `a[1] = 22`)\n\n\n## Memory safety\n\nCompis manages the lifetime of values which are considered \"owners.\"\nIn other words, memory allocation and deallocation is automatically managed.\nThere's an escape hatch (\"unsafe\" blocks \u0026 functions; unimplemented) for when you need precise\ncontrol.\n\nA type is either considered \"copyable\" or \"owning\":\n\n- Copyable types can be trivially copied without side effects.\n  For example, all primitive types, like `int` and `bool`, are copyable.\n  The lifetime of a copyable value is simply the lifetime of its owning variable,\n  parameter or expression.\n\n- Owning types have managed, linear lifetimes. They can only be\n  copied—as in diverge, or \"fork\" if you will—by explicit user code.\n  When a value of owning type ceases to exist it is \"dropped\";\n  any `drop()` function defined for the type is called and if the type\n  is stored in heap memory, it is freed.\n\nAssigning a copyable value to variable (or using it as an rvalue expression in any situation, like passing it as an argument to a call) creates a distinct \"deep\" copy:\n\n    type Vec2 { x, y int }\n    type Line { start, end Vec2 }\n    var Line a\n    var b = a  // 'a' is copied\n    b.start.x = 2\n    assert(a.start.x == 0)\n\nAssigning an owning value to a variable _moves_ the value; its previous owner becomes inaccessible and any attempt to use the old owner\ncauses a compile-time error.\n\n    type Vec2 { x, y int }\n    type Line { start, end Vec2 }\n    // implementing \"drop\" for Vec2 makes is an \"owning\" type\n    fun Vec2.drop(mut this) {}\n    var Line a\n    var b = a // 'a's value moves to 'b'\n    b.start.x = 2\n    a.start.x // error: 'a' has moved\n\n\n### References\n\nReferences are used for \"lending\" a value somewhere, without a change in storage or ownership.\nReference types are defined with a leading ampersand `\u0026T`\nand created with the `\u0026` prefix operation: `\u0026expr`.\n\n    type Vec2 { x, y int }\n    fun rightmost(a, b \u0026Vec2) int {\n      // 'a' and 'b' are read-only references here\n      if a.x \u003e= b.x { a.x } else { b.x }\n    }\n    fun main() {\n      var a = Vec2(1, 1)\n      var b = Vec2(2, 2)\n      rightmost(\u0026a, \u0026b) // =\u003e 2\n    }\n\nMutable reference types are denoted with the keyword \"mut\": `mut\u0026T`.\nMutable references are useful when you want to allow a function to modify a value without copying the value or transferring its ownership back and forth.\n\n    type Vec2 { x, y int }\n    fun translate_x(v mut\u0026Vec2, int delta) {\n      v.x += delta\n    }\n    fun main() {\n      var a = Vec2(1, 1)\n      translate_x(\u0026a, 2)  // lends a mutable reference to callee\n      assert(a.x == 3)\n    }\n\nCompis does _not_ enforce exclusive mutable borrowing, like for example Rust does.\nThis makes Compis a little more forgiving and flexible at the expense of aliasing;\nit is possible to have multiple pointers to a value which may change at any time:\n\n    type Vec2 { x, y int }\n    type Line { start, end mut\u0026Vec2 }\n    fun main() {\n      var a = Vec2(1, 1)\n      var line = Line(\u0026a, \u0026a)\n      a.x = 2\n      assert(line.start.x == 2)\n      assert(line.end.x == 2)   // same value\n    }\n\n\n_This may change_ and Compis may introduce \"borrow checking\" or some version of it,\nthat enforces that no aliasing can occur when dealing with references. Mutable Value Semantics is another interesting idea on this topic.\n\nReferences are semantically more similar to values than pointers: a reference used as an rvalue does not need to be \"dereferenced\" (but pointers do.)\n\n    fun add(x int, y \u0026int) int {\n      let result = x + y  // no need to deref '*y' here\n      assert(result == 2)\n      return result\n    }\n\nThe only situations where a reference needs to be \"dereferenced\" is when replacing a mutable reference to a copyable value with a new value:\n\n    var a = 1\n    var b mut\u0026int = \u0026a\n    *b = 2   // must explicitly deref mutable refs in assignment\n    assert(a == 2)\n\n\n### Pointers\n\nA pointer is an address to a value stored in long-term memory, \"on the heap\".\nIt's written as `*T`.\nPointers are \"owned\" types and have the same semantics as regular \"owned\" values, meaning their lifetime is managed by the compiler.\nPointers can never be \"null\". A pointer value which may or may not hold a value is made [optional](#optional), i.e. `?*T`.\n\n\u003e Planned feature: unmanaged \"raw\" pointer `rawptr T` which can only be created\n\u003e or dereferenced inside \"unsafe\" blocks.\n\n```\ntype Vec2 { x, y int }\nfun translate_x(v mut\u0026Vec2, int delta) {\n  v.x += delta\n}\nfun example(v *Vec2) {\n  v.x = 1            // pointer types are mutable\n  translate_x(\u0026a, 2) // lends a mutable reference to callee\n  assert(v.x == 1)\n  // v's memory is freed here\n}\n```\n\n\n### Ownership\n\nIn this example, `Thing` is considered an \"owning\" type since it has a \"drop\" function defined. Compis will, at compile time, make sure that there's exactly one owner of a \"Thing\" (that it is not copied.)\n\n```\ntype Thing {\n  x i32\n}\nfun Thing.drop(mut this) {\n  print(\"Thing dropped\")\n}\nfun example(thing Thing) i32 {\n  return thing.x\n} // \"Thing dropped\"\n```\n\nWhen the scope of an owning value ends that value is \"dropped\":\n\n1. If the type is optional and empty, do nothing, else\n2. If there's a \"drop\" type function defined for the type of value, that function is called to do any custom cleanup like closing a file descriptor.\n3. If the value has subvalues that are owners, like a struct field, those are dropped.\n4. If the value is heap-allocated, its memory is freed.\n\n\n### Optional\n\nCompis has _optional types_ `?T` rather than nullable types.\n\n```co\nfun example(x ?i32)\n  // let y i32 = x  // error\n  if x\n    // type of x is \"i32\" here, not \"?i32\"\n    let y i32 = x // ok\n```\n\nThe compiler is clever enough to know when an optional value is\ndefinitely available or not, based on prior tests:\n\n```co\nfun example(name ?str)\n  if name \u0026\u0026 name.len() \u003e 0\n    print(name)\n\nfun example2(a, b ?int) int\n  if a \u0026\u0026 b { a * b } else 0\nfun example3(a, b ?int) int\n  if !(a \u0026\u0026 b) 0 else { a * b }\nfun example4(a, b ?int) int\n  if a \u0026\u0026 b { a * b } else\n    a * 2  // error: a may be valid, but must be checked first\nfun example5(a ?int) int\n  if !a { a * 2 } // error: a is definitely empty\n```\n\nYou can use variable definitions with `if` expressions, which is useful for taking some action on the result of a function call, without spamming your scope with temporary variables:\n\n```co\nfun try_read_file(path str) ?str\n\nif let contents = try_read_file(\"/etc/passwd\")\n  // in here, the type of contents is str, not ?str\n  print(contents)\n```\n\nIn the above example, `contents` is only defined inside the \"then\" branch.\nAccessing `contents` in an \"else\" branch or outside the \"then\" branch either\nleads to an \"undefined identifier\" error or referencing some other variable in\nthe outer scope.\n\n\n### No uninitialized memory\n\nMemory in Compis is always initialized. When no initializer or initial value is provided, memory is zeroed. Therefore, all types in Compis are valid when their memory is all zero.\n\n```co\ntype Vec3 { x, y, z f32 }\nvar v Vec3 // initialized to {0.0, 0.0, 0.0}\n```\n\n\n## Variables\n\nCompis has variables `var` and one-time bindings `let`. `let` bindings are not variable, they can not be reassigned once defined to. The type can be omitted if a value is provided (the type is inferred from the value.) The value can be omitted for `var` if type is defined.\n\n```co\nvar b i32 = 1 // a 32-bit signed integer\nvar a = 1     // type inferred as \"int\"\na = 2         // update value of a to 2\nvar c u8      // zero initialized\nlet d i64 = 1 // a 64-bit signed integer\nlet e = 1     // type inferred as \"int\"\ne = 2         // error: cannot assign to binding\nlet f i8      // error: missing value\n```\n\n\n\u003e FUTURE: introduce a `const` type for immutable compile-time constants\n\n\u003e FUTURE: support deferred binding, e.g. `let x i8; x = 8`\n\n\n\n## Functions\n\nAmazingly, Compis has functions!\nHere are a few examples:\n\n```co\nfun mul_or_div(x, y int, mul bool) int\n  if mul x * y\n  else   x / y\n\nfun no_args() int { return 42 }\nfun no_args_implicit_return() int { 42 }\nfun no_result(message str) { print(message) }\nfun inferred_result(x, y f32) = x * y\nlet f = fun(x, y int) = x * y\n```\n\n\n### Type functions\n\nCompis doesn't have classes, but it has something called \"type functions\" that allows\nyou to clearly associate functions with data:\n\n```co\ntype Account\n  id   uint\n  name str\n\nfun Account.isRoot(this) bool\n  this.id == 0\n\nfun Account.setName(mut this, newname str) str\n  let prevname = .name\n  .name = newname\n  prevname\n\nfun example()\n  let account Account\n  account.setName(\"anne\")\n```\n\n\n### Using C ABI functions\n\nTo access a C ABI function you need to declare it in the compis source file\nthat accesses it using `pub \"C\" fun ...`:\n\n`example/a.c`:\n\n```c\nlong mul_or_div(long x, long y, _Bool mul) {\n  return mul ? x*y : x/y;\n}\n```\n\n`example/b.co`:\n\n```co\npub \"C\" fun mul_or_div(x, y int, mul bool) int\nfun example() int\n  mul_or_div(1, 2, true)\n```\n\n\n\n\n## Arrays\n\nAn array in Compis is a representation of a region of linear memory,\ninterpreted as zero or more values of a uniform type.\nArrays have ownership of their values.\n\nThere are two kinds of arrays with very similar syntax:\n\n- Fixed-size arrays `[type size]` (e.g. `[int 3]` for three ints)\n- Dynamic arrays `[type]` which can grow at runtime\n\n```co\nvar three_ints [int 3]\nvar some_bytes [u8]\nthree_ints[3] // compile error: out of bounds\nsome_bytes[3] // runtime panic: out of bounds\n```\n\nFixed-size arrays are concretely represented simply by a pointer to the\nunderlying memory while dynamic arrays are represented by a triple\n`(capacity, length, data_pointer)`.\n\n\n\n## Slices\n\nSlices are references to arrays. Like regular references, they can be mutable.\n\n```co\nvar three_ints [int 3]\nvar a \u0026[int]    = \u0026three_ints\nvar b mut\u0026[int] = \u0026three_ints\n```\n\nSlices are concretely represented by a tuple `(length, data_pointer)`.\n\nNote that you can make references to arrays while retaining compile-time size\ninformation:\n\n\n\n\n### Slices vs array references\n\nReferences to fixed-size arrays are concretely represented by a pointer to the array's\nunderlying memory, rather than a slice construct:\n\n```co\nvar four_bytes [u8 4]      // fixed-size array\nvar some_bytes [u8]        // dynamically-sized array\nvar a = \u0026four_bytes        // mut\u0026[u8 3] -- reference to array\nvar b = \u0026some_bytes        // mut\u0026[u8] -- slice of array\nvar c \u0026[u8] = \u0026four_bytes  // slice of array, from explicit type\n```\n\nWhen you don't specify the type, as in the case for `a` and `b` above,\nCompis will chose the highest-fidelity type. For example, referencing a fixed-size array\nyields a mutable array reference with the length encoded in the type.\nThis leads the most efficient machine code (just a pointer.)\nHowever, you can specify a more narrow type or a compatible type of lower fidelity, as\nin the case with `c` above.\n\nTo understand the practical impact, consider the two following functions,\none which takes a reference to an array and one which takes a slice:\n\n```co\nfun decode_u32le_array_ref(bytes \u0026[u8 4]) u32\n  u32(bytes[0]) |\n  u32(bytes[1]) \u003c\u003c 8 |\n  u32(bytes[2]) \u003c\u003c 16 |\n  u32(bytes[3]) \u003c\u003c 24\n\nfun decode_u32le_slice(bytes \u0026[u8]) u32\n  u32(bytes[0]) |\n  u32(bytes[1]) \u003c\u003c 8 |\n  u32(bytes[2]) \u003c\u003c 16 |\n  u32(bytes[3]) \u003c\u003c 24\n```\n\nThe equivalent generated C code looks like this:\n\n```c\nu32 decode_u32le_array_ref(const u8* bytes) {\n  return (u32)bytes[0] |\n         (u32)bytes[1] \u003c\u003c 8u |\n         (u32)bytes[2] \u003c\u003c 16u |\n         (u32)bytes[3] \u003c\u003c 24u;\n}\n\nu32 decode_u32le_slice(struct { uint len; const u8* ptr; } bytes) {\n  if (bytes.len \u003c= 3) __co_panic_out_of_bounds(); // bounds check\n  return (u32)bytes.ptr[0] |\n         (u32)bytes.ptr[1] \u003c\u003c 8u |\n         (u32)bytes.ptr[2] \u003c\u003c 16u |\n         (u32)bytes.ptr[3] \u003c\u003c 24u;\n}\n```\n\nNotice how for the \"slice\" version, two machine words are passed as arguments\n(length and address) instead of just one (address) as is the case with the\n\"array_ref\" version. Additionally, a bounds check is performed at runtime in the\n\"slice\" version.\n\n\u003e Note: Currently the actual generated code is a little less elegant for the \"slice\" version as there are bounds checks for every access, not just one for the largest offset. In the future the Compis code generator will be more clever and perform a minimum amount of runtime checks.\n\n\n\n## Structures\n\nStructures are used for organizing data.\nA structure have named fields and can be nested.\nHere's an example of declaring a named structure type:\n\n```co\ntype Thing\n  x, y f32\n  size uint\n  metadata          // anonymous struct\n    debugname str\n    is_hidden bool\n```\n\nThey can be composed, and the order they are defined in does not matter:\n\n```co\ntype Thing\n  x, y f32\n  size uint\n  metadata Metadata\n\ntype OtherThing\n  name     str\n  metadata Metadata\n\ntype Metadata\n  debugname str\n  is_hidden bool\n```\n\n\n\n## Templates\n\nTemplate types (generic types) are useful when describing shapes of data with\nincomplete details.\nThey are also useful for reusing the same structures for many other types.\nHere's an example where the same type `Vector` is used to implement two functions\nthat accept different types of primitive values (`int` and `f32`):\n\n```co\ntype Vector\u003cT\u003e\n  x, y T\n\nfun example1(point Vector\u003cint\u003e) int\n  point.x * point.y\n\nfun example2(point Vector\u003cf32\u003e) f32\n  point.x * point.y\n```\n\nTemplate parameters can define default values:\n\n```co\ntype Foo\u003cT,Offs=uint\u003e\n  value T\n  offs  Offs\n\nvar a Foo\u003cint\u003e  // is really Foo\u003cint,uint\u003e\n```\n\nTemplates can be partially expanded:\n\n```co\ntype Foo\u003cT,Offs=uint\u003e\n  value T\n  offs  Offs\n\ntype Bar\u003cT\u003e\n  small_foo Foo\u003cT,u8\u003e  // Foo partially expanded\n\nvar a Bar\u003cint\u003e  // Bar and Foo fully expanded\n```\n\n\nCurrently only types can be templates, not yet functions, but I'll add that.\nIt is also not yet possible to define type functions for template types\n(i.e. `fun Foo\u003cT\u003e.bar(this)` does not compile.)\n\n\n\n## Packages\n\nA package in Compis is a directory of files and identified by its file path.\nDeclarations in any file is visible in other files of the same package. For example:\n\n```\n$ cat foo/a.co\nfun a(x int) int\n  x * 2\n\n$ cat foo/b.co\nfun b(x int) int\n  a(x) // the \"a\" function is visible here\n\n$ compis build ./foo\n[foo 6/6] build/opt-x86_64-linux/bin/foo\n```\n\nThe only exception to this rule is imported symbols,\nwhich are only visible within the same source file:\n\n```\n$ cat foo/a.co\nimport \"std/runtime\" { print }\n// here, \"print\" is only available in this source file\nfun say_hello()\n  print(\"Hello\")\n\n$ cat foo/b.co\npub fun main()\n  say_hello()    // ok; say_hello is available\n  print(\"Hello\") // error; print is not available here\n\n$ compis build ./foo\nfoo/b.co:3:3: error: unknown identifier \"print\"\n3 → │   print(\"Hello\") // error; print is not available here\n    │   ~~~~~\n```\n\nAs a convenience, Compis can build \"ad-hoc packages\" out of arbitrary source files:\n\n```shell\n$ compis build somedir/myprog.co\n[somedir/myprog 4/4] build/opt-x86_64-macos.10/bin/myprog\n```\n\n\n### Importing packages\n\n```co\nimport \"foo/bar\"            // package available as \"bar\"\nimport \"foo/bar\" as lol     // package available as \"lol\"\nimport \"./mysubpackage\"     // relative to source file's directory\nimport \"cat\" { x, y }       // import only members x and y\nimport \"cat\" { x, y as y2 } // change name of some members\nimport \"cat\" as c { x, y }  // import both package and some members\nimport \"html\" { * }         // import all members\nimport \"html\" { *, a as A } // import all members, change name of some\n```\n\nList of members is a block which contains lists (comma separated names),\nwhich allows us to write it in different ways depending on the amount of members\nwe are importing. This can help readability and improved version control diffs.\nThe following three are equivalent:\n\n```co\nimport \"fruit\" { apple, banana, citrus, peach as not_a_plum }\n\nimport \"fruit\"\n  apple\n  banana\n  citrus\n  peach as not_a_plum\n\nimport \"fruit\" { apple,\n  banana, citrus,\n  peach as not_a_plum\n}\n```\n\nSimilarly—as a convenience—imports themselves can be grouped in a block:\n\n```co\nimport\n  \"foo/bar\"\n  \"foo/bar\" as lol\n  \"./mysubpackage\"\n  \"cat\" { x, y as y2 }\n  \"html\" { * }\n  \"fruit\"\n    apple\n    banana\n    citrus\n    peach as not_a_plum\n\n// equivalent to:\nimport \"foo/bar\"\nimport \"foo/bar\" as lol\nimport \"./mysubpackage\"\nimport \"cat\" { x, y as y2 }\nimport \"html\" { * }\nimport \"fruit\" { apple, banana, citrus, peach as not_a_plum }\n```\n\nWhen a package's namespace is imported and no explicit name is given with `as name`,\nthe package's identifier is inferred from its last path component:\n\n```co\nimport \"foo/abc\"   // imported as identifier \"abc\"\nimport \"foo/a b c\" // imported as identifier \"c\"\nimport \"foo/a-b-c\" // imported as identifier \"c\"\n```\n\nIf the last path component is not a valid identifier `as name` is required:\n\n```co\nimport \"foo/abc!\"        // error: cannot infer package identifier\nimport \"foo/abc!\" as abc // imported as identifier \"abc\"\n```\n\nTo import a package only for its side effects, without introducing any identifiers\ninto the source file scope, name it `_`:\n\n```co\n\"test/check-that-random-works\" as _\n```\n\nExamples of invalid import paths:\n\n```co\nimport \"\"   // error: invalid import path (empty path)\nimport \"/\"  // error: invalid import path (absolute path)\nimport \".\"  // error: invalid import path (cannot import itself)\nimport \" \"  // error: invalid import path (leading whitespace)\nimport \"a:\" // error: invalid import path (invalid character)\n```\n\nImports syntax specification:\n\n```abnf\nimportstmt  = \"import\" (importgroup | importspec) \";\"\nimportgroup = \"{\" (importspec \";\")+ \"}\"\nimportspec  = posixpath (\"as\" id)? membergroup?\nmembergroup = \"{\" (memberlist \";\")+ \"}\"\nmemberlist  = member (\",\" member)*\nmember      = \"*\" | id (\"as\" id)?\n```\n\n\n## Primitive types\n\nCompis has the following primitive types:\n\n```\nTYPE   VALUES\n\nvoid   nothing (can only be used as function-result type)\nbool   false or true\ni8     -128 … 127\ni16    -32768 … 32767\ni32    -2147483648 … 2147483647\ni64    -9223372036854775808 … 9223372036854775807\nu8     0 … 255\nu16    0 … 65535\nu32    0 … 4294967295\nu64    0 … 18446744073709551615\nint    Target-specific address size (i.e. i8, i16, i32 or i64)\nuint   Target-specific address size (i.e. u8, u16, u32 or u64)\nf32    -16777216 … 16777216 (integer precision)\nf64    −9007199254740992 … 9007199254740992 (-2e53 … 2e53; integer precision)\n```\n\nIntegers are represented as [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement).\nFloating-point numbers are represented as [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).\nOverflow of signed integers causes a runtime panic.\nOverflow of unsigned integers wrap.\n\n\n\n## Integer literals\n\n```\nfun inferred_int_types()\n  let _ = 1_23_4      // int (32-bit target)  int (64-bit target)\n  let _ = -1          // int (32-bit target)  int (64-bit target)\n  let _ = 2147483647  // int (32-bit target)  int (64-bit target)\n  let _ = -2147483648 // int (32-bit target)  int (64-bit target)\n  let _ = 4294967295  // uint (32-bit target) int (64-bit target)\n  let _ = -2147483649 // i64 (32-bit target)  int (64-bit target)\n  let _ = 4294967296  // u64 (32-bit target)  uint (64-bit target)\n\nfun integer_limits()\n  let _ i8 = -0x80\n  let _ u8 = 255\n  let _ i16 = -32768\n  let _ u16 = 65535\n  let _ i32 = -2147483648\n  let _ u32 = 4_294_967_295\n  let _ i64 = -9223372036854775808\n  let _ u64 = 0xffff_ffff_ffff_ffff\n```\n\n\n\n### Ideally typed integer constants\n\nRather than literals being of a fixed type, Compis features \"ideally typed\" integer constants; the actual type is inferred from context and its value is verified to be within range:\n\n```\nfun foo(v i32) void\nfun bar(v u16) void\nfun cat(v i8) void\nfun example()\n  foo(200) // materialized as i32\n  bar(200) // materialized as u16\n  //cat(200) // error: integer constant overflows i8\n```\n\n\n\n## Character literals\n\nThere's no special type for representing text runes in Compis.\nInstead you deal with either `u32` for Unicode codepoints,\nor `u8` for UTF-8 encoded sequences.\n\nHowever Compis has syntax for character literals.\nCharacter literals must be valid Unicode codepoints.\n\n```\nfun example()\n  let _ = 'a'      // 0x61\n  let _ = '\\n'     // 0x0A\n  let _ = '©'      // 0xA9 (encoded as UTF-8 C2 A9)\n  let _ = '\\u00A9' // 0xA9\n  //let _ = '\\xA9' // error: invalid UTF-8 sequence\n  //let _ = ''     // error: empty character literal\n```\n\nIf you feel the need to specify character values which are not Unicode codepoints,\nuse regular integers:\n\n```\nfun example()\n  let _ = 0xA9\n  let _ = 0xFF\n  let _ = 0xFFFFFFFF\n```\n\n\n\n## Strings\n\nStrings represent UTF-8 text. The type `str` is an alias of `\u0026[u8]` (slice of bytes.)\nString literals are high-fidelity types, embedding the size in the type (e.g. `\u0026[u8 5]`).\n\n```\nfun foo(s str)\nfun bar(s \u0026[u8])\nfun cat(s \u0026[u8 5])\nfun mak(s \u0026[u8 6])\nfun example()\n  let s = \"hello\" // \u0026[u8 5]\n  foo(s)\n  bar(s)\n  cat(s)\n  mak(s) // error: passing value of type \u0026[u8 5] to parameter of type \u0026[u8 6]\n```\n\n\n\n# Building\n\nBuild an optimized product:\n\n    ./build.sh\n\nBuild a debug product:\n\n    ./build.sh -debug\n\nRun co:\n\n    out/debug/co build -o out/hello examples/hello.c examples/foo.co\n    out/hello\n\nBuild \u0026 run debug product in continuous mode:\n\n    ./build.sh -debug -wf=examples/foo.co \\\n      -run='out/debug/co build examples/hello.c examples/foo.co \u0026\u0026 build/debug/main'\n\n","funding_links":[],"categories":["C","Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsms%2Fcompis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frsms%2Fcompis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsms%2Fcompis/lists"}