
@jerome.martin.dev has joined the channel

@soegaard2 has joined the channel

@soegaard2 Hey, I think I stumbled upon an issue: https://github.com/soegaard/urlang/issues/17

I’m trying to find where the grammar disallows such syntax, but I’m still new to the code, and it’s a bit dense

Ok, I think it’s because <expr>
cannot contain <keyword>

(I’m new to nanopass but I think I’m starting to understand how it works)

Hi @jerome.martin.dev

In the body of a lambda-expression, you will need to use var.

(urlang
(urmodule js
(var [x (lambda ()
(var [hello (lambda(x) (+ "Hello " x))])
(hello "world"))])))


<module> ::= (urmodule <module-name> <module-path> <module-level-form> ...)
<module-level-form> ::= <export> \| <import> \| <definition> \| <statement>
<definition> ::= (define (f <formal> ...) <body>)
\| (define x <expr>)

and further down

<lambda> ::= (lambda (<formal> ...) <body>)
<body> ::= <statement> ... <expr>

It’s natural (as one who is used to Racket) to assume that define
also works in the body of lambda
.

I think, the reason I didn’t add definitions to the body of lambda expressions is that the obvious translation (define x expr)
-> (var [x expr])
has a different semantics from the define we are used to in Racket.

Should I reconsider?

@soegaard2 set the channel purpose: Discussions on Urlang - see https://github.com/soegaard/urlang

Also this issue should be mentioned in the docs.

Also the error message from the example is technically correct, but it could easily be improved to say, that define is legal only in a module-level-form.

> Should I reconsider? Well, I expect (define)
to generate a corresponding named function in the javascript result. As named functions inside functions are legal in js, I suppose it can be considered a regression that the construct is not available in urlang.

In Racket internal defines are rewritten as a letrec-expression.

This means the functions can be mutually recursive.

Would the same work in JavaScript?

I think so, yes

the system is internal in javascript, but works pretty much the same

How do you define a local function in JavaScript without using var
?

function generates a local var with the name of the function

In ES5 ?

yep

Hmm.

I guess I ought to add function
then.

functions are first-class in js

Yes. I was more concerned about the syntax.

the thing is, in js, function started as a statement, and became an expression later on

which is pretty much the opposite from lisps

Maybe I am misremembering. I probably just had var
in the beginning. Added define
at the module level, and never added them in lambda
expressions.

do you think it’s a tough change to make? I’ve read the whole code in main.rkt, and the comments are really helping (thanks for that!) but I still need to try nanopass on a smaller project to really grasp what’s going on with the passes

I am pretty sure that (var [x1 expr1]) (var [x2 expr2]) (var [x3 expr3]) behaves as a let*
expression and not a letrec
expression.

The easiest way to add them for now, is to write small urlang macro, say, def
that expands into var
. I think I can do that in 10 minutes. Modifying the grammar will take longer.

mmmh, I see

I’ll do that for now, yes

If you are interested in playing with Nanopass, I think it would be good project to start with.

following your concerns: var effectively behave like let*, but functions are not interpreted immediately, and can contain not yet defined code in them, so mutually recursive functions work out of the box

this is why it works in plain js

Let’s see what you need to add to make local definitions:

We want to be able to make local definitions in lambda-expressions.

<lambda> ::= (lambda (<formal> …) <body>) <body> ::= <statement> … <expr>

If <statement> is changed from

<statement> ::= <var-decl> | <block> | <while> | <do-while> | <if> | <break> | <expr>

to, say,

<statement> ::= <local-define> | <var-decl> | <block> | <while> | <do-while> | <if> | <break> | <expr>

we need to check whether <statement> is used elsewhere.

It is used in <module-level-form>, <block>, <while>, <do-while>, <if> and <let>.

Oh!

I think the problem is the scope of the declaration.

If you have a local definition in JavaScript using function, what scope is the function visible in?

the function is scoped to its enclosing func

exactly like in racket

it has some edge cases in ES6 with “arrow-functions”, and the differences between var/let/const, but for functions, it works as expected



Would this work:

