Git Product home page Git Product logo

dfinity / motoko Goto Github PK

View Code? Open in Web Editor NEW
494.0 40.0 98.0 39.69 MB

Simple high-level language for writing Internet Computer canisters

License: Apache License 2.0

OCaml 46.50% Makefile 0.52% TeX 1.16% Standard ML 0.01% Nix 1.27% HTML 0.03% JavaScript 0.53% Shell 0.94% Emacs Lisp 0.29% C 0.03% WebAssembly 0.03% Haskell 2.23% Swift 32.31% Perl 0.14% sed 0.08% Rust 13.82% Python 0.12%
internet-computer motoko motoko-language programming-language

motoko's Introduction

Motoko · GitHub license Tests PRs Welcome

A safe, simple, actor-based programming language for authoring Internet Computer (IC) canister smart contracts.

User Documentation & Samples

Introduction

Motivation and Goals

  • High-level language for programming IC applications

  • Simple ("K.I.S.S.") design and familiar syntax for average programmers

  • Good and convenient support for actor model

  • Good fit for underlying Wasm and IC execution model

  • Anticipate future extensions to Wasm where possible

Key Design Points

  • Simple class-based OO language, objects as closures

  • Classes can be actors

  • Async construct for direct-style programming of asynchronous messaging

  • Structurally typed with simple generics and subtyping

  • Overflow-checked number types, explicit conversions

  • JavaScript/TypeScript-style syntax but without the JavaScript madness

  • Inspirations from Java, C#, JavaScript, Swift, Pony, ML, Haskell

Related repositories

Community resources

Contribution

See our CONTRIBUTING and CODE OF CONDUCT to get started.

motoko's People

Contributors

aterga avatar basvandijk avatar chenyan-dfinity avatar christyjacob4 avatar crusso avatar dependabot[bot] avatar dfinity-bot avatar dfx-json avatar dprats avatar enzoh avatar floorlamp avatar ggreif avatar jessiemongeon1 avatar kentosugama avatar kinghong001 avatar knl avatar kritzcreek avatar lostman avatar lsgunnlsgunn avatar luc-blaeser avatar matthewhammer avatar mergify[bot] avatar ninegua avatar nmattia avatar nomeata avatar osa1 avatar paulyoung avatar pikajude avatar rossberg avatar rvanasa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

motoko's Issues

async lowering failures

This program typechecks:

