Code Monkey home page Code Monkey logo

urlang's Introduction

URLANG

Urlang is JavaScript with a sane syntax

Urlang is a language designed to allow straightforward translation to JavaScript. Think of Urlang as JavaScript with sane syntax and JavaScript semantics. JavaScript in this context is short for ECMAScript 5 in strict mode.

Although the constructs of Urlang and JavaScript map almost one-to-one, a little sugar was added:

  • function definitions allow default arguments
  • let expressions

Even though the syntax of Urlang is Racket-like, remember that the semantics is standard JavaScript. This means in particular that tail calls build context.

Examples

The following examples are compiled using the urlang form.

(urlang <module> ...)

The urlang form compiles the modules. The result of compiling a module is saved a file whose path is the module-name with .js added.

The urlang form is controlled by the following parameters:

(current-urlang-run?                           #t)  ; compile and run (using node)
(current-urlang-echo?                          #t)  ; print JavaScript to screen
(current-urlang-console.log-module-level-expr? #t)  ; use conole.log to print module-level exprs

Example (factorial)

 > (urlang
     (urmodule fact                                           ; module name
       (export fact)                                          ; fact is exported
       (import + - * = displayln ref console)
       (define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))
       (fact 5)))

The generated JavaScript:

 "use strict";
 function fact(n){return (((n===0)===false)?(n*(fact((n-1)))):1);};
 console.log((fact(5)));
 exports.fact=fact;

The output from Node:

 "120\n"

Example (cond-macro and array)

Urlang macro transformers receive and produce standard Racket syntax objects. This implies that standard tools such as syntax-parse are available.

Consider an Urlang version of cond:

SYNTAX (cond [e0 e1 e2 ...] ... [else en]), 
   like Racket cond except there is no new scope 

The urlang macro transformer is an standard (phase 0) Racket function.

  (define-urlang-macro cond
    (λ (stx)   
      (syntax-parse stx
        [(_cond [else e0:Expr e:Expr ...])
         #'(begin e0 e ...)]
        [(_cond [e0 e1 e2 ...] clause ...)
         (syntax/loc stx
           (if e0 (begin e1 e2 ...) (cond clause ...)))]
        [(_cond)
         (raise-syntax-error 'cond "expected an else clause" stx)])))

The macro can now we be used:

> (urlang
    (urmodule sum-example
      (define (even? x) (=== (% x 2) 0))
      (var (sum 0) x (a (array 1 2 3 4 5)) (i 0) (n a.length))
      (while (< i n)
        (:= x (ref a i))
        (cond
          [(even? x)  (+= sum (ref a i))]
          [else       "skip"])
        (+= i 1))
      sum))

The generated JavaScript:

"use strict";
function even_p(x){return ((x%2)===0);};
var sum=0,x,a=[1,2,3,4,5],i=0,n=a.length;
while((i<n)){(x=a[i]);(((even_p(x))===false)?"skip":(sum+=a[i]));(i+=1);};
console.log(sum);

The output from Node:

"6\n"

Overview

The heart of the system is a compiler written using the Nanopass compiler Framework. The compiler is exported as a function

compile : urlang-module -> JavaScript

that compiles an urlang module and produces JavaScript, that can be evaluated by the Node.js platform (or be embedded in a web page).

The Urlang module to be compiled can be represented

  1. as a syntax object
  2. as a Nanopass structure (representing an Lurlang program)

Use 1) to program in Urlang directly.

Use 2) if you intend to use Urlang as a compiler backend.

[Note: Nanopass is a framework for implementing compilers.]

The intended use of Urlang is to use 1) to write (generate) a Racket runtime in JavaScript. The middle-end of the Racket-to-JavaScript compiler will produce output as Nanopass structures, so 2) will be used as the backend for the Racket-to-JavaScript compiler.

Internally the function expand

expand : syntax -> LUrlang

will parse and expand its input and produce an LUrlang representation.

Note that expand allows the user to extend the input language using define-urlang-macro. An Urlang macro is a syntax to syntax transformation implemented as a normal Racket function. This allow you to use all of the standard Racket macro machinery.

Main functions:

expand : syntax -> Lurlang
    expand the input and produce a fully expanded Urlang program
    represented as a Lurlang structure
    
compile : syntax ->
    Expand and compile. The output is written to standard out.
    
eval : syntax -> value
    expand, compile and run the input (an Urlang module represented as a syntax object)
    Running means that `node` is used to run the generated JavaScript.

Having Urlang as a #lang language allows

  • macros (using full Racket at compile time)
  • export of defined names
  • easier testing

In the grammar below:

  • x stands for a non-keyword identifier
  • f stands for an identifier defined as a function
 <module>            ::= (urmodule <module-name> <module-path> <module-level-form> ...)

 <module-level-form> ::= <export> | <import> | <definition> | <statement> 
 <export>            ::= (export x ...)
 <import>            ::= (import x ...)
 <definition>        ::= (define (f <formal> ...) <body>)
                      |  (define x <expr>)
 <formal>           ::= x | [x <expr>]

 <statement>         ::= <var-decl> | <block> | <while> | <do-while> | <if> | <break> | <expr> 
 <var-decl>          ::= (var <var-binding> ...)
 <block>             ::= (block <statement> ...)
 <var-binding>       ::= x | (x e)
 <while>             ::= (while <expr> <statement> ...)
 <do-while>          ::= (do-while <expr> <statement> ...)
 <if>                ::= (sif <expr> <statement> <statement>)
 <break>             ::= (break) | (break label)

 <body>              ::= <statement> ... <expr>

 <expr>              ::= <datum>   | <reference> | <application> | <sequence>
                      |  <ternary> | <assignment> | <let> | <lambda> | <dot>
 <ternary>           ::= (if <expr> <expr> <expr>)
 <reference>         ::= x
 <application>       ::= (<expr> <expr> ...)
 <sequence>          ::= (begin <expr> ...)
 <assignment>        ::= (:= x <expr>)
 <let>               ::= (let ((x <expr>) ...) <statement> ... <expr>)
 <lambda>            ::= (lambda (<formal> ...) <body>)

 <keyword>           ::= define | begin | urmodule | if | := | ...se code...

 <datum>             ::= <fixnum> | <string> | #t | #f

 <identifier>     an identifier that is not a keyword
 <fixnum>         an integer between -2^53 and 2^53
 <module-name>    a symbol or string

NOTES

Some application are special cases:

(ref e0 e1)     becomes  e0[e1]
(ref e0 "str")  becomes  e0.str    
(array e ...)   becomes  [e,...]

Property access with dot notation is rewritten to use bracket syntax in the parser.

Example: object.property becomes object["property"]

SEMANTICS

(if e0 e1 e2)

If e0 evaluates to value strictly equal to false, then e2 otherwise e1. Note: The JavaScript becomes ((e0===false) ? e2 : e1)

(var x (y 3))

Binds x to undefined and y to 3.

urlang's People

Contributors

danprager avatar jackrosenthal avatar leifandersen avatar samth avatar soegaard 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

urlang's Issues

Failure while building urlang docs

(Working from commit: b6ceb75)

urlang fails to build if the html-parsing package isn't installed:

raco setup: error: during making for <pkgs>/urlang/urlang/html
raco setup:   standard-module-name-resolver: collection not found
raco setup:     for module path: html-parsing
raco setup:     collection: "html-parsing"
raco setup:     in collection directories:
raco setup:      /Users/jessealama/Library/Racket/7.1/collects
raco setup:      /Applications/Racket v7.1/collects
raco setup:      ... [186 additional linked and package directories]
raco setup:     compiling: <pkgs>/urlang/urlang/html/to-at-exp.rkt

There are a few more errors like this, all pointing to the absence of html-parsing.

Installing html-parsing does the job. It's enough to add html-parsing to build-deps in info.rkt.

Detect duplicate names in object literals

The object literal

(object [foo 1] [foo 2])

will generate {foo: 1, foo:2}. It evaluates to the object {foo: 2}.

A warning saying that the name foo is used twice would be a good thing.

Urlang won't work with NodeJS on Windows

Running Urlang file with Node (current-urlang-run #t) or any node modules (babel, js-beautify) will result in an error:
"System can't find the path specified"

binding error

I don't know if you are still working on this, but I've been having fun playing with urlang and came across an error.

This definition in a urmodule:

(define a  
  (let ()
    (var [a (object)])
    (:= a "key" "val")
    a))

produces:

var a=((function(){var a_1={};(a["key"]="val");return a_1;})());

Modules

Originally Urlang targeted ES5.

Now we can target ES6, so Urlang needs to support ES6 (esm) modules.

And (cross fingers) then we can use Skypack for easy import of
modules written in plain JavaScript.

Support spread in object literals.

The spread operator ... can be used like this:

{...styles, color: "red"}

In Urlang, allow a spread operator at the beginning of an object literal.

multiple requires using the same MODULE var

Requiring multiple modules produces javascript that has multiple var MODULE = instances.

Two modules:

(urlang
 (urmodule mod-a
   (export a)
   (define a 0))
 (urmodule mod-b
   (export b)
   (define b 1)))

And another module requiring both:

(urlang
 (urmodule test
   (require mod-a)
   (require mod-b)))

produces:

"use strict";
var MODULE = require("./mod-b.js");
var b = MODULE["b"];
var MODULE = require("./mod-a.js");
var a = MODULE["a"];

I don't know that this is necessarily a bug or even incorrect code, but some tools don't like it. Rollup (rollupjs.org) for example chokes on it with the error [!] Error: Identifier 'MODULE' has already been declared.

Fix mangling of last part of dotted identifiers

Identifiers such as foo.bar expand to foo.bar which means foo["bar"].
The identifier foo.bar-baz currently expands to foo.bar-baz but should
be foo["bar-baz"] or perhaps foo.bar_baz.

Note: The dot notation is currently (ab)used to call methods.
Therefore switching to foo["bar"] everywhere in the expansion
will break method calls.

How to export default modules?

I am hoping to use Urlang to write code for Gatsby. The Gatsby code examples use ES6 syntax. Gatsby pages and components export modules as default.

Is there a way in Urlang to export modules as default rather than named? I don't see any examples. I'm also inexperienced in both Javascript and Racket, so I may be missing something obvious.

(define) is not allowed inside a (lambda) form

This works:

(urlang
  (urmodule js
    (define (hello x) (+ "Hello " x))
    (hello "world")))
"use strict";
function hello(x){return ("Hello "+x);};
(hello("world"));

This does not and seems to trigger a Racket parsing error:

(urlang
  (urmodule js
    (lambda ()
      (define (hello x) (+ "Hello " x))
      (hello "world"))))
; readline-input:29:7: parse-expr: expected an expression, got
;   #<syntax:readline-input:29:7 define>
;   in: define
; [,bt for context]

This does not either and is what I'd like to do:

(urlang
  (urmodule js
    (var [y (lambda ()
              (define (hello x) (+ "Hello " x))
              (hello "world"))])))
; readline-input:42.4: var: expected Fixnum; expected Flonum; expected String;
;   expected Boolean; expected Application; expected Sequence; expected
;   Ternary; expected Assignment; expected Let; expected Lambda; expected
;   ArrayReference; expected Array; expected New; expected Object; expected
;   Dot; expected Id; or expected one of these identifiers: `while', `var',
;   `block', `do-while', `sif', `break', `continue', `return', `label',
;   `begin', `if', `:=', `let', `lambda', `λ', `ref', `array', `new', `object',
;   or `dot'
;   at: define
;   in: (var (y (lambda () (define (hello x) (+ "Hello " x)) (hello "world"))))
;   parsing context:
;    while parsing different things...
;    while parsing Body
;     term: ((define (hello x) (+ "Hello " x)) (hello "worl...
;     location: readline-input:42.12
;    while parsing Lambda
;     term: (lambda () (define (hello x) (+ "Hello " x)) (h...
;     location: readline-input:42.12
;    while parsing Expr
;     term: (lambda () (define (hello x) (+ "Hello " x)) (h...
;     location: readline-input:42.12
;    while parsing VarBinding
;     term: (y (lambda () (define (hello x) (+ "Hello " x))...
;     location: readline-input:42.9
; [,bt for context]

I guess (define) is missing from the allowed syntax in (lambda) or in (var).

I think (ref ...) is still broken at the top level.

I pulled the latest fix, but I think (ref ...) is still broken in some cases.

This doesn't work:

(ref a "b" "c" "d") => a["b"]["b"]["b"];

but it works for other inputs:

(var a b c d) (ref a b c d) => a[b][c][d];
(ref a 0 1 2 3) => a[0][1][2][3];

and the chain works:

(ref (ref (ref a "b") "c") "d") => a["b"]["c"]["d"];

Fails to build on the pkg-build server

Error here: http://pkg-build.racket-lang.org/server/built/fail/urlang.txt

raco setup: --- summary of errors ---
raco setup: error: during making for <pkgs>/urlang/compiler-rjs
raco setup:   urmodule-name->exports: exports file not found: "runtime.exports"
raco setup:     compiling: <pkgs>/urlang/compiler-rjs/compiler.rkt
raco setup: error: during making for <pkgs>/urlang/compiler-test
raco setup:   urmodule-name->exports: exports file not found: "runtime.exports"
raco setup:     compiling: <pkgs>/urlang/compiler-rjs/compiler.rkt
raco setup: error: during making for <pkgs>/urlang/urlang-examples/ractive
raco setup:   /home/racket/build-pkgs/user/.racket/6.4/pkgs/urlang/urlang-examples/ractive/ractive-original.rkt:10:9: collection not found
raco setup:     for module path: html-writing
raco setup:     collection: "html-writing"
raco setup:     in collection directories:
raco setup:      /home/racket/build-pkgs/user/.racket/6.4/collects
raco setup:      /home/racket/build-pkgs/racket/collects
raco setup:      ... [197 additional linked and package directories]
raco setup:     compiling: <pkgs>/urlang/urlang-examples/ractive/ractive-original.rkt

The html-writing part should be fixed by #2.

Can I assign to a field nested N levels into an object?

Maybe I'm using urlang incorrectly but I don't know how to produce the following instruction in Javascript:
renderer.view.style.position = "absolute";
I'm trying with:
(:= (dot renderer view style position) "absolute")
but the error suggests that assignments only allow identifiers as the first parameter.

racket bindings interfering with urlang bindings of the same name

The following example gives the error α-rename: (urlang) unbound variable in: fn when it shouldn't.

#lang racket
(require urlang)

;; comment this out and the unbound variable error goes away
(define (fn) "this is causing the problem")

(urlang
 (urmodule
  test-mod-a
  (export fn)
  (define (fn)
    "hello?"))
 (urmodule
  test-mod-b
  (require test-mod-a)
  (fn)))

The issue is caused by the racket definition of fn. If you comment out the racket binding of fn, the issue goes away.

I'm not sure exactly why this is happening, but is urlang somehow capturing the racket binding?

Macro problem

This works

     (define (array-intersection ars)
       ; copy first array
       (var [result ((dot (ref ars 0) slice))])
       ; intersect the remaining arrays with the result array
       (for ([a in-array ars]
             [j in-naturals 0]) ; (sigh) don't use i here
         (when (> j 0)
           (:= result (string-array-intersection2 result a))))
       result)

But if j is replaced with i the macro expansion is wrong.

Creating a string in Racket and using it in Urlang

Hi, I'm playing around with Urlang coming from the Javascript world. My understanding is that is should be possible through macros to create a string (in something like the compile phase) that Urlang would be able to use. Here is my attempt:

(define-syntax m
(syntax-rules ()
[(_) (xexp->html `(html (head (title "Hello world!"))
(body (p "Hey out there!"))))]))

(urlang
(urmodule demo-fact
console.log(m)
))

but it seems it is not evaluating "m" right away as I thought it did. My understanding was that it would literally replace the macro with the result of the expression? How is this achieved?

Dot identifiers in assignment positions.

While dot identifiers work in reference positions, they don't work in assignment ones:

(define x (object))
(:= x.a 42)

for example throws an error. Obviously I 'could' put the identifier in an import list, but that does seem a bit clunky:

(import x.a)
(define x (object))
(:= x.a 42)

It seems like this syntax does work, but is not documented:

(define x (object))
(:= x "a" 42)

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.