((lambda () (define (foo) (bar)) (define (bar) 42) (foo))

yep.

Let’s look at the grammar again.

Does <local-define> make sense in all of <module-level-form>, <block>, <while>, <do-while>, <if> and <let> ?

Or would it be easier to modify <body> from <statement> … <expr>

to <body> ::= ( <statement> | <local-define> ) … <expr>

That might be simpler. Then the change won’t affect the module level.

You will need to :

- Change the grammar.

- Change parse-body to accept <statement> | <local-define> rather than just <statement>.

- Nanopass will now produce errors, alerting you to the pieces in the code that now needs to handle <local-define>.

The trickiest part is make sure alpha-renaming handles names introduced by <local-define>

yep

The example of the first page of http://www.doc.ic.ac.uk/~maffeis/aplas08.pdf is part of the reason, I am not sure the scope rules are the same as in Racket.

> This and other examples lead us to suggest the var f= function (… ){…} form for programming, since this form avoids various anomalies. Ok I see why now :slightly_smiling_face:

If (define (foo arg …) body) is translated into (var [foo (lambda (arg …) body)]) then it would be enough to change the initial grammar, and then let parse output (var [foo (lambda (arg …) body)]). No further changes would be needed.

It might be closer to Racket if “strict” mode is in use.

well, it would be good, except var has strange scope behavior

Oh yeah.

it will create global functions :confused:

global?

well I need to check but sometimes var behaves strangely, I need to find the exact rule

first there’s “hoisting”



> var declarations, wherever they occur, are processed before any code is executed. This is called hoisting, and is discussed further below.

The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value. Assigning a value to an undeclared variable implicitly creates it as a global variable (it becomes a property of the global object) when the assignment is executed.

so our var will work as long as the code is not outside a function

Apropos: “Assigning a value to an undeclared variable implicitly creates it as a global variable (it becomes a property of the global object) when the assignment is executed. ”

otherwise it’ll create global functions

Urlang is designed to throw an undeclared identifier error.

yep, so it should be good for this case

the issue is there: > or, for variables declared outside any function, global

but now that I think about it…

it might not be that of an issue in the end

the only issue might be the lack of letrec in the urlang transformer, in case of mutually recursive functions

urlang will throw an error because it’ll parse the content of the function

while js does not care

Yes, when analyzing the variables in scope in a body, it will be necessary to add all variables introduced by define before handling the right hand sides.

This works currently:

(urlang (urmodule js (var [foo (lambda () (bar))]) (var [bar (lambda () 42)]) (foo)))

And

((lambda () (var [foo (lambda () (bar))]) (var [bar (lambda () 42)]) (foo)))

will not - the bar in (bar) will produce an unbound variable error.

mmmh

so the way you keep the list of vars is only working in the module scope for now

Maybe :slightly_smiling_face:

(urlang (urmodule js ((lambda () (var [foo bar]) (var [bar 42]) (foo)))))

This will produce the same error.

What would the corresponding JavaScript produce?

it’ll hoist bar higher than foo

No hoisting since foo is not a function.

It should be:

(urlang (urmodule js ((lambda () (var [foo bar]) (var [bar 42]) foo))))

right

Well, the declaration is hoisted, but the initilization of foo happens before bar.

So I expect it the result to be “undefined” in JavaScript.

yep

So producing an error at compile time seems a good thing here.

definitely

I think adding local definitions to a lambda body would be a good thing.

It would be ideal if they get a Racket-like semantics.

for now I’m trying using a simple urlang macro

(define-urlang-macro function
(lambda (stx)
(syntax-parse stx
[(function (func-name arg ...) body ...)
(var [func-name (lambda (arg ...) body ...)])])))
(not tried yet)

Looks okay to me.

it lacks (syntax)
or #'
I think x)

Right.

Hmm. I see extra.rkt has a defintion of letrec as an Urlang macro. I had forgotten that.

oh

I guess that will handle mutual recursion smoothly :slightly_smiling_face:

Fingers crossed!

ahah

