All Projects → lys-lang → Lys

lys-lang / Lys

Licence: apache-2.0
⚜︎ A language that compiles to WebAssembly

Programming Languages

typescript
32286 projects
language
365 projects

Labels

Projects that are alternatives of or similar to Lys

Nectarjs
🔱 Javascript's God Mode. No VM. No Bytecode. No GC. Just native binaries.
Stars: ✭ 3,113 (+730.13%)
Mutual labels:  wasm
Wasm Util
WebAssembly utilities
Stars: ✭ 313 (-16.53%)
Mutual labels:  wasm
Unrust
unrust - A pure rust based (webgl 2.0 / native) game engine
Stars: ✭ 341 (-9.07%)
Mutual labels:  wasm
Wasabi
A dynamic analysis framework for WebAssembly programs.
Stars: ✭ 279 (-25.6%)
Mutual labels:  wasm
Highway
Performance-portable, length-agnostic SIMD with runtime dispatch
Stars: ✭ 301 (-19.73%)
Mutual labels:  wasm
Awesome Wasi
😎 Curated list of awesome things regarding WebAssembly WASI ecosystem.
Stars: ✭ 319 (-14.93%)
Mutual labels:  wasm
Wasm Git
GIT for nodejs and the browser using https://libgit2.org compiled to WebAssembly with https://emscripten.org
Stars: ✭ 261 (-30.4%)
Mutual labels:  wasm
Awesome Yew
😎 A curated list of awesome things related to Yew / WebAssembly.
Stars: ✭ 353 (-5.87%)
Mutual labels:  wasm
Pigo
Fast face detection, pupil/eyes localization and facial landmark points detection library in pure Go.
Stars: ✭ 3,542 (+844.53%)
Mutual labels:  wasm
Platon Go
Golang implementation of the PlatON protocol
Stars: ✭ 331 (-11.73%)
Mutual labels:  wasm
Modfy.video
A video transcoder and converter built using Web Assembly and FFMPEG to transcode and convert videos right in your browser while protecting your privacy
Stars: ✭ 283 (-24.53%)
Mutual labels:  wasm
Ant Design Blazor
🌈A set of enterprise-class UI components based on Ant Design and Blazor WebAssembly.
Stars: ✭ 3,890 (+937.33%)
Mutual labels:  wasm
Wasm Pack
This tool seeks to be a one-stop shop for building and working with rust- generated WebAssembly that you would like to interop with JavaScript, in the browser or with Node.js. wasm-pack helps you build rust-generated WebAssembly packages that you could publish to the npm registry, or otherwise use alongside any javascript packages in workflows that you already use, such as webpack.
Stars: ✭ 3,848 (+926.13%)
Mutual labels:  wasm
Glas
WebGL in WebAssembly with AssemblyScript
Stars: ✭ 278 (-25.87%)
Mutual labels:  wasm
Nimtorch
PyTorch - Python + Nim
Stars: ✭ 346 (-7.73%)
Mutual labels:  wasm
Idawasm
IDA Pro loader and processor modules for WebAssembly
Stars: ✭ 264 (-29.6%)
Mutual labels:  wasm
Parity Wasm
WebAssembly serialization/deserialization in rust
Stars: ✭ 314 (-16.27%)
Mutual labels:  wasm
Wac
WebAssembly interpreter in C
Stars: ✭ 372 (-0.8%)
Mutual labels:  wasm
React Wasm
Declarative WebAssembly instantiation for React
Stars: ✭ 349 (-6.93%)
Mutual labels:  wasm
Wasm3
🚀 The fastest WebAssembly interpreter, and the most universal runtime
Stars: ✭ 4,375 (+1066.67%)
Mutual labels:  wasm







Lys, a language that compiles to WebAssembly.

Read more about it in this blog post.

Where to start?

  • To learn what can be used so far: browse the standard library
  • To learn how real code looks like: browse the execution tests
  • To learn how high level constructs get compiled: browse the sugar syntax tests
  • To start developing it locally, I do make watch and then I run the tests in other terminal with make snapshot
  • To see an example project: browse the keccak repo

Getting started