let a = actor {
  hello () : async Text {
    "Hello ";
  };
  world () : async Text {
    "World!\n"
  };
  go () : async () {
    print((await hello()) # (await world()));
  };
};

let _ = a.go()

Yet the lowering seems to do the wrong thing, as shown by compiling this with -dl:

(BlockE
  (LetD
    (VarP a)
    (ObjE
      Actor
      anon-actor-1.9
…
      (go
        go
        (DecE
          (FuncD
            Local
            go
            (TupP (TupP) (VarP $13))
            (TupT
              (FuncT Call Local (FuncT Call Local (TupT) (TupT)) (TupT))
              (FuncT
                Call Sharable
                (FuncT Call Local (FuncT Call Local (TupT) (TupT)) (TupT))
                (TupT)
              )
            )
            (CallE
…
          )
        )
        Const
        Public
      )
    )
  )
  (LetD WildP (CallE (DotE (VarE a) go) (TupE)))
)

Note that the type of a.go changes: It gains an extra parameter taking the continuation, but the call to a.go is not affected.

System actors and async/await

I imagine that calling functions provided by the systems actors (particularly lookup) will be a fairly common, so it would be nice to be able to use async/await to do so.

It looks like we would need to provide wrappers around functions like lookup to be able to do that. Is this something that would be exposed as part of a prelude?

From https://github.com/dfinity-lab/sdk-testenv/issues/4:

The function lookup(requstId, address, callback_found, callback_unknown) expects two funcrefs as parameters, callback_found, callback_unknown. The latter one is called if the address has no corresponding element in the registry

Array semantics

I think there are some questions about array semantics unclear:

  • Do arrays have a fixed, immutable size determined upon creation? Can it be queried?
  • Do out-of-bounds access trap? Or cause undefined behavior (surely not…)
  • Or are arrays dynamically resized to whatever size is required to hold them? With what values are they initialized with?

replace dev-in-nix with dev?

dev-in-nix is still a submodule here, which causes hydra to failed for this PR:

dfinity-lab/dev/pull/97

We can either fix it by pointing the submodule to the correct repo, or avoid using dsh in this repo, and move related tests to dev instead, or merge actorscript into dev :-)

Type casting

To borrow from the example in the README:

let account = await registry.lookup(...);

if (account is Account) {
  // Now it's safe to do things with the account
}

Could we do something like this instead?

let account : Account? = await registry.lookup(...);
// The type system now enforces that we must be treat the account as optional

What I would prefer for lookup specifically is if the wrapper discussed in #18 would return an optional value so that casting wasn't needed.

In general though I think casting values in a safe way would be useful. as? in Swift is probably the closest thing to what I have in mind as opposed to as (compiled checked) or as! (forced)

Installation instructions

The README could need installation instructions (or a separate INSTALL file).

This is what I did on Mac (not sure if all is necessary):

brew install ocaml
brew install opam
opam init
eval `opam config env`
opam install ocamlfind
opam install wasm
opam install zarith
opam install menhir
make

Run as _build/main.byte.

closures.as:25.42-25.44: fatal error, Invalid_argument("as_nat")

I found some code where the interpreter throws an exception:

-- Finished closures.as:
let add1 : Nat -> Nat = func

closures.as:25.42-25.44: fatal error, Invalid_argument("as_nat")

Raised at file "value.ml", line 163, characters 22-42
Called from file "value.ml", line 214, characters 34-44
Called from file "main.ml", line 37, characters 18-62
Called from file "map.ml", line 270, characters 20-25
Called from file "map.ml", line 270, characters 10-18
Called from file "main.ml", line 79, characters 6-42

Last context:
add1 = func
answer = 42
answer2 = 42
answer3 = 51
answers = [43, 44]
fs = [func, func, func]
test_answer = func
test_answer2 = func
test_answer3 = func
test_answers = func

(See src/tests/closures.as)

@rossberg, can you have a look?

Source maps

We would like to be able to produce source maps from WebAssembly to the corresponding ActorScript.

Simple interop with system actors

So far, the compiler only implements a actor-specific serialization of heap structures to DFINITY types (e.g. elembuf and databuf), and that is probably fine – we don’t expect all our heap structure (with cycles and whatnot) to be suitable for language interop.

But we also have to deal with other actors that expect a different format. In particular, in order to update to the latest version of dvm (previously dsh), we need to talk to the console actor.

As a first shot I gave it the following type:

actor { log : Text -> () } 

but that is not good enough: We need to know that this log function expects a Text value expressed as a raw databuf, and not our serialization format.

Any ideas?

An alternatively would be to change our format to “accidentally” coincide with what the system actors expect, but I am worried that such a non-homogenous format would be tricky in the presence of polymorphism and subtyping and what not. (But maybe it would work just fine and I just have to think about it harder.)

Implement a desugaring pass

Could simplify things like boolean operators, objects, etc.

Lowering to a subset of the input syntax would allow running the interpreter and (once we have it) type validator against both and compare.

Elaborate all operationally relevant information in the type checker

In a simple world, a type system tells you which programs will not fail, but do not affect the operational behavior. It is possible to ignore the types, and still run or compile the program, i.e. all operational information is present in the terms.

But in more complex languages, like ours, there are some operationally relevant details not manifest in the source term AST, but are filled in by type inference: E.g. whether (-) needs to prevent creating negative numbers or not; which number types to deal with; how to compile a function call etc.

So I would suggest that we extend the term AST with foo option fields that are always None after parsing, but are filled in by type inference with enough information that one can run or compile the programs without looking at the type annotations. E.g. for Subtract operation, it would specify the ground type and whether to trap on creating negative numbers.

This would avoid duplicating logic like this:

  | RelE (exp1, op, exp2) ->
    let t = T.lub Con.Env.empty (* both types are primitive *)
      (T.as_immut exp1.note.note_typ) (T.as_immut exp2.note.note_typ) in

which looks like it should be part of type inference (only), but currently needs to be included in the interpreter and (later) in the compiler.

TL;DR: The post type-checker AST should executable even after type annotations are erased.

Compiling from asc.js with an empty string produces a lot of output

Is this the expected behavior? I was expecting (module)

(Click each to expand)

> asc.ActorScript.compileWat("")
'(module\n (type $0 (func))\n (type $1 (func (param i32 i32) (result i32)))\n (table $0 14 14 anyfunc)\n (memory $0 1024)\n (global $0 (mut i32) (i32.const 0))\n (func $0\n (type 1)\n (local i32)\n (get_local 0)\n (i32.load offset=4)\n (get_local 1)\n (i32.const 7)\n (i32.add)\n (i32.const 4)\n (i32.mul)\n (i32.add)\n (i32.load)\n )\n (func $1\n (type 1)\n (local i32)\n (get_local 0)\n (i32.load offset=4)\n (get_local 1)\n (i32.load)\n (i32.const 7)\n (i32.add)\n (i32.const 4)\n (i32.mul)\n (i32.add)\n (get_local 1)\n (i32.load offset=4)\n (i32.store)\n (i32.const 0)\n )\n (func $2\n (type 1)\n (local i32)\n (get_local 0)\n (i32.load offset=4)\n (i32.load offset=24)\n )\n (func $3\n (type 1)\n (local i32 i32 i32 i32 i32 i32 i32)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 8)\n (get_local 8)\n (i32.const 0)\n (i32.store)\n (get_local 8)\n (set_local 3)\n (get_global 0)\n (get_global 0)\n (i32.const 12)\n (i32.add)\n (set_global 0)\n (set_local 7)\n (get_local 7)\n (i32.const 4)\n (i32.store)\n (get_local 7)\n (get_local 3)\n (i32.store offset=4)\n (get_local 7)\n (get_local 0)\n (i32.load offset=4)\n (i32.store offset=8)\n (get_local 7)\n (set_local 4)\n (get_global 0)\n (get_global 0)\n (i32.const 28)\n (i32.add)\n (set_global 0)\n (set_local 5)\n (get_local 5)\n (i32.const 24)\n (i32.add)\n (set_local 6)\n (get_local 5)\n (i32.const 1)\n (i32.store)\n (get_local 5)\n (get_local 4)\n (i32.store offset=24)\n (get_local 5)\n )\n (func $4\n (type 1)\n (local i32 i32)\n (get_local 0)\n (i32.load offset=4)\n (i32.load)\n (set_local 3)\n (get_local 3)\n (get_local 0)\n (i32.load offset=8)\n (i32.load offset=24)\n (i32.eq)\n (if\n (result i32)\n (then (i32.const 2147483647))\n (else\n (get_local 0)\n (i32.load offset=4)\n (get_local 3)\n (i32.const 1)\n (i32.add)\n (i32.store)\n (get_local 3)\n )\n )\n )\n (func $5\n (type 1)\n (local i32 i32 i32 i32 i32 i32 i32)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 8)\n (get_local 8)\n (i32.const 0)\n (i32.store)\n (get_local 8)\n (set_local 3)\n (get_global 0)\n (get_global 0)\n (i32.const 12)\n (i32.add)\n (set_global 0)\n (set_local 7)\n (get_local 7)\n (i32.const 6)\n (i32.store)\n (get_local 7)\n (get_local 3)\n (i32.store offset=4)\n (get_local 7)\n (get_local 0)\n (i32.load offset=4)\n (i32.store offset=8)\n (get_local 7)\n (set_local 4)\n (get_global 0)\n (get_global 0)\n (i32.const 28)\n (i32.add)\n (set_global 0)\n (set_local 5)\n (get_local 5)\n (i32.const 24)\n (i32.add)\n (set_local 6)\n (get_local 5)\n (i32.const 1)\n (i32.store)\n (get_local 5)\n (get_local 4)\n (i32.store offset=24)\n (get_local 5)\n )\n (func $6\n (type 1)\n (local i32 i32)\n (get_local 0)\n (i32.load offset=4)\n (i32.load)\n (set_local 3)\n (get_local 3)\n (get_local 0)\n (i32.load offset=8)\n (i32.load offset=24)\n (i32.eq)\n (if\n (result i32)\n (then (i32.const 2147483647))\n (else\n (get_local 0)\n (i32.load offset=4)\n (get_local 3)\n (i32.const 1)\n (i32.add)\n (i32.store)\n (get_local 0)\n (i32.load offset=8)\n (get_local 3)\n (i32.const 7)\n (i32.add)\n (i32.const 4)\n (i32.mul)\n (i32.add)\n (i32.load)\n )\n )\n )\n (func $7\n (type 1)\n (local i32)\n (get_local 1)\n (i32.const 0)\n (i32.lt_s)\n (if\n (result i32)\n (then (i32.const 0) (get_local 1) (i32.sub))\n (else (get_local 1))\n )\n )\n (func $8\n (type 1)\n (local i32 i32 i32)\n (get_local 0)\n (i32.load offset=4)\n (set_local 3)\n (get_local 0)\n (i32.load offset=8)\n (set_local 4)\n (get_local 1)\n (set_local 2)\n (block (result i32) (get_local 2) (drop) (i32.const 1))\n (if (then) (else (unreachable)))\n (get_local 3)\n (i32.load)\n (get_local 4)\n (i32.load)\n (i32.le_s)\n (if\n (result i32)\n (then (i32.const 2147483647))\n (else\n (get_local 3)\n (get_local 3)\n (i32.load)\n (i32.const 1)\n (i32.sub)\n (i32.store)\n (i32.const 0)\n (drop)\n (get_local 3)\n (i32.load)\n )\n )\n )\n (func $9\n (type 1)\n (local i32 i32 i32 i32 i32 i32 i32 i32 i32)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 3)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 4)\n (get_local 1)\n (set_local 2)\n (block\n (result i32)\n (get_local 2)\n (tee_local 2)\n (get_local 2)\n (i32.load)\n (set_local 2)\n (get_local 3)\n (get_local 2)\n (i32.store)\n (tee_local 2)\n (get_local 2)\n (i32.load offset=4)\n (set_local 2)\n (get_local 4)\n (get_local 2)\n (i32.store)\n (drop)\n (i32.const 1)\n )\n (if (then) (else (unreachable)))\n (get_global 0)\n (get_global 0)\n (i32.const 32)\n (i32.add)\n (set_global 0)\n (set_local 5)\n (get_local 5)\n (i32.const 28)\n (i32.add)\n (set_local 6)\n (get_local 5)\n (i32.const 24)\n (i32.add)\n (set_local 7)\n (get_local 5)\n (get_local 0)\n (i32.store)\n (get_local 5)\n (get_local 3)\n (i32.load)\n (i32.const 1)\n (i32.add)\n (i32.store offset=28)\n (get_local 5)\n (get_global 0)\n (get_global 0)\n (i32.const 12)\n (i32.add)\n (set_global 0)\n (set_local 8)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 10)\n (get_local 10)\n (get_local 8)\n (i32.store)\n (get_local 10)\n (set_local 9)\n (get_local 8)\n (i32.const 8)\n (i32.store)\n (get_local 8)\n (get_local 6)\n (i32.store offset=4)\n (get_local 8)\n (get_local 4)\n (i32.store offset=8)\n (get_local 8)\n (i32.store offset=24)\n (get_local 5)\n )\n (func $10\n (type 1)\n (local i32 i32 i32 i32)\n (get_local 0)\n (i32.load offset=4)\n (set_local 3)\n (get_local 0)\n (i32.load offset=8)\n (set_local 4)\n (get_local 1)\n (set_local 2)\n (block (result i32) (get_local 2) (drop) (i32.const 1))\n (if (then) (else (unreachable)))\n (get_local 3)\n (i32.load)\n (get_local 4)\n (i32.load)\n (i32.gt_s)\n (if\n (result i32)\n (then (i32.const 2147483647))\n (else\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 5)\n (get_local 3)\n (i32.load)\n (set_local 2)\n (block\n (result i32)\n (get_local 2)\n (set_local 2)\n (get_local 5)\n (get_local 2)\n (i32.store)\n (i32.const 1)\n )\n (if (then) (else (unreachable)))\n (get_local 3)\n (get_local 3)\n (i32.load)\n (i32.const 1)\n (i32.add)\n (i32.store)\n (i32.const 0)\n (drop)\n (get_local 5)\n (i32.load)\n )\n )\n )\n (func $11\n (type 1)\n (local i32 i32 i32 i32 i32 i32 i32 i32 i32)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 3)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 4)\n (get_local 1)\n (set_local 2)\n (block\n (result i32)\n (get_local 2)\n (tee_local 2)\n (get_local 2)\n (i32.load)\n (set_local 2)\n (get_local 3)\n (get_local 2)\n (i32.store)\n (tee_local 2)\n (get_local 2)\n (i32.load offset=4)\n (set_local 2)\n (get_local 4)\n (get_local 2)\n (i32.store)\n (drop)\n (i32.const 1)\n )\n (if (then) (else (unreachable)))\n (get_global 0)\n (get_global 0)\n (i32.const 32)\n (i32.add)\n (set_global 0)\n (set_local 5)\n (get_local 5)\n (i32.const 28)\n (i32.add)\n (set_local 6)\n (get_local 5)\n (i32.const 24)\n (i32.add)\n (set_local 7)\n (get_local 5)\n (get_local 0)\n (i32.store)\n (get_local 5)\n (get_local 3)\n (i32.load)\n (i32.store offset=28)\n (get_local 5)\n (get_global 0)\n (get_global 0)\n (i32.const 12)\n (i32.add)\n (set_global 0)\n (set_local 8)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 10)\n (get_local 10)\n (get_local 8)\n (i32.store)\n (get_local 10)\n (set_local 9)\n (get_local 8)\n (i32.const 10)\n (i32.store)\n (get_local 8)\n (get_local 6)\n (i32.store offset=4)\n (get_local 8)\n (get_local 4)\n (i32.store offset=8)\n (get_local 8)\n (i32.store offset=24)\n (get_local 5)\n )\n (func $12\n (type 1)\n (local i32)\n (get_local 1)\n (set_local 2)\n (block (result i32) (get_local 2) (drop) (i32.const 1))\n (if (then) (else (unreachable)))\n (i32.const 0)\n )\n (func $13\n (type 0)\n (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 2)\n (get_local 2)\n (i32.const 7)\n (i32.store)\n (get_local 2)\n (set_local 1)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 3)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 4)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 6)\n (get_local 6)\n (get_local 4)\n (i32.store)\n (get_local 6)\n (set_local 5)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 7)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 9)\n (get_local 9)\n (get_local 7)\n (i32.store)\n (get_local 9)\n (set_local 8)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 10)\n (get_global 0)\n (get_global 0)\n (i32.const 4)\n (i32.add)\n (set_global 0)\n (set_local 12)\n (get_local 12)\n (get_local 10)\n (i32.store)\n (get_local 12)\n (set_local 11)\n (get_local 1)\n (set_local 0)\n (block\n (result i32)\n (get_local 0)\n (set_local 0)\n (get_local 3)\n (get_local 0)\n (i32.store)\n (i32.const 1)\n )\n (if (then) (else (unreachable)))\n (get_local 4)\n (i32.const 12)\n (i32.store)\n (get_local 7)\n (i32.const 11)\n (i32.store)\n (get_local 10)\n (i32.const 9)\n (i32.store)\n (get_local 10)\n (drop)\n )\n (start 13)\n (elem 0 (offset (i32.const 0)) 0 1 2 3 4 5 6 7 8 9 10 11 12 13)\n)\n'
> asc.ActorScript.compileWasm("")
'\u0000asm\u0001\u0000\u0000\u0000\u0001����\u0000\u0002`\u0000\u0000`\u0002��\u0001�\u0003����\u0000\u000e\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0000\u0004����\u0000\u0001p\u0001\u000e\u000e\u0005����\u0000\u0001\u0000�\b\u0006����\u0000\u0001�\u0001A\u0000\u000b\b����\u0000\r\t����\u0000\u0001\u0000A\u0000\u000b\u000e\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\nÐ���\u0000\u000e����\u0000\u0001\u0001� \u0000(\u0002\u0004 \u0001A\u0007jA\u0004lj(\u0002\u0000\u000b����\u0000\u0001\u0001� \u0000(\u0002\u0004 \u0001(\u0002\u0000A\u0007jA\u0004lj \u0001(\u0002\u00046\u0002\u0000A\u0000\u000b����\u0000\u0001\u0001� \u0000(\u0002\u0004(\u0002\u0018\u000bã���\u0000\u0001\u0007�#\u0000#\u0000A\u0004j$\u0000!\b \bA\u00006\u0002\u0000 \b!\u0003#\u0000#\u0000A\fj$\u0000!\u0007 \u0007A\u00046\u0002\u0000 \u0007 \u00036\u0002\u0004 \u0007 \u0000(\u0002\u00046\u0002\b \u0007!\u0004#\u0000#\u0000A\u001cj$\u0000!\u0005 \u0005A\u0018j!\u0006 \u0005A\u00016\u0002\u0000 \u0005 \u00046\u0002\u0018 \u0005\u000b²���\u0000\u0001\u0002� \u0000(\u0002\u0004(\u0002\u0000!\u0003 \u0003 \u0000(\u0002\b(\u0002\u0018F\u0004�Aÿÿÿÿ\u0007\u0005 \u0000(\u0002\u0004 \u0003A\u0001j6\u0002\u0000 \u0003\u000b\u000bã���\u0000\u0001\u0007�#\u0000#\u0000A\u0004j$\u0000!\b \bA\u00006\u0002\u0000 \b!\u0003#\u0000#\u0000A\fj$\u0000!\u0007 \u0007A\u00066\u0002\u0000 \u0007 \u00036\u0002\u0004 \u0007 \u0000(\u0002\u00046\u0002\b \u0007!\u0004#\u0000#\u0000A\u001cj$\u0000!\u0005 \u0005A\u0018j!\u0006 \u0005A\u00016\u0002\u0000 \u0005 \u00046\u0002\u0018 \u0005\u000bÁ���\u0000\u0001\u0002� \u0000(\u0002\u0004(\u0002\u0000!\u0003 \u0003 \u0000(\u0002\b(\u0002\u0018F\u0004�Aÿÿÿÿ\u0007\u0005 \u0000(\u0002\u0004 \u0003A\u0001j6\u0002\u0000 \u0000(\u0002\b \u0003A\u0007jA\u0004lj(\u0002\u0000\u000b\u000b����\u0000\u0001\u0001� \u0001A\u0000H\u0004�A\u0000 \u0001k\u0005 \u0001\u000b\u000bÍ���\u0000\u0001\u0003� \u0000(\u0002\u0004!\u0003 \u0000(\u0002\b!\u0004 \u0001!\u0002\u0002� \u0002\u001aA\u0001\u000b\u0004@\u0005\u0000\u000b \u0003(\u0002\u0000 \u0004(\u0002\u0000L\u0004�Aÿÿÿÿ\u0007\u0005 \u0003 \u0003(\u0002\u0000A\u0001k6\u0002\u0000A\u0000\u001a \u0003(\u0002\u0000\u000b\u000b·���\u0000\u0001\t�#\u0000#\u0000A\u0004j$\u0000!\u0003#\u0000#\u0000A\u0004j$\u0000!\u0004 \u0001!\u0002\u0002� \u0002"\u0002 \u0002(\u0002\u0000!\u0002 \u0003 \u00026\u0002\u0000"\u0002 \u0002(\u0002\u0004!\u0002 \u0004 \u00026\u0002\u0000\u001aA\u0001\u000b\u0004@\u0005\u0000\u000b#\u0000#\u0000A j$\u0000!\u0005 \u0005A\u001cj!\u0006 \u0005A\u0018j!\u0007 \u0005 \u00006\u0002\u0000 \u0005 \u0003(\u0002\u0000A\u0001j6\u0002\u001c \u0005#\u0000#\u0000A\fj$\u0000!\b#\u0000#\u0000A\u0004j$\u0000!\n \n \b6\u0002\u0000 \n!\t \bA\b6\u0002\u0000 \b \u00066\u0002\u0004 \b \u00046\u0002\b \b6\u0002\u0018 \u0005\u000bô���\u0000\u0001\u0004� \u0000(\u0002\u0004!\u0003 \u0000(\u0002\b!\u0004 \u0001!\u0002\u0002� \u0002\u001aA\u0001\u000b\u0004@\u0005\u0000\u000b \u0003(\u0002\u0000 \u0004(\u0002\u0000J\u0004�Aÿÿÿÿ\u0007\u0005#\u0000#\u0000A\u0004j$\u0000!\u0005 \u0003(\u0002\u0000!\u0002\u0002� \u0002!\u0002 \u0005 \u00026\u0002\u0000A\u0001\u000b\u0004@\u0005\u0000\u000b \u0003 \u0003(\u0002\u0000A\u0001j6\u0002\u0000A\u0000\u001a \u0005(\u0002\u0000\u000b\u000b´���\u0000\u0001\t�#\u0000#\u0000A\u0004j$\u0000!\u0003#\u0000#\u0000A\u0004j$\u0000!\u0004 \u0001!\u0002\u0002� \u0002"\u0002 \u0002(\u0002\u0000!\u0002 \u0003 \u00026\u0002\u0000"\u0002 \u0002(\u0002\u0004!\u0002 \u0004 \u00026\u0002\u0000\u001aA\u0001\u000b\u0004@\u0005\u0000\u000b#\u0000#\u0000A j$\u0000!\u0005 \u0005A\u001cj!\u0006 \u0005A\u0018j!\u0007 \u0005 \u00006\u0002\u0000 \u0005 \u0003(\u0002\u00006\u0002\u001c \u0005#\u0000#\u0000A\fj$\u0000!\b#\u0000#\u0000A\u0004j$\u0000!\n \n \b6\u0002\u0000 \n!\t \bA\n6\u0002\u0000 \b \u00066\u0002\u0004 \b \u00046\u0002\b \b6\u0002\u0018 \u0005\u000b����\u0000\u0001\u0001� \u0001!\u0002\u0002� \u0002\u001aA\u0001\u000b\u0004@\u0005\u0000\u000bA\u0000\u000b¹���\u0000\u0001\r�#\u0000#\u0000A\u0004j$\u0000!\u0002 \u0002A\u00076\u0002\u0000 \u0002!\u0001#\u0000#\u0000A\u0004j$\u0000!\u0003#\u0000#\u0000A\u0004j$\u0000!\u0004#\u0000#\u0000A\u0004j$\u0000!\u0006 \u0006 \u00046\u0002\u0000 \u0006!\u0005#\u0000#\u0000A\u0004j$\u0000!\u0007#\u0000#\u0000A\u0004j$\u0000!\t \t \u00076\u0002\u0000 \t!\b#\u0000#\u0000A\u0004j$\u0000!\n#\u0000#\u0000A\u0004j$\u0000!\f \f \n6\u0002\u0000 \f!\u000b \u0001!\u0000\u0002� \u0000!\u0000 \u0003 \u00006\u0002\u0000A\u0001\u000b\u0004@\u0005\u0000\u000b \u0004A\f6\u0002\u0000 \u0007A\u000b6\u0002\u0000 \nA\t6\u0002\u0000 \n\u001a\u000b'

Can't build using nix

$ nix-build -A js
these derivations will be built:
  /nix/store/7c2v8n8gcb7aa5zzxhw9lrdlkk7mfk8r-ocaml4.05.0-wasm-1.0.drv
  /nix/store/4m70yz4c43b60bx4fy82xanqmja1c2x8-js.drv
building '/nix/store/7c2v8n8gcb7aa5zzxhw9lrdlkk7mfk8r-ocaml4.05.0-wasm-1.0.drv'...
unpacking sources
unpacking source archive /nix/store/3q0nr7q740qxx2xch6hkc61fpk60v5z6-wasm-spec
source root is wasm-spec
/nix/store/15kgcm8hnd99p7plqzx7p4lcr2jni4df-set-source-date-epoch-to-latest.sh: line 13: [: : integer expression expected
patching sources
configuring
no configure script, doing nothing
building
build flags: SHELL=/nix/store/zbhk0jhc9y8hmbcax9nvddbik3rgsary-bash-4.4-p23/bin/bash -C interpreter
make: *** interpreter: No such file or directory.  Stop.
builder for '/nix/store/7c2v8n8gcb7aa5zzxhw9lrdlkk7mfk8r-ocaml4.05.0-wasm-1.0.drv' failed with exit code 2
cannot build derivation '/nix/store/4m70yz4c43b60bx4fy82xanqmja1c2x8-js.drv': 1 dependencies couldn't be built
error: build of '/nix/store/4m70yz4c43b60bx4fy82xanqmja1c2x8-js.drv' failed
$ nix-build -A native
these derivations will be built:
  /nix/store/7c2v8n8gcb7aa5zzxhw9lrdlkk7mfk8r-ocaml4.05.0-wasm-1.0.drv
  /nix/store/7kxnpvfhlcas3rvs5686whz709y5vazd-native.drv
building '/nix/store/7c2v8n8gcb7aa5zzxhw9lrdlkk7mfk8r-ocaml4.05.0-wasm-1.0.drv'...
unpacking sources
unpacking source archive /nix/store/3q0nr7q740qxx2xch6hkc61fpk60v5z6-wasm-spec
source root is wasm-spec
/nix/store/15kgcm8hnd99p7plqzx7p4lcr2jni4df-set-source-date-epoch-to-latest.sh: line 13: [: : integer expression expected
patching sources
configuring
no configure script, doing nothing
building
build flags: SHELL=/nix/store/zbhk0jhc9y8hmbcax9nvddbik3rgsary-bash-4.4-p23/bin/bash -C interpreter
make: *** interpreter: No such file or directory.  Stop.
builder for '/nix/store/7c2v8n8gcb7aa5zzxhw9lrdlkk7mfk8r-ocaml4.05.0-wasm-1.0.drv' failed with exit code 2
cannot build derivation '/nix/store/7kxnpvfhlcas3rvs5686whz709y5vazd-native.drv': 1 dependencies couldn't be built
error: build of '/nix/store/7kxnpvfhlcas3rvs5686whz709y5vazd-native.drv' failed

Pull `dsh` into nix for testing

We now have preliminary support for compiling to Dfinity: We export the start function as "start", and we import console.log32 as log32.

In order to test this I set up this: https://github.com/dfinity-lab/actorscript/blob/master/src/test/run-dfinity/run.sh

But this test is not activated by default yet. It needs dsh in the path, and it needs a version that supports console.log32, e.g. this pull request by @paulyoung: https://github.com/dfinity-lab/hs-hypervisor/pull/32

I do not see a good way of adding a derivation to this package’s .nix file that adds that… help appreciated.

Sharable types in the type checker

It will not be possible to pass values of all types in messages. (Counterexample: mutable arrays). So there needs to be a classification of types into sharable and non-sharable types, and the type checker ought to check that only sharable types appear as arguments (or results) of actor messages.

I would like to start supporting sending actor messages in the compiler and certainly will not support all eventually sharable types from the start.

Instead of throwing an error in the compiler when I come across something I cannot handle yet, I would prefer to declare somewhere what is supported so far, and have the type checker guarantee that the compiler only gets to see code it can handle.

So @rossberg, could you add the infrastructure to do checking of what types are sharable into the typechecker in a way where I can add more and more types as I improve the implementation?

You could start with only allowing nat and int as sharable types, as that would be my first goal.

Error: Unbound constructor Wasm.Memory.Mem8

I'm working around the issues mentioned in https://github.com/dfinity-lab/dev-in-nix/pull/38 by commenting out references to dsh, the hypervisor, and the test phase in default.nix

If I run $ nix-build -A native or $ nix-build -A js I get the following error:

...
File "compile.ml", line 561, characters 71-87:
Error: Unbound constructor Wasm.Memory.Mem8
Command exited with code 2.
make: *** [Makefile:25: main.native] Error 10
make: Leaving directory '/private/tmp/nix-build-native.drv-0/actorscript/src'
builder for '/nix/store/n1f76jkmnia3ia5s6xqn9hqqn7lvl8b0-native.drv' failed with exit code 2
error: build of '/nix/store/n1f76jkmnia3ia5s6xqn9hqqn7lvl8b0-native.drv' failed

Source maps and js_compile_wat/js_compile_wasm

No matter what value I pass for the source map parameter, if I use compileWat/js_compile_wat I never get a source map and if I use compileWasm/js_compile_wasm I always get a source map.

I'm not sure if this is expected but if it is I think we should remove the parameter since it doesn't do anything.

Join-patterns construct in actorscript

We have (or Claudio has) been thinking about supporting join patterns, probably on the system-level. But are there plans to support them first on the language level? The compiler can probably implement them without system support by creating proxy actors.

An example could be if an actor wants to send two messages and wants to process the two corresponding callbacks jointly and only jointly. It's probably much easier to code with some language support, or?

Interpreter and compiler errors

I pushed some code to https://github.com/dfinity-lab/dev/compare/paulyoung/actorspec

Everything in List.as type checks but the interpreter errors when some of the functions are called. They are currently commented out.

actorspec/List.as:11.11-11.13: fatal error, File "interpret.ml", line 524, characters 11-17: Assertion failed

Last environment:
abs = func
cons = func
head = func
head0 = _
head1 = _
ignore = func
length = func
length0 = _
length1 = _
list0 = (0, null)
list1 = (0, (1, null))
list1b = (0, (1, null))
nil = null
print = func
printInt = func
range = class
revrange = class
tail = func
tail0 = _
tail1 = _
tuple0 = (0, null)
tuple0b = (0, null)
tuple1 = (0, (1, null))
xs = (0, null)

Raised at file "interpret.ml", line 524, characters 11-23
Called from file "interpret.ml", line 439, characters 10-25
Called from file "interpret.ml", line 77, characters 8-23
Called from file "interpret.ml", line 81, characters 36-44
Called from file "interpret.ml", line 690, characters 2-18
Called from file "pipeline.ml", line 161, characters 20-54
520   | OptP pat1 ->
521     (match v with
522     | V.Opt v1 -> match_pat pat1 v1
523     | V.Null -> None
524     | _ -> assert false
525     )

When compiling, the definitions alone are problematic:

compile_exp: (ProjE (VarE tuple) 1)
compile_exp: (ProjE (VarE tuple) 0)

Lowering the async type itself

This is a write up of a fruitful discussion with @crusso today in Cambridge.

The current async-translation is CPS-transforming the context of an await, but leaves in the async type, and does not change the type of actor methods. Because the DFINITY execution environment does not support an “async” type, nor are actor messages actually able to return something, a second pass is required to lower the concept of async itself. This issue outlines the transformation.

Recap: The async-CPS-transform.

@crusso’s translation can (I believe) be characterizing as following:

Instead of

  • async (e : t) : async t and
  • await (a : async t) : t,

the code is now expressed in terms of

  • async' (e' : (t -> ()) -> ()) : async t and
  • await' (a : async t, t -> ()) : ()

with the invariant that the continuation passed by async', as well as all calls to await', are in a tail-call positions with regard to the enclosing async' or the enclosing continuation.

The async-lowering

Based on this CPS’ed form, the following transformation should get rid the async type and remove return types from actor messages. I am distinguishing message sending from function invocation here via an explicit send keyword for clarity, but this does not seem to require a separate syntactic form for message sending.

  • We have the following code in the prelude (I am liberal with the syntax here):
    type async t = (t -> ()) -> ();
    func new_async<t> : (async t, t -> ()) {
      var ref : ((t -> ()) + t) = inl (λ _ -> ()); // this assumes we have sum types
      let enqueue (k : t -> ()) : () {
        switch !ref {
          case (inl ks) -> ref := inl (λ x → ks x; k x);
          case (inr x) -> k x
        }
      } in
      let fill (x : t) : () {
        switch !ref {
          case (inl ks) -> ref := inr x; ks x
          case (inr _) -> trap
        }
      } in
      (enqueue, fill)
    } 
    
    This is essentially a future with two states (empty, or filled). Filling executes all queued continuations. Queuing into a filled async will execute the continuation directly. Calling fill twice traps: This is important to protect against misbehaving actors who invoke the callback twice.
  • Type actor { foo(t1,t2) -> async t3; bar(t1,t2) -> () }actor { foo(t1,t2, t3 -> ()) -> (); bar(t1,t2) -> () }
    This step ensures that all actor methods have return type (), because no actor can actually return something.
  • Expression await' a ka(k).
    Simple enough.
  • Expression actor { foo(a,b) : async t { async' e } }actor { foo(a,b,k) { e k } };
    This turns an actor into an actor where all messages return (), and a wrapper object that translates into the language of async objects.
    The translation assumes that the body of an async-returning actor method always has an async block (and not an expression that returns an async. I am not sure what it should mean to have actual code that returns a value of type async t here, and maybe the async should be implicit in an actor method.
  • act.foo : (a, b) -> async t (act is an actor) → func (x : a, y : b) { let (a,fill) = new_async<t>(); in send act.foo(x, y, fill); a }
    (Maybe there is a way of doing this only once for each actor, instead of at each field access. Likely we can optimize a.foo(a,b) to skip the anonymous function.)
  • async' elet (a,fill) = new_async<t>(); in send (func (k : t -> ()) { k e }) (fill); a
    A stand-alone async block turns into a function (which would be lambda-lifted to the enclosing actor and thus become a method), that is immediately called via an asynchronous message call.

After this (type-changing) translation, all actors have methods that do not return, and the async type has disappeared.

Are async’s sharable

They don’t need to be for what we describe here: They are captured in the environment of closures, but that’s fine, as we can do that with mutable references. It is unclear how to share mutable references, and hence async’s, but also unclear whether we need it.

Do we need the async e expression?

Claudio and I were wondering if we can do away with async {e} on the surface level. Instead, would it make sense to say that there is an implicit async around

  • The top-level definitions and
  • The body of each (public) method of an actor
    because these are the code paths that are called asynchronously. What do you think, @rossberg?

Messages with two callbacks

Related to #18, we could would then provide:

func(requestId, address) : async (result?) {
  let (a,fill) =  new_async<t>(); in
  send lookup(requstId, address, func (r) { fill(r?) }, func () { fill(None) });
  a
}

and it would seamlessly integrate into the ActorScript world of asyncs. It would require hand-written wrappers around “foreign” actors, but that is not surprising if other actors don’t follow the ActorScript calling convention.

Suspicious test output

make: Leaving directory '/private/tmp/nix-build-native.drv-0/actorscript/test/run'
make: Entering directory '/private/tmp/nix-build-native.drv-0/actorscript/test/tc-fail'
[check] duplicate-field ✗
[check] prim-in-source ✗
All passed!
make: Leaving directory '/private/tmp/nix-build-native.drv-0/actorscript/test/tc-fail'
make: Entering directory '/private/tmp/nix-build-native.drv-0/actorscript/test/tc-only'
[check] coverage ✗
[check] switch ✗
[check] type-inference ✗
All passed!
make: Leaving directory '/private/tmp/nix-build-native.drv-0/actorscript/test/tc-only'
/nix/store/m12assk99f7381v6yqgssrjyfs1g33rx-native

Is this expected? It looks like tests are failing but the build still succeeds.

Type error with nix-build -A js

+ ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o js_main.cmo js_main.ml
File "js_main.ml", line 5, characters 32-53:
Error: This expression has type string but an expression was expected of type
         Pipeline.stat_env = Typing.env
Command exited with code 2.
make: *** [Makefile:28: js_main.byte] Error 10
builder for '/nix/store/vinydnbw2bfhsk9pp58gpr1ifp21zmhl-js.drv' failed with exit code 2
error: build of '/nix/store/vinydnbw2bfhsk9pp58gpr1ifp21zmhl-js.drv' failed
Full output
$ nix-build -A js
these derivations will be built:
  /nix/store/vinydnbw2bfhsk9pp58gpr1ifp21zmhl-js.drv
building '/nix/store/vinydnbw2bfhsk9pp58gpr1ifp21zmhl-js.drv'...
unpacking sources
unpacking source archive /nix/store/l3hda0dd6gdd2b9s0d5sk7w934qff7jj-src
source root is src
patching sources
configuring
no configure script, doing nothing
building
ocamlbuild -cflags '-w +a-4-27-30-42-44-45 -warn-error +a' -use-ocamlfind -use-menhir -menhir "menhir --infer --dump --explain" -I src -I lib -pkg wasm -pkg num -tags debug -pkg js_of_ocaml -pkg js_of_ocaml-ppx -plugin-tag "package(js_of_ocaml.ocamlbuild)" js_main.byte
Warning: option -plugin-tag(s) has no effect in absence of plugin file "myocamlbuild.ml"
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules js_main.ml > js_main.ml.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules pipeline.mli > pipeline.mli.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules interpret.mli > interpret.mli.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules source.mli > source.mli.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules syntax.ml > syntax.ml.depends
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o source.cmi source.mli
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules type.mli > type.mli.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules con.mli > con.mli.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules env.ml > env.ml.depends
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o env.cmo env.ml
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o con.cmi con.mli
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules value.mli > value.mli.depends
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules lib.mli > lib.mli.depends
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o lib.cmi lib.mli
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o type.cmi type.mli
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o value.cmi value.mli
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o syntax.cmo syntax.ml
ocamlfind ocamldep -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -modules typing.mli > typing.mli.depends
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o interpret.cmi interpret.mli
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o typing.cmi typing.mli
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o pipeline.cmi pipeline.mli
ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o js_main.cmo js_main.ml
+ ocamlfind ocamlc -c -w +a-4-27-30-42-44-45 -warn-error +a -g -package wasm -package js_of_ocaml-ppx -package js_of_ocaml -package num -o js_main.cmo js_main.ml
File "js_main.ml", line 5, characters 32-53:
Error: This expression has type string but an expression was expected of type
         Pipeline.stat_env = Typing.env
Command exited with code 2.
make: *** [Makefile:28: js_main.byte] Error 10
builder for '/nix/store/vinydnbw2bfhsk9pp58gpr1ifp21zmhl-js.drv' failed with exit code 2
error: build of '/nix/store/vinydnbw2bfhsk9pp58gpr1ifp21zmhl-js.drv' failed

Compiling ActorScript to WebAssembly in a browser

My understanding is that we want to be able to do this in isolation (e.g. offline) so I wanted to start a discussion around this in case it hasn’t come up before or needs further discussion.

I’m assuming the ActorScript compiler can be compiled to WebAssembly and then run in a browser.

If not, or if there are challenges with that, what are the alternatives?

Output type information about compiled actors

Please don’t take the above title as the ultimate ask here; I think there are multiple ways to address this particular goal but I think this might not be a bad starting point for a discussion.

We may need to be able to take an actor defined in ActorScript and produce a description of the functions it exports, including their types. This likely needs to done in a programming language other than OCaml.

At first I thought we could just see what is exported from the resulting WebAssembly but we need ActorScript types, not Wasm types.

This describes something I did for my Swift back end for PureScript that we might want to consider:

  1. Tell the PureScript compiler to produce a core JSON dump instead of its regular code generation.
  2. Decode the JSON using a language of choice
  3. Interpret the data and produce the desired output

This is probably the most general approach to solving this but perhaps it could be specialized to exactly the subset of data we need, which is basically exports with type signatures.

Language Server implementation

A growing number of popular editors and other tools support LSP (the Language Server Protocol)

The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, go to definition, find all references and alike into the tool

I think langserver.org summarizes the benefits quite well:

LSP creates the opportunity to reduce the m-times-n complexity problem of providing a high level of support for any programming language in any editor, IDE, or client endpoint to a simpler m-plus-n problem.

For example, instead of the traditional practice of building a Python plugin for VSCode, a Python plugin for Sublime Text, a Python plugin for Vim, a Python plugin for Sourcegraph, and so on, for every language, LSP allows language communities to concentrate their efforts on a single, high performing language server that can provide code completion, hover tooltips, jump-to-definition, find-references, and more, while editor and client communities can concentrate on building a single, high performing, intuitive and idiomatic extension that can communicate with any language server to instantly provide deep language support.

Warning: integer overflow:

~/dfinity/actorscript/src $ make asc.js
ocamlfind query wasm num
/home/jojo/.opam/default/lib/wasm
/home/jojo/.opam/default/lib/num
ocamlbuild -cflags '-w +a-4-27-30-42-44-45 -warn-error +a' -use-ocamlfind -use-menhir -menhir "menhir --infer --dump --explain" -I src -I lib -pkg wasm -pkg num -tags debug -pkg js_of_ocaml -pkg js_of_ocaml-ppx js_main.byte
Finished, 74 targets (71 cached) in 00:00:00.
js_of_ocaml +nat.js js_main.byte -o asc.js
Warning: integer overflow: integer 0x100000000 (4294967296) truncated to 0x0 (0); the generated code might be incorrect.
Warning: integer overflow: integer 0x100000000 (4294967296) truncated to 0x0 (0); the generated code might be incorrect.

Andreas, in de703c2 you introduce the span function with some pretty large constants – larger than 32 bit it seems, and it seems to trip js_of_ocaml.

Do you think using bigints would be the right thing here? (But probably not since you compare that number with the result of Set.cardinal, which is an int.

So maybe treat 32bit like 64bit:

  | Prim Word32 -> Some 0x100000000
  | Prim Word64 -> None  (* for all practical purpuses *)

CompileError: WasmCompile: Wasm decoding failed: expected section length @+13

I've tried to compile various things both in the browser and in node (even just an empty string) and I'm encountering this error:

CompileError: WasmCompile: Wasm decoding failed: expected section length @+13

I'm using js-actorscript which is pointing at c45622c

I can't run the tests because of issues with v8, but I'm surprised that this isn't an issue with the node test:

https://github.com/dfinity-lab/actorscript/blob/c45622c94baf054eeea70f52aae7c28831d212b2/test/node-test.js#L18-L19

Creating an Array of length n and type T

At the moment it’s not possible to implement functions like concat and map for Array because we can’t create a new array of a certain length with a desired type.

I imagine it would be used like this:

func mapArray<A, B>(f : A -> B, xs : A[]) : B[] {
  let ys : var B[] = newArray<B>(xs.len());
  for (i in xs.keys()) {
    y[i] := f(x[i]);
  };
  ys;
};

We’d also need a way to convert var T[] to T[]

async failures in the interpreter

I am testing the -a -A flags on the test cases in test/run, and found these issues:

~/dfinity/actorscript/test/run $ ../../src/asc -r -a -A actors.as 
actors.as:2.50-2.51: fatal error, Invalid_argument("Value.as_tup")

Last environment:
Array_init = func
Array_tabulate = func
abs = func
ignore = func
n = 10
new_async = func
print = func
printInt = func
range = class
revrange = class
self = {tac_msg = func; tic_msg = func}
tac_msg = func
tic_msg = func
tictac_actor = {tac_msg = func; tic_msg = func}
tictac_actor_async = {tac_msg_async = func; tic_msg_async = func}
tictac_async = {tac_async = func; tic_async = func}

Raised at file "nat.ml", line 524, characters 55-59
~/dfinity/actorscript/test/run $ ../../src/asc -r -a -A bank-example.as 
(unknown location): fatal error, Invalid_argument("Value.as_func")

Last environment:
 = func
$11 = func
$13 = func
$49 = func
$51 = (func, func)
$52 = func
$53 = func
Account = class
Array_init = func
Array_tabulate = func
Bank = class
Issuer = class
a1@2 = _
a2@3 = _
abs = func
b = {getIssuer = func; getReserve = func; issuer = {hasIssued = func}; reserve = {balance = 100; credit = func; getBalance = func; isCompatible = func; join = func; split = func}}
ignore = func
main = _
new_async = func
print = func
printInt = func
range = class
reserve@1 = _
revrange = class
show = func
test = func
transfer = func

Raised at file "nat.ml", line 524, characters 55-59

Error with nix-build -A native

$ nix-build -A native
these derivations will be built:
  /nix/store/qv1c5iqpxmw9z3ivccbr1afxx65v65ip-native.drv
building '/nix/store/qv1c5iqpxmw9z3ivccbr1afxx65v65ip-native.drv'...
unpacking sources
unpacking source archive /nix/store/l3hda0dd6gdd2b9s0d5sk7w934qff7jj-src
source root is src
patching sources
configuring
no configure script, doing nothing
building
ocamlfind query wasm num
/nix/store/h0lcai5q3w46q0h513608v3prnlm6jvb-ocaml4.05.0-wasm-1.0/lib/ocaml/4.05.0/site-lib/wasm
/nix/store/650m9ic99ka8mgli2ykq1vgkw0mfj1id-ocaml-findlib-1.8.0/lib/ocaml/4.05.0/site-lib/num
ocamlbuild -cflags '-w +a-4-27-30-42-44-45 -warn-error +a' -use-ocamlfind -use-menhir -menhir "menhir --infer --dump --explain" -I src -I lib -pkg wasm -pkg num -tags debug main.native
mv main.native asc
installing
post-installation fixup
/nix/store/5im29qx9rni3w8l3j8i2yr22sin55kxv-cctools-binutils-darwin/bin/strip is /nix/store/5im29qx9rni3w8l3j8i2yr22sin55kxv-cctools-binutils-darwin/bin/strip
stripping (with command /nix/store/5im29qx9rni3w8l3j8i2yr22sin55kxv-cctools-binutils-darwin/bin/strip and flags -S) in /nix/store/d5cgizrl8cfqbwh3jw30p8fg43l8zhk2-native/bin
patching script interpreter paths in /nix/store/d5cgizrl8cfqbwh3jw30p8fg43l8zhk2-native
running install tests
/nix/store/d5cgizrl8cfqbwh3jw30p8fg43l8zhk2-native/bin/asc: unknown option '--version'.
Usage: asc [option] [file ...]
  -       run interactively (default if no files given)
  -t      trace phases
  -d      debug, trace calls
  -p      set print depth
  -v      show version
  -help   Display this list of options
  --help  Display this list of options
builder for '/nix/store/qv1c5iqpxmw9z3ivccbr1afxx65v65ip-native.drv' failed with exit code 2
error: build of '/nix/store/qv1c5iqpxmw9z3ivccbr1afxx65v65ip-native.drv' failed

Error in type checker when comparing Ints

In trying out #62 I had written the following function:

func concatArray<A>(xs : A[], ys : A[]) : A[] {
  switch(xs.len(), ys.len()) {
    case (0, 0) { []; };
    case (0, _) { ys; };
    case (_, 0) { xs; };
    case (xsLen, ysLen) {
      Array_tabulate<A>(xsLen + ysLen, func (i : Nat) : A {
        if (i < xsLen) {
          xs[i];
        } else {
          ys[i - xsLen];
        };
      });
    };
  };
};

This errors when attempting to type check:

$ asc Array.as --check
(unknown location): internal error, Invalid_argument("compare: abstract value")

Last environment:

Raised by primitive operation at file "coverage.ml", line 93, characters 7-13
Called from file "coverage.ml", line 90, characters 6-36
Called from file "coverage.ml", line 165, characters 19-73
Called from file "typing.ml", line 617, characters 11-51
Called from file "typing.ml", line 582, characters 2-23
Called from file "typing.ml", line 1024, characters 18-37
Called from file "typing.ml", line 1004, characters 2-56
Called from file "typing.ml", line 609, characters 11-42
Called from file "typing.ml", line 582, characters 2-23
Called from file "typing.ml", line 976, characters 6-45
Called from file "typing.ml", line 1049, characters 16-42
Called from file "typing.ml", line 22, characters 6-9
Called from file "typing.ml" (inlined), line 28, characters 6-27
Called from file "typing.ml", line 1017, characters 16-59
Called from file "typing.ml", line 22, characters 6-9
Called from file "typing.ml" (inlined), line 28, characters 6-27
Called from file "typing.ml", line 1018, characters 16-66
Called from file "typing.ml", line 22, characters 6-9
Called from file "typing.ml" (inlined), line 28, characters 6-27
Called from file "typing.ml", line 1018, characters 16-66
Called from file "typing.ml", line 22, characters 6-9
Called from file "typing.ml" (inlined), line 28, characters 6-27
Called from file "typing.ml", line 1018, characters 16-66
Called from file "typing.ml", line 1004, characters 2-56
Called from file "typing.ml" (inlined), line 1219, characters 2-40
Called from file "pipeline.ml", line 144, characters 43-70
Called from file "pipeline.ml", line 115, characters 37-52
Called from file "pipeline.ml", line 138, characters 10-41
Called from file "main.ml", line 74, characters 38-72
Called from file "main.ml", line 97, characters 4-23

I have run into this before and forgot to report it. My workaround has been to compare boolean values instead:

func concatArray<A>(xs : A[], ys : A[]) : A[] {
  let xsLen = xs.len();
  let ysLen = ys.len();
  switch(xsLen > 0, ysLen > 0) {
    case (false, false) { []; };
    case (false, _) { ys; };
    case (_, false) { xs; };
    case (true, true) {
      Array_tabulate<A>(xsLen + ysLen, func (i : Nat) : A {
        if (i < xsLen) {
          xs[i];
        } else {
          ys[i - xsLen];
        };
      });
    };
  };
};

This now type checks and runs without issue:

let nats = [ 1, 2, 3, 4 ];
let moreNats = concatArray<Nat>(nats, nats);

for (nat in moreNats.vals()) {
  printInt(nat);
};
$ asc Array.as -r
printInt(1)
printInt(2)
printInt(3)
printInt(4)
printInt(1)
printInt(2)
printInt(3)
printInt(4)

Static/class members

I can emulate this really well already by doing the following:

type FooClass = {
  init : Int -> FooInstance;
  someStaticMethod : () -> Text;
};

type FooInstance = {
  someInstanceMethod : () -> Int;
};

let Foo : FooClass = new Self {
  init = func (x : Int) : FooInstance {
    new self {
      someInstanceMethod = func () : Int {
        x;
      };
    };
  };

  someStaticMethod = func () : Text {
    "Some text";
  };
};

print(Foo.someStaticMethod() # "\n");
let foo = Foo.init(42);
printInt(foo.someInstanceMethod());

This also allows me to reference class members inside of the main block using Self and instance members via self.

However, it isn’t transparent (mainly) because the instance type FooInstance and binding name Foo aren’t allowed to be the same and it leaks into other types, e.g FooInstance[] instead of Foo[]

Is there any possibility of doing either of the following?

  1. Distinguish between types and values so that type Foo = {}; and let Foo = {}; are allowed to coexist.
  2. Support static/class members natively via the class keyword

The latter has the benefit of dropping init methods but I’d also request something like Self and self for class in that case. Perhaps self would be sufficient if the class can be referenced within itself by name, e.g. Foo.someStaticMember

Typechecker - bug in synthesizing types of RetE expressions when assumed return type is T.Pre?

func f(i:Int) : Int { i; }; let d = async { return f(4); };
crashes the compiler:
[crusso@LAPTOP-CT4C97Q0 async]$ asc -r repro.as
prelude:34.26-34.38: internal error, File "typing.ml", line 590, characters 2-8: Assertion failed

Last environment:
abs = func
ignore = func
print = func
printInt = func
range = class
revrange = class

Raised at file "typing.ml", line 590, characters 2-21
Called from file "typing.ml", line 521, characters 8-28
Called from file "typing.ml", line 302, characters 10-28
Called from file "typing.ml", line 290, characters 13-36
Called from file "typing.ml", line 955, characters 4-21
Called from file "typing.ml", line 940, characters 10-59
Called from file "typing.ml", line 430, characters 24-51
Called from file "typing.ml", line 302, characters 10-28
Called from file "typing.ml", line 290, characters 13-36
Called from file "typing.ml", line 529, characters 12-31
Called from file "typing.ml", line 302, characters 10-28
Called from file "typing.ml", line 290, characters 13-36
Called from file "typing.ml", line 957, characters 31-50
Called from file "typing.ml", line 1040, characters 16-42
Called from file "typing.ml", line 996, characters 2-56
Called from file "pipeline.ml", line 134, characters 43-70
Called from file "pipeline.ml", line 105, characters 37-52
Called from file "pipeline.ml", line 128, characters 10-41
Called from file "pipeline.ml", line 166, characters 8-23
Called from file "pipeline.ml", line 228, characters 15-33
Called from file "main.ml", line 56, characters 38-65
Called from file "main.ml", line 73, characters 4-23
[crusso@LAPTOP-CT4C97Q0 async]$

How does compilation to DFINITY actors differ?

Currently, the (prototype) WebAssembly backend creates a .wasm (well, a .wat) file that can be run in a vanilla WebAssembly environment (e.g. the reference interpreter wasm, or a browser).

But the execution module on the DFINITY blockchain is different in various ways.

For example: wasm executes the (start) function of the module upon module initialization. This is used for the top-level declarations of the .as file.

But our hypervisor does not seem to support that.
How does this affect our compilation? It seems to me as if we can only produce a “static module”, i.e. one where nothing need to be executed at start-up. So maybe we should only support (besides things like type declarations and maybe immutable value declarations)

  • .as files where all top-level declarations are function definitions, or
  • .as files with only one top-level declaration, which defines an actor, or
  • .as files with many (static) declarations, but the last one needs to be an actor, which represents the “main” actor of the program and its entry point.

I assume we want to use the private flag on object fields in the ActorScript syntax to distinguish between exported functions and non-exported functions. But this flag only exists for object fields… which supports the “main actor” approach.

Do we want to allow the users to explicitly switch to DFINITY-compatible compilation, which has more ramifications (e.g. the use of a root actor)? This way they can still write “normal” wasm files, and use them elsewhere? Or should the presence of an actor indicate that we are targetting DFINITY?

Interoperability with non-AS actors

This may be mis-titled so please rename if necessary.

In a discussion with Dom today about goals for an online environment for writing actors, the following scenario came up.

For the sake of simplicity, I may change or omit certain details. I will also use pseudo-code.


Consider a Profile actor that already exists on the internet computer. It exports a function named setStatus which takes a string and returns void.

The goal is for users to be able to call profile.setStatus(“Hello World”) in their own actors.

Do we currently have any specification or syntax defined around imports of this nature?

My interpretation of what Dom envisions is something like the following:

// JavaScript-like
import Profile from <uuid> <capability> <source>
-- PureScript-like
foreign import Profile <uuid> <capability> <source>

This describes a module being imported that must be instantiated before functions can be called.

In these examples, uuid, capability, and source are known.

What Dom actually proposed was a “meta” file with a DSL for describing functions that would be available to ActorScript. I’m unclear on whether that’s meant to be at a file or project level but I think the intention was for there to still be explicit import statements like import Profile or similar.


I’m interested to hear if this has been discussed already, and/or what the current thinking is.

Fix intra-actor function calls

Local calls to functions should be synchronise according to the current design. In the interpreter, currently the inner environment stores the same async closures as the actor object.

File ordering

At the moment I have some bash scripts in which I enumerate the files I want to pass to asc in an order that ensures things are defined before I try to use them.

I'm assuming that this won't always be the case but that it also may not be of high priority right now, so wanted to make sure it was being tracked somewhere.

Type-level union?

Given something like the following:

type Foo = { foo : Text };
type Bar = { bar : Text };

Is it possible to represent the union of Foo and Bar at the type level, in an ad hoc manner and without defining FooAndBar?

type FooAndBar = { foo : Text, bar : Text };

Dependency graph/deployment order

Today I've been trying to understand more about what might happen when someone chooses to deploy an application to the network.

After looking at the dsh demo I wondered if any decisions had been made regarding ordering when deploying modules written in ActorScript.

It seems like there might be a couple of options:

  1. Users must write code themselves and interact with the root actor (much like the dsh demo)
  2. Perform static analysis on ActorScript code and figure out the order in which actors need to exist on chain

I imagine we might start with the first and some day may have the second?

Error with bank example/test

I'm seeing an error when trying to use the code from the bank test:

screen shot 2018-09-28 at 11 48 04 am

It seems to be because of the credit method:

screen shot 2018-09-28 at 11 48 30 am

Commenting out the implementation doesn't help:

screen shot 2018-09-28 at 11 59 28 am

Neither does making the method public:

screen shot 2018-09-28 at 12 00 18 pm

Report multiple type errors

@rossberg, the web IDE is shaping up, thanks to @paulyoung, and it already highlights type errors reported by our type checker. But of course this would be more useful if we could report as many type errors as possible.

Do you have a design in mind? Would you still use exceptions to achieve that (and maybe have a combinator that evaluates two expressions, catches exceptions in both, and combines the type errors)? Or would you rather switch to a monadic/applicative type for the type checker that accumulates type errors and warnings explicitly?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.