I get a weird result with: (urlang
(urmodule js
(var [Hello (lambda () (var [hey (lambda (x) (+ "Hey " x))]))])))
; readline-input:4.4: var: expected more terms starting with Statement or Expr
; at: ()
; within: (lambda () (var (hey (lambda (x) (+ "Hey " x)))))
; in: (var (Hello (lambda () (var (hey (lambda (x) (+ "Hey " x)))))))
; parsing context:
; while parsing Body
; term: ((var (hey (lambda (x) (+ "Hey " x)))))
; location: readline-input:4.16
; while parsing Lambda
; term: (lambda () (var (hey (lambda (x) (+ "Hey " x)))))
; location: readline-input:4.16
; while parsing Expr
; term: (lambda () (var (hey (lambda (x) (+ "Hey " x)))))
; location: readline-input:4.16
; while parsing VarBinding
; term: (Hello (lambda () (var (hey (lambda (x) (+ "Hey...
; location: readline-input:4.9
; [,bt for context]

okay, got it

i need to return something from a lambda

this fixed it (urlang
(urmodule js
(var [Hello (lambda () (var [hey (lambda (x) (+ "Hey " x))]) hey)])))

yes, the last thing in a body needs to be an expression

Okay, I post this so that we can work on it later: (javascript Hello (lambda (x) (+ "Hello " x)))
=> "\"use strict\";\nvar Hello=(((function(){return (function(x){return (\"Hello \"+x);});}))());\n"
(javascript Hello (var [hey (lambda (x) (+ "Hello " x))]) hey)
=> ; readline-input:9:19: α-rename: (urlang) unbound variable in: var

the javascript
macro I use does this: (define-simple-macro
(javascript name jsexpr ...)
(let ([js (open-output-string)])
(parameterize ([current-urlang-run? #f]
[current-urlang-echo? #t]
[current-output-port js])
(urlang
(urmodule name
(var [name ((lambda () jsexpr ...))]))))
(get-output-string js)))

It is supposed to expand to this: (urlang
(urmodule Hello
(var [Hello ((lambda () (var [hey (lambda (x) (+ "Hello " x))]) hey))])))
Which actually works: var Hello=(((function(){var hey=(function(x){return ("Hello "+x);});return hey;}))());

It seems like the α-rename does not like urlang to be inside a macro… It’s weird

Oh damn

I found out

It’s actually complaining about urlang
missing

If I (require urlang)
, suddenly my macro works

Here (javascript Hello (var [hey (lambda (x) (+ "Hello " x))]) hey)

gives

""use strict";\nvar Hello=(((function(){var hey=(function(x){return ("Hello "+x);});return hey;}))());\n"

Yep, but it only works if I require urlang

I thought my macro would have urlang in scope

if I provide only my macro in a file that requires urlang, then import that file, the macro doesn’t work

In that I think the problem is to get the keywords (var, if, etct) in scope too.

yes

Does a (require (for-template urlang)) help ?

oh, didn’t try that

I tried for-syntax

didn’t new about for-template

Rarely used.

nope, doesn’t change anything

hmm

If you javascript
macro is defined in one file and you use it in a different, then I think you need to provide the keywords var, if, etc from the file where javascript
is defined.

yep, that’s what I thought…

I guess I’ll do (all-from-out urlang)

yep, it works..

Great.

I just hope it won’t shadow some other identifiers x)

Urlang provides keyword which is a literal-set consisting of all the keywords.

I wonder whether it could be used to provide keywords only.

alas, there’s no way to provide from a whitelist in racket, as far as I know

there’s (except)
, but not the other way around

for some time now I wonder how to write custom directives for (provide)
, but I did not learn yet

the kind of stuff that (provide (struct-out))
does for example

didn’t got the time to check it out yet

Experiment with “provide transformers”. I think that’s how struct-out is implemented.

Haven’t played with them yet though.

Anyways, I need to go now. Thanks for you time and your help! See you!

See you later.

Try this version tomorrow: (require urlang (for-syntax syntax/parse))
(define-syntax (javascript stx) (syntax-parse stx [(_javascript name jsexpr …) (with-syntax ([(name jsexpr …) (syntax->datum #’(name jsexpr …))]) (syntax/loc stx (let ([js (open-output-string)]) (parameterize ([current-urlang-run? #f] [current-urlang-echo? #t] [current-output-port js]) (urlang (urmodule name (var [name ((lambda () jsexpr …))])))) (get-output-string js))))]))