For the time being I'll use npm to distribute the language.

  1. npm i -g lys

  2. Create a folder and a file main.lys

    import support::env
    
    #[export]
    fun test(): void = {
      support::test::START("This is a test suite")
    
      printf("Hello %X", 0xDEADBEEF)
      support::test::mustEqual(3 as u8, 3 as u16, "assertion name")
    
      support::test::END()
    }
    
  3. Run lys main.lys --test --wast. It will create main.wasm main.wast and will run the exported function named test.

How does it look?

Structs & Implementing operators

struct Vector3(x: f32, y: f32, z: f32)

impl Vector3 {
  fun -(lhs: Vector3, rhs: Vector3): Vector3 =
    Vector3(
      lhs.x - rhs.x,
      lhs.y - rhs.y,
      lhs.z - rhs.z
    )

  #[getter]
  fun length(this: Vector3): f32 =
    f32.sqrt(
      this.x * this.x +
      this.y * this.y +
      this.z * this.z
    )
}

fun distance(from: Vector3, to: Vector3): f32 = {
  (from - to).length
}

Pattern matching

// this snippet is an actual unit test

import support::test

enum Color {
  Red
  Green
  Blue
  Custom(r: i32, g: i32, b: i32)
}

fun isRed(color: Color): boolean = {
  match color {
    case is Red -> true
    case is Custom(r, g, b) -> r == 255 && g == 0 && b == 0
    else -> false
  }
}

#[export]
fun main(): void = {
  mustEqual(isRed(Red), true, "isRed(Red)")
  mustEqual(isRed(Green), false, "isRed(Green)")
  mustEqual(isRed(Blue), false, "isRed(Blue)")

  mustEqual(isRed(Custom(255,0,0)), true, "isRed(Custom(255,0,0))")
  mustEqual(isRed(Custom(0,1,3)), false, "isRed(Custom(0,1,3))")
  mustEqual(isRed(Custom(255,1,3)), false, "isRed(Custom(255,1,3))")
}

Algebraic data types

// this snippet is an actual unit test

enum Tree {
  Node(value: i32, left: Tree, right: Tree)
  Empty
}

fun sum(arg: Tree): i32 = {
  match arg {
    case is Empty -> 0
    case is Node(value, left, right) -> value + sum(left) + sum(right)
  }
}

#[export]
fun main(): void = {
  val tree = Node(42, Node(3, Empty, Empty), Empty)

  support::test::mustEqual(sum(tree), 45, "sum(tree) returns 45")
}

Types and overloads are created in the language itself

The 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.

To do that, I had to add either a %wasm { ... } code block, and a %stack { ... } type.

  • %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.

  • %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).

/** We first define the type `int` */
type int = %stack { wasm="i32", size=4 }

/** Implement some operators for the type `int` */
impl int {
  fun +(lhs: int, rhs: int): int = %wasm {
    (i32.add (get_local $lhs) (get_local $rhs))
  }
  fun -(lhs: int, rhs: int): int = %wasm {
    (i32.sub (get_local $lhs) (get_local $rhs))
  }
  fun >(lhs: int, rhs: int): boolean = %wasm {
    (i32.gt_s (get_local $lhs) (get_local $rhs))
  }
}

fun fibo(n: int, x1: int, x2: int): int = {
  if (n > 0) {
    fibo(n - 1, x2, x1 + x2)
  } else {
    x1
  }
}

#[export "fibonacci"] // "fibonacci" is the name of the exported function
fun fib(n: int): int = fibo(n, 0, 1)

Some sugar

Enum types

enum Tree {
  Node(value: i32, left: Tree, right: Tree)
  Empty
}

Is the sugar syntax for

type Tree = Node | Empty

struct Node(value: i32, left: Tree, right: Tree)
struct Empty()

impl Tree {
  fun is(lhs: Tree): boolean = lhs is Node || lhs is Empty
  // ...
}

impl Node {
  fun as(lhs: Node): Tree = %wasm { (local.get $lhs) }

  // ... many methods were removed for clarity ..
}

impl Empty {
  fun as(lhs: Node): Tree = %wasm { (local.get $lhs) }
  // ...
}

is and as operators are just functions

