Urlang is JavaScript with a sane syntax

Package link: urlang

  1. raco pkg install urlang
  2. Enter some Urlang (check the examples)
  3. Hit run (Racket Mode, DrRacket, etc.)

Fun!

Examples at urlang/urlang-examples at master · soegaard/urlang · GitHub

Space invaders!

https://github.com/soegaard/urlang/tree/master/urlang-examples/space-invaders

Try it here:
http://soegaard.github.io/urlang/space-invaders.html

1 Like

I should know this but... can you compare this to RacketScript?

1 Like

Urlang is JavaScript; but nice syntax :slight_smile: and you can put it straight into Emacs or DrRacket, hit run, and it pops up in your browser. (That felt really cool)

RacketScript lets you do this:

#lang rhombus

import:
 racketscript/base

base.displayln("Hello")

to get

$ node js-build/modules/hello.rkt.js 
Hello

I'm wondering if there are situations where you would use RacketScript and Urlang together?

@jbclements

Hi,

The text became longer than I anticipated, but there were a lot of ground to cover.

The core Urlang language more or less translates constructs from Urlang forms directly to JavaScript forms. In particular Urlang does not introduce new data types like symbols.

As a trivial example

(var [x 42] [y 43])

is translated to

var x = 42,
    y = 43;

There are a few differences though:

  • #f is the only falsy value
  • functions allow default arguments
  • functions don't require return - the value returned is the value of the last expression in the body
  • let expressions (JavaScript let is a statement)
  • identifiers follow Racket syntax (i.e. - and ? allowed)

There are a few important twists though:

  • unbound variable references are detected at compile time
  • users can define macros to extend Urlang

The first twist: If Urlang detects a reference to an unbound
identifier, DrRacket or racket-mode will highlight the offending
identifier as usual. This has two use cases: One, if Urlang is used
directly to write JavaScript, then fixing the errors become
faster. Two, consider the case where Urlang is used as the output
language for a compiler. If the unbound identifier were inserted by a
compiler pass, then the identifier highlighted will be the one in the
compiler source that caused the problem. This is a big deal: compiling
directly to JavaScript would have postponed detecting of the error to
runtime in the browser. And going from the identifier in the output to
the original is not trivial.

The second twist: Users can define macros. This is feature is intended
to be used, when Urlang is used directly (i.e. not as a compiler
back-end language). Anyone used to Racket attempting to write a
JavaScript program quickly discovers that JavaScript is limited in its
selection of forms. Especilly statements are favored over expressions.

In particular, in Racket if, cond and case are all expressions.
JavaScript does have a ternary conditional (e ? e1 : e2) but that's it.
With macros available it is easy to fix this.

In urlang/extra I have defined forms that work like their Racket
counter parts (i.e. they are expressions): ​begin0, when, unless,
cond, case, letrec, let*. If needed, there are statement versions
as well: swhen, sunless and scond.

In urlang/for I have defined a for macro that feels like Racket for.

When these expressions are available Urlang begins to feel
more like Racket than JavaScript.

As an example. This program will produce an array of factorials.

(urlang
 (urmodule main
   (define (fact n)
     (if (= n 0)
         1
         (* n (fact (- n 1)))))

   (for/array ([x in-range 1 5])
     (fact x))))

Running this Racket program will write the JavaScript output to "main.js".
If the parameter current-urlang-run? is set to #t, then the JavaScript
runtime node will be invoked. And the output will be displayed directly
in the DrRacket/racket-mode repl.

In this particular I see "[ 1, 2, 6, 24 ]\n" in the repl.

The original plan was to write a Racket to JavaScript compiler.
I decided to split the project in two parts. The first part Urlang
is simply JavaScript with S-expressions syntax and macros as described
above. The second part was a compiler from Racket to Urlang.
It turned out that I was content using Urlang directly.

When Urlang development started browsers only supported ES5, so the
compilation target was ES5. However ES6 has now become standard,
so recently I have added support for some ES6 features in particular
ES6 modules, but also operators like spread (the ... operator).

The second part of the project (the Racket to Urlang) compiler is not done.
I have implemented a compiler for top-level programs. I.e. no support
for Racket modules. The next step would be to look at linklets.
The runtime for the Racket compiler is implemented in Urlang. It represents
Racket values as arrays, whose first element contains a "tag" that
describes the type. This differs from most compiler targeting JavaScript,
that represent foreign values as objects. The hope was that arrays
would be fast to allocate and that array references with known indices
would be optimized by even simple JavaScript engines.

The compiler compiles all Racket tail calls to JavaScript tail calls.
If only ES6 had supported TCO as promised, then ...

Personally I am using Urlang directly to write a web site where high school
students can solve various math problems. For the front-end I chose React (Hooks).
React introduces a virtual DOM representing html elements. When the
program changes the virtual DOM, React will update the real DOM
based on the changes made to the virtual dom. The trick is that React
compares the old virtual dom with the new one, so it can restrict updates
of the real DOM to the parts that changed. Modern React (React Hooks)
represent user components as functions. It feels Rackety.

One complication of using React is that all React tutorials use
so-called jsx expressions to generate html-elements.

This

const element = <h1>Hello</h1>;

doesn't work in vanilla JavaScript, so JavaScripters run their
programs with jsx expression through a pre-processor that
translates <h1>Hello</h1> into JavaScript expressions that
produce a value that represents a h1-header in the virtual DOM.

In order to escape back to JavaScript, one can use braces {}
inside a jsx expression:

const element = <h1>The sum 1+2 is {1+2}.</h1>;

In Urlang I could similar expressions as a macro without changing
the core and without added an extra build step.

Instead of jsx expressions, I have urx expressions.
Instead of {} to escape back, I use `ur.

It looks like:

(var [element @urx[@h1{Hello}]])
(var [element @urx[@h1{The sum 1+2 is @ur(+ 1 2).}]])

Note: If the html tag is written with lower case, it must be one of the
tags in the html spec. Spelling errors are detected at compile time.
User defined tags are written with upper case.

If there is interest, I can produce a few examples of using React with Urlang.

In short: I see Urlang as JavaScript with macros and a nicer syntax.
If JavaScript had macros, I would not have written Urlang.

Guide to the Urlang sources:

  • urlang/main.rkt Urlang->JS compiler using NanoPass
  • urlang/extra.rkt implements cond, if, when, ... as Urlang macros
  • urlang/for.rkt implements for loops as an Urlang macro
  • urlang/react/urx.rkt urx expressions
  • compiler-rjs/compiler.rkt the Racket->Urlang compiler
  • compiler-rjs/runtime.rkt

Github:

8 Likes

I’ve made my post a wiki so members (@soegaard ) can correct any errors I have made. :grinning:

I added a link to a page, where people can try the Space Invaders example.

1 Like

Thank you . And thank you for creating Urlang. I'm really enjoying it.

1 Like