{"id":13727431,"url":"https://github.com/lys-lang/lys","last_synced_at":"2025-05-07T22:31:18.432Z","repository":{"id":34307195,"uuid":"130402516","full_name":"lys-lang/lys","owner":"lys-lang","description":"⚜︎ A language that compiles to WebAssembly","archived":false,"fork":false,"pushed_at":"2025-01-24T14:47:57.000Z","size":5206,"stargazers_count":423,"open_issues_count":6,"forks_count":9,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-28T18:02:16.900Z","etag":null,"topics":["language","lys","wasm"],"latest_commit_sha":null,"homepage":"https://lys-lang.dev","language":"TypeScript","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/lys-lang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2018-04-20T18:55:38.000Z","updated_at":"2025-03-05T19:26:02.000Z","dependencies_parsed_at":"2024-01-10T20:10:48.818Z","dependency_job_id":"e2f36e7b-72b1-4a06-9a45-f1365f56c4f0","html_url":"https://github.com/lys-lang/lys","commit_stats":{"total_commits":117,"total_committers":7,"mean_commits":"16.714285714285715","dds":0.5470085470085471,"last_synced_commit":"7813a323db5b7d79e8f9dc74f5dc340886531f2d"},"previous_names":["menduz/aureum"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lys-lang%2Flys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lys-lang%2Flys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lys-lang%2Flys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lys-lang%2Flys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lys-lang","download_url":"https://codeload.github.com/lys-lang/lys/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252965449,"owners_count":21832891,"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":["language","lys","wasm"],"created_at":"2024-08-03T01:03:55.748Z","updated_at":"2025-05-07T22:31:17.303Z","avatar_url":"https://github.com/lys-lang.png","language":"TypeScript","funding_links":[],"categories":["Languages","TypeScript","Other"],"sub_categories":["Other"],"readme":"\u003cp align=\"center\"\u003e\u003cbr\u003e\u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/260114/54724904-c7e7d300-4b4b-11e9-8bbd-ec3f9044c86e.png\" width=\"200\" /\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003c/p\u003e\n\n[Lys](https://github.com/lys-lang/lys), a language that compiles to WebAssembly.\n\n[![justforfunnoreally.dev badge](https://img.shields.io/badge/justforfunnoreally-dev-9ff)](https://justforfunnoreally.dev)\n\nRead more about it [in this blog post](https://menduz.com/posts/lys-language-project/).\n\n## Where to start?\n\n- To learn what can be used so far: browse the [standard library](https://github.com/lys-lang/lys/tree/master/stdlib/system)\n- To learn how real code looks like: browse the [execution tests](https://github.com/lys-lang/lys/tree/master/test/fixtures/execution)\n- To learn how high level constructs get compiled: browse the [sugar syntax tests](https://github.com/lys-lang/lys/tree/master/test/fixtures/semantics)\n- To start developing it locally, I do `make watch` and then I run the tests in other terminal with `make snapshot`\n- To see an example project: browse the [keccak repo](https://github.com/lys-lang/keccak)\n\n## Getting started\n\nFor the time being I'll use npm to distribute the language.\n\n1. `npm i -g lys`\n2. Create a folder and a file `main.lys`\n\n   ```dwl\n   import support::env\n\n   #[export]\n   fun test(): void = {\n     support::test::START(\"This is a test suite\")\n\n     printf(\"Hello %X\", 0xDEADBEEF)\n     support::test::mustEqual(3 as u8, 3 as u16, \"assertion name\")\n\n     support::test::END()\n   }\n   ```\n\n3. Run `lys main.lys --test --wast`. It will create `main.wasm` `main.wast` and will run the exported function named `test`.\n\n## How does it look?\n\n### Structs \u0026 Implementing operators\n\n```lys\nstruct Vector3(x: f32, y: f32, z: f32)\n\nimpl Vector3 {\n  fun -(lhs: Vector3, rhs: Vector3): Vector3 =\n    Vector3(\n      lhs.x - rhs.x,\n      lhs.y - rhs.y,\n      lhs.z - rhs.z\n    )\n\n  #[getter]\n  fun length(this: Vector3): f32 =\n    f32.sqrt(\n      this.x * this.x +\n      this.y * this.y +\n      this.z * this.z\n    )\n}\n\nfun distance(from: Vector3, to: Vector3): f32 = {\n  (from - to).length\n}\n```\n\n### Pattern matching\n\n```lys\n// this snippet is an actual unit test\n\nimport support::test\n\nenum Color {\n  Red\n  Green\n  Blue\n  Custom(r: i32, g: i32, b: i32)\n}\n\nfun isRed(color: Color): boolean = {\n  match color {\n    case is Red -\u003e true\n    case is Custom(r, g, b) -\u003e r == 255 \u0026\u0026 g == 0 \u0026\u0026 b == 0\n    else -\u003e false\n  }\n}\n\n#[export]\nfun main(): void = {\n  mustEqual(isRed(Red), true, \"isRed(Red)\")\n  mustEqual(isRed(Green), false, \"isRed(Green)\")\n  mustEqual(isRed(Blue), false, \"isRed(Blue)\")\n\n  mustEqual(isRed(Custom(255,0,0)), true, \"isRed(Custom(255,0,0))\")\n  mustEqual(isRed(Custom(0,1,3)), false, \"isRed(Custom(0,1,3))\")\n  mustEqual(isRed(Custom(255,1,3)), false, \"isRed(Custom(255,1,3))\")\n}\n```\n\n### Algebraic data types\n\n```lys\n// this snippet is an actual unit test\n\nenum Tree {\n  Node(value: i32, left: Tree, right: Tree)\n  Empty\n}\n\nfun sum(arg: Tree): i32 = {\n  match arg {\n    case is Empty -\u003e 0\n    case is Node(value, left, right) -\u003e value + sum(left) + sum(right)\n  }\n}\n\n#[export]\nfun main(): void = {\n  val tree = Node(42, Node(3, Empty, Empty), Empty)\n\n  support::test::mustEqual(sum(tree), 45, \"sum(tree) returns 45\")\n}\n```\n\n### Types and overloads are created in the language itself\n\nThe compiler only knows how to emit functions and how to link function names. I did that so I had fewer things hardcoded into the compiler and allows me to write the language in the language.\n\nTo do that, I had to add either a `%wasm { ... }` code block, and a `%stack { ... }` type.\n\n- `%wasm { ... }`: can only be used as a function body, not as an expression. It is **literally** the code that will be emited to WAST. The parameter names remain the same (prefixed with `$` as WAST indicates). Other symbols can be resolved with `fully::qualified::names`.\n\n- `%stack { wasm=\"i32\", size=4 }`: it is a type literal, it indicates how much memory should be allocated in structs (`size`) and what type to use in locals and function parameters (`wasm`, it needs a better name).\n\n```lys\n/** We first define the type `int` */\ntype int = %stack { wasm=\"i32\", size=4 }\n\n/** Implement some operators for the type `int` */\nimpl int {\n  fun +(lhs: int, rhs: int): int = %wasm {\n    (i32.add (local.get $lhs) (local.get $rhs))\n  }\n  fun -(lhs: int, rhs: int): int = %wasm {\n    (i32.sub (local.get $lhs) (local.get $rhs))\n  }\n  fun \u003e(lhs: int, rhs: int): boolean = %wasm {\n    (i32.gt_s (local.get $lhs) (local.get $rhs))\n  }\n}\n\nfun fibo(n: int, x1: int, x2: int): int = {\n  if (n \u003e 0) {\n    fibo(n - 1, x2, x1 + x2)\n  } else {\n    x1\n  }\n}\n\n#[export \"fibonacci\"] // \"fibonacci\" is the name of the exported function\nfun fib(n: int): int = fibo(n, 0, 1)\n```\n\n---\n\n## Some sugar\n\n### Enum types\n\n```lys\nenum Tree {\n  Node(value: i32, left: Tree, right: Tree)\n  Empty\n}\n```\n\nIs the sugar syntax for\n\n```lys\ntype Tree = Node | Empty\n\nstruct Node(value: i32, left: Tree, right: Tree)\nstruct Empty()\n\nimpl Tree {\n  fun is(lhs: Tree): boolean = lhs is Node || lhs is Empty\n  // ...\n}\n\nimpl Node {\n  fun as(lhs: Node): Tree = %wasm { (local.get $lhs) }\n\n  // ... many methods were removed for clarity ..\n}\n\nimpl Empty {\n  fun as(lhs: Node): Tree = %wasm { (local.get $lhs) }\n  // ...\n}\n\n```\n\n### `is` and `as` operators are just functions\n\n```lys\nimpl u8 {\n  /**\n   * Given an expression with the shape:\n   *\n   *   something as Type\n   *   ^^^^^^^^^    ^^^^\n   *        $lhs    $rhs\n   *\n   * A function with the signature:\n   *     fun as($lhs: LHSType): $rhs = ???\n   *\n   * Will be searched in the impl of LHSType\n   *\n   */\n\n\n  fun as(lhs: u8): f32 = %wasm { (f32.convert_i32_u (local.get $lhs)) }\n}\n\nfun byteAsFloat(value: u8): f32 = value as f32\n```\n\n```lys\nstruct CustomColor(rgb: i32)\n\ntype Red = void\nimpl Red {\n  fun is(lhs: CustomColor): boolean = match lhs {\n    case is Custom(rgb) -\u003e (rgb \u0026 0xFF0000) == 0xFF0000\n    else -\u003e false\n  }\n}\n\nvar x = CustomColor(0xFF0000) is Red\n\n// this may not be a good thing, but you get the idea\n```\n\n### There are no dragons behind the structs\n\nThe `struct` keyword is only a high level construct that creats a type and base implementation of something that behaves like a data type, normally in the heap.\n\n```lys\nstruct Node(value: i32, left: Tree, right: Tree)\n```\n\nIs the sugar syntax for\n\n```lys\n// We need to keep the name and order of the fields for deconstructors\n\ntype Node = %struct { value, left, right }\n\nimpl Node {\n  fun as(lhs: Node): Tree = %wasm {\n    (local.get $lhs)\n  }\n\n  #[explicit]\n  fun as(lhs: Node): ref = %wasm {\n    (local.get $lhs)\n  }\n\n  // the discriminant is the type number assigned by the compiler\n  #[inline]\n  private fun Node$discriminant(): u64 = {\n    val discriminant: u32 = Node.^discriminant\n    discriminant as u64 \u003c\u003c 32\n  }\n\n  // this is the function that gets called when Node is used as a function call\n  fun apply(value: i32, left: Tree, right: Tree): Node = {\n    // a pointer is allocated. Then using the function `fromPointer` it is converted\n    // to a valid Node reference\n    var $ref = fromPointer(system::core::memory::calloc(1 as u32, Node.^allocationSize))\n    property$0($ref, value)\n    property$1($ref, left)\n    property$2($ref, right)\n    $ref\n  }\n\n  // this function converts a raw address into a valid Node type\n  private fun fromPointer(ptr: u32): Node = %wasm {\n    (i64.or (call Node$discriminant) (i64.extend_i32_u (local.get $ptr)))\n  }\n\n  fun ==(a: Node, b: Node): boolean = %wasm {\n    (i64.eq (local.get $a) (local.get $b))\n  }\n\n  fun !=(a: Node, b: Node): boolean = %wasm {\n    (i64.ne (local.get $a) (local.get $b))\n  }\n\n  #[getter]\n  fun value(self: Node): i32 = property$0(self)\n  #[setter]\n  fun value(self: Node, value: i32): void = property$0(self, value)\n\n  #[inline]\n  private fun property$0(self: Node): i32 = i32.load(self, Node.^property$0_offset)\n  #[inline]\n  private fun property$0(self: Node, value: i32): void = i32.store(self, value, Node.^property$0_offset)\n\n  #[getter]\n  fun left(self: Node): Tree = property$1(self)\n  #[setter]\n  fun left(self: Node, value: Tree): void = property$1(self, value)\n\n  #[inline]\n  private fun property$1(self: Node): Tree = Tree.load(self, Node.^property$1_offset)\n  #[inline]\n  private fun property$1(self: Node, value: Tree): void = Tree.store(self, value, Node.^property$1_offset)\n\n  #[getter]\n  fun right(self: Node): Tree = property$2(self)\n  #[setter]\n  fun right(self: Node, value: Tree): void = property$2(self, value)\n\n  #[inline]\n  private fun property$2(self: Node): Tree = Tree.load(self, Node.^property$2_offset)\n  #[inline]\n  private fun property$2(self: Node, value: Tree): void = Tree.store(self, value, Node.^property$2_offset)\n\n  fun is(a: (Node | ref)): boolean = %wasm {\n    (i64.eq (i64.and (i64.const 0xffffffff00000000) (local.get $a)) (call Node$discriminant))\n  }\n\n  fun store(lhs: ref, rhs: Node, offset: u32): void = %wasm {\n    (i64.store (i32.add (local.get $offset) (call addressFromRef (local.get $lhs))) (local.get $rhs))\n  }\n\n  fun load(lhs: ref, offset: u32): Node = %wasm {\n    (i64.load (i32.add (local.get $offset) (call addressFromRef (local.get $lhs))))\n  }\n}\n```\n\n![Build Status](https://dev.azure.com/lys-lang/Lys/_apis/build/status/lys-lang.lys?branchName=master)\n[![Build Status](https://travis-ci.org/lys-lang/lys.svg?branch=master)](https://travis-ci.org/lys-lang/lys)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flys-lang%2Flys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flys-lang%2Flys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flys-lang%2Flys/lists"}