0.1.0 • Published 6 years ago

jaem v0.1.0

Weekly downloads
1
License
BSD-3-Clause
Repository
github
Last release
6 years ago

Jaem : an Erlang Flavoured Javascript runtime

Jaem is a minial js kernel thrives to bring erlang sementics and UBF to the js world. If you know erlang, you've already known how to use jaem.

Highlights:

  1. minimal: single file 1K loc, no compilation, put it in your tag and go

  2. atom literal: _`foo`

  3. immutable erlang data structures: list, tuple, and binary

  4. term conversions including term_to_binary/1 and binary_to_term/1 which are implemented using Joe's beautiful UBF with 2 tweaks

  5. and yes we got actor, send spawn receive and friends

  6. code server and its own dyamic module system that looks and feels like erlang(potential for hotcode loading)

  7. we got dets in kernel for persistent storage and code caching(a api wrapper over idb 2.0)

Motivation

UI is important, visual cortex takes big portion of animal brain is a proof of it. But the way we write UI is so awkward and painful, many have discovered the actor model from erlang colud provide a better option.

Another major force from the web community has pushed this component based UI for many years. But Today Web component is landed on every browsers now, doing UI using actor model is first class citizen in modern browsers.

On the other hand, Erlang is beautiful and making programmer's life easier and enjoyable. I think the most important reason it's not widely used is because it doesn't provide a way to write UI(forget about wxWidget). With the new features landed in javascript recently years, bring erlang sementic to the web can be done nicely, then this situation can be improved.

one project has been doing this, but their implementation has lots places can be improved to be more erlang like(eg. receive expression can return value just like erlang) and they are not provide the whole solution like(code server dets,and term_conversion) so I try to do it my way.

Principle

if Nodejs is taking the javascript to server, Jaem is doing the opposite. It brings the language sementic on server(erlang) to the browsers. Although many attempts have done so, but they are too big and intrusive.

The reason I emphasise on sementic not the whole language including syntax is because I want a layered solution. Although Jaem could be a compliation target for core erlang, if that happens, other languages on Beam like lfe and elixir could benefit from it, but the first step is to provide a subset of vanilla es7 base language which is can be written by hand, the benefit is the excellent tooling in browsers like web inspector and debugger can be used dirrectly.

Syntax

Jaem is written in and for a small subset of es7 called EFJ(Erlang flavoured Javascript), name follows convention of Robert's brainchild LFE. It has only functional style construct just like in erlang, no class, no prototype chain, no this, no new, basically the original scheme that Brendan Eich was trying to bring to the web with modern functional js features and syntax, thanks Mr Eich.

Atom

foo %erlang
_`foo` /* EFJ */

implementation: _ is a global function for es6 symbol creation and es6 tagged template string allow we use it without parenthesis. so EFJ atom is es6 global symbol. es6 is good, but you may know symbol has can't be structure cloned cross web worker and idb, we solved it by not using structure clone at all, we because god gave us UBF and transferable

List

[a|[]] % Erlang cons cell
cons(_`a`, null) /* EFJ cons cell */

o(_`a`, null) /* EFJ: cons cell shorthand */
[a,b] % Erlang list
list(_`a`, _`b`) /* EFJ list*/

i(_`a`, _`b`) /* EFJ list shorthand */
[A, B] = [a, b] % Erlang pmatch
const {h: a, t: b} = list(_`a`, _`b`) /* EFJ pmatch */
const {h, t} = list(_`a`, _`b`) /* EFJ pmatch using default varible h, t */
hd(L) % Erlang
tl(L)
l.h /* EFJ * /
l.t 

implementation: cons cell is created using Object.freeze(Object.create(null, x)), so there is no prototype chain created and it's immutable by freeze.

EFJ using es6 destructing assigment for pmatch.

notice EFJ doesn't enforce UpperCase for variable, I personally like lowercase for EFJ

Tuple

{a, b} % Erlang tuple
tuple(_`a`, _`b`) /* EFJ tuple */
u(_`a`, _`b`) /* EFJ tuple shorthand */
{A, B} = {a, b} % Erlang tuple pmatch
const [a, b] = u(_`a`, `b`) /* EFJ tuple pmatch */

implementation: tuple in EFJ is just freeze() array

Spawn

spawn(F) % Erlang 
spawn(Node, F)
spawn(M, F, A)
spawn(Node, M, F, A)

spawn_link(F)
...
spawn_monitor(F)
...
spawn(f) /* EFJ */
spawn(node, f) /* wip */
spawn(m, f, a)
spawn(node, m, f, a) /* wip */

spawn_link(f)
spawn_monitor(f)

implementation: create a sealed process data structure and enqueue to a internal run loop trampoline by promise microtask

Send

Pid ! a % Erlang
send(pid, _`a`) /* EFJ */
erlang:send_after(100, pid, a) % Erlang send with a timeout
send_after(100, pid, _`a`) /* EFJ send with a timeout */

implementation: send is asynchronous, it put message to the process's new mailbox and put it in the run queue

Receive

receive % Erlang single clause receive
  a -> true
end
receive( /* EFJ single clause receive */
  _`a`,
  () => true
)
receive % Erlang receive with a timeout
  a -> true
after
  100 -> false
end
receive( /* EFJ receive with a timeout */
  _`a`,
  () => true,
  u(_`after`, 100),
  () => false
)
X = receive %Erlang receive return a value
  a -> true
end
const x = await receive( /* EFJ receive return a value */
  _`a`,
  () => true
)
receive /* Erlang receive matching a tuple with a varible
  {A, b} -> A
 end
receive( /* EFJ receive matching a tuple with a varible */
  [a, _`b`],
  (a, b) => a
)
receive /* Erlang receive multiple clause
  {A, b} -> A;
  [H | T] -> H
 end
receive( /* EFJ receive matching a tuple with a varible */
  [a, _`b`],
  (a, b) => a,
  {h, t},
  (h, t) => h
)
receive /* Erlang receive with a guard
  A when A =:= 1 -> A;
 end
receive( /* EFJ receive with a guard (sort of) */
  a => {
    if(a === 1)
      return a
  },
  a => a
)

implementation: save the current continuation with es6 promise and mailbox matching rules faithfully mimics the erlang counterpart.

module

-module(m1). % simple erlang module
-export([foo/0]).
foo() -> ok.
module("m1") /* simple EFJ module */
const foo => () => _`ok`
export({foo})
-module(m1). % erlang module dynamic calling other module
-export([foo/0]).
foo() -> m2:bar().
module("m1") /* EFJ module dynamically calling other module */
const m2 = await imports("m2") 
const foo = () => m2.bar()
exports({foo})
-module(m1). % erlang import function from other module
-import(m2, [bar/0]).
-export([foo/0]).
foo() -> bar().
module("m1") /* EFJ import function from other module */
const {bar} = await imports("m2")
const foo = () => m2.bar()
exports({foo})

implementation: every module is wrapped in an async function by code server runtime loader.

To achieve best performance, the code server first try to find if there is a already loaded module in a internal es6 Map object. If there isn't, code server then try to find whether the code binary is in dets table(an idb data store), if there still isn't, it finally fetch the code binary from remote server using UBF as a communication protocol over websocket

0.1.0

6 years ago

0.0.5

6 years ago

0.0.1

6 years ago