impl u8 {
  /**
   * Given an expression with the shape:
   *
   *   something as Type
   *   ^^^^^^^^^    ^^^^
   *        $lhs    $rhs
   *
   * A function with the signature:
   *     fun as($lhs: LHSType): $rhs = ???
   *
   * Will be searched in the impl of LHSType
   *
   */


  fun as(lhs: u8): f32 = %wasm { (f32.convert_i32_u (get_local $lhs)) }
}

fun byteAsFloat(value: u8): f32 = value as f32
struct CustomColor(rgb: i32)

type Red = void
impl Red {
  fun is(lhs: CustomColor): boolean = match lhs {
    case is Custom(rgb) -> (rgb & 0xFF0000) == 0xFF0000
    else -> false
  }
}

var x = CustomColor(0xFF0000) is Red

// this may not be a good thing, but you get the idea

There are no dragons behind the structs

The 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.

struct Node(value: i32, left: Tree, right: Tree)

Is the sugar syntax for

// We need to keep the name and order of the fields for deconstructors

type Node = %struct { value, left, right }

impl Node {
  fun as(lhs: Node): Tree = %wasm {
    (local.get $lhs)
  }

  #[explicit]
  fun as(lhs: Node): ref = %wasm {
    (local.get $lhs)
  }

  // the discriminant is the type number assigned by the compiler
  #[inline]
  private fun Node$discriminant(): u64 = {
    val discriminant: u32 = Node.^discriminant
    discriminant as u64 << 32
  }

  // this is the function that gets called when Node is used as a function call
  fun apply(value: i32, left: Tree, right: Tree): Node = {
    // a pointer is allocated. Then using the function `fromPointer` it is converted
    // to a valid Node reference
    var $ref = fromPointer(system::core::memory::calloc(1 as u32, Node.^allocationSize))
    property$0($ref, value)
    property$1($ref, left)
    property$2($ref, right)
    $ref
  }

  // this function converts a raw address into a valid Node type
  private fun fromPointer(ptr: u32): Node = %wasm {
    (i64.or (call Node$discriminant) (i64.extend_u/i32 (local.get $ptr)))
  }

  fun ==(a: Node, b: Node): boolean = %wasm {
    (i64.eq (local.get $a) (local.get $b))
  }

  fun !=(a: Node, b: Node): boolean = %wasm {
    (i64.ne (local.get $a) (local.get $b))
  }

  #[getter]
  fun value(self: Node): i32 = property$0(self)
  #[setter]
  fun value(self: Node, value: i32): void = property$0(self, value)

  #[inline]
  private fun property$0(self: Node): i32 = i32.load(self, Node.^property$0_offset)
  #[inline]
  private fun property$0(self: Node, value: i32): void = i32.store(self, value, Node.^property$0_offset)

  #[getter]
  fun left(self: Node): Tree = property$1(self)
  #[setter]
  fun left(self: Node, value: Tree): void = property$1(self, value)

  #[inline]
  private fun property$1(self: Node): Tree = Tree.load(self, Node.^property$1_offset)
  #[inline]
  private fun property$1(self: Node, value: Tree): void = Tree.store(self, value, Node.^property$1_offset)

  #[getter]
  fun right(self: Node): Tree = property$2(self)
  #[setter]
  fun right(self: Node, value: Tree): void = property$2(self, value)

  #[inline]
  private fun property$2(self: Node): Tree = Tree.load(self, Node.^property$2_offset)
  #[inline]
  private fun property$2(self: Node, value: Tree): void = Tree.store(self, value, Node.^property$2_offset)

  fun is(a: (Node | ref)): boolean = %wasm {
    (i64.eq (i64.and (i64.const 0xffffffff00000000) (local.get $a)) (call Node$discriminant))
  }

  fun store(lhs: ref, rhs: Node, offset: u32): void = %wasm {
    (i64.store (i32.add (local.get $offset) (call addressFromRef (local.get $lhs))) (local.get $rhs))
  }

  fun load(lhs: ref, offset: u32): Node = %wasm {
    (i64.load (i32.add (local.get $offset) (call addressFromRef (local.get $lhs))))
  }
}

Build Status Build Status

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].