jerome.martin.dev
2018-11-13 16:53:43

@jerome.martin.dev has joined the channel


soegaard2
2018-11-13 16:53:44

@soegaard2 has joined the channel


jerome.martin.dev
2018-11-13 16:54:19

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


jerome.martin.dev
2018-11-13 16:55:34

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


jerome.martin.dev
2018-11-13 17:05:47

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


jerome.martin.dev
2018-11-13 17:07:32

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


soegaard2
2018-11-13 17:49:57

Hi @jerome.martin.dev


soegaard2
2018-11-13 17:50:30

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


soegaard2
2018-11-13 17:50:39
(urlang
  (urmodule js
    (var [x (lambda ()
              (var [hello (lambda(x) (+ "Hello " x))])
              (hello "world"))])))

soegaard2
2018-11-13 17:52:18

The grammar is (at the bottom of https://github.com/soegaard/urlang )


soegaard2
2018-11-13 17:52:56
<module>            ::= (urmodule <module-name> <module-path> <module-level-form> ...)

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

soegaard2
2018-11-13 17:52:59

and further down


soegaard2
2018-11-13 17:53:20
<lambda>            ::= (lambda (<formal> ...) <body>)
<body>              ::= <statement> ... <expr>

soegaard2
2018-11-13 17:54:04

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


soegaard2
2018-11-13 17:56:05

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.


soegaard2
2018-11-13 17:56:39

Should I reconsider?


soegaard2
2018-11-13 17:57:12

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


soegaard2
2018-11-13 17:59:14

Also this issue should be mentioned in the docs.


soegaard2
2018-11-13 18:05:32

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.


jerome.martin.dev
2018-11-13 18:10:22

> 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.


soegaard2
2018-11-13 18:11:27

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


soegaard2
2018-11-13 18:11:50

This means the functions can be mutually recursive.


soegaard2
2018-11-13 18:12:06

Would the same work in JavaScript?


jerome.martin.dev
2018-11-13 18:12:13

I think so, yes


jerome.martin.dev
2018-11-13 18:12:28

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


soegaard2
2018-11-13 18:14:00

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


jerome.martin.dev
2018-11-13 18:14:22

function generates a local var with the name of the function


soegaard2
2018-11-13 18:14:41

In ES5 ?


jerome.martin.dev
2018-11-13 18:14:43

yep


soegaard2
2018-11-13 18:14:48

Hmm.


soegaard2
2018-11-13 18:15:28

I guess I ought to add function then.


jerome.martin.dev
2018-11-13 18:16:00

functions are first-class in js


soegaard2
2018-11-13 18:16:17

Yes. I was more concerned about the syntax.


jerome.martin.dev
2018-11-13 18:17:22

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


jerome.martin.dev
2018-11-13 18:17:58

which is pretty much the opposite from lisps


soegaard2
2018-11-13 18:18:45

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.


jerome.martin.dev
2018-11-13 18:20:07

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


soegaard2
2018-11-13 18:20:39

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


soegaard2
2018-11-13 18:21:33

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.


jerome.martin.dev
2018-11-13 18:21:50

mmmh, I see


jerome.martin.dev
2018-11-13 18:22:02

I’ll do that for now, yes


soegaard2
2018-11-13 18:23:02

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


jerome.martin.dev
2018-11-13 18:23:11

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


jerome.martin.dev
2018-11-13 18:23:40

this is why it works in plain js


soegaard2
2018-11-13 18:23:58

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


soegaard2
2018-11-13 18:24:41

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


soegaard2
2018-11-13 18:24:56

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


soegaard2
2018-11-13 18:25:42

If <statement> is changed from


soegaard2
2018-11-13 18:26:03

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


soegaard2
2018-11-13 18:26:06

to, say,


soegaard2
2018-11-13 18:26:26

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


soegaard2
2018-11-13 18:26:44

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


soegaard2
2018-11-13 18:28:31

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


soegaard2
2018-11-13 18:28:53

Oh!


soegaard2
2018-11-13 18:29:11

I think the problem is the scope of the declaration.


soegaard2
2018-11-13 18:30:07

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


jerome.martin.dev
2018-11-13 18:31:02

the function is scoped to its enclosing func


jerome.martin.dev
2018-11-13 18:31:17

exactly like in racket


jerome.martin.dev
2018-11-13 18:32:19

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




soegaard2
2018-11-13 18:38:05

Would this work:


soegaard2
2018-11-13 18:38:38

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


soegaard2
2018-11-13 18:40:27

yep.


soegaard2
2018-11-13 18:41:25

Let’s look at the grammar again.


soegaard2
2018-11-13 18:41:48

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


soegaard2
2018-11-13 18:42:51

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


soegaard2
2018-11-13 18:43:13

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


soegaard2
2018-11-13 18:44:04

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


soegaard2
2018-11-13 18:44:46

You will need to :


soegaard2
2018-11-13 18:44:52
  1. Change the grammar.

soegaard2
2018-11-13 18:45:23
  1. Change parse-body to accept <statement> | <local-define> rather than just <statement>.

soegaard2
2018-11-13 18:47:00
  1. Nanopass will now produce errors, alerting you to the pieces in the code that now needs to handle <local-define>.

soegaard2
2018-11-13 18:48:05

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


jerome.martin.dev
2018-11-13 18:54:03

yep


soegaard2
2018-11-13 18:56:00

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.


jerome.martin.dev
2018-11-13 18:59:31

> 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:


soegaard2
2018-11-13 18:59:45

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.


soegaard2
2018-11-13 19:00:27

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


jerome.martin.dev
2018-11-13 19:00:36

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


soegaard2
2018-11-13 19:00:46

Oh yeah.


jerome.martin.dev
2018-11-13 19:01:02

it will create global functions :confused:


soegaard2
2018-11-13 19:01:14

global?


jerome.martin.dev
2018-11-13 19:01:49

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


jerome.martin.dev
2018-11-13 19:02:11

first there’s “hoisting”




jerome.martin.dev
2018-11-13 19:04:09

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


jerome.martin.dev
2018-11-13 19:04:53

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.


jerome.martin.dev
2018-11-13 19:06:29

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


soegaard2
2018-11-13 19:06:34

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. ”


jerome.martin.dev
2018-11-13 19:06:40

otherwise it’ll create global functions


soegaard2
2018-11-13 19:06:52

Urlang is designed to throw an undeclared identifier error.


jerome.martin.dev
2018-11-13 19:07:02

yep, so it should be good for this case


jerome.martin.dev
2018-11-13 19:07:49

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


jerome.martin.dev
2018-11-13 19:08:33

but now that I think about it…


jerome.martin.dev
2018-11-13 19:08:42

it might not be that of an issue in the end


jerome.martin.dev
2018-11-13 19:09:49

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


jerome.martin.dev
2018-11-13 19:10:12

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


jerome.martin.dev
2018-11-13 19:10:21

while js does not care


soegaard2
2018-11-13 19:11:53

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.


soegaard2
2018-11-13 19:13:20

This works currently:


soegaard2
2018-11-13 19:13:22

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


soegaard2
2018-11-13 19:13:49

And


soegaard2
2018-11-13 19:13:50

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


soegaard2
2018-11-13 19:14:10

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


jerome.martin.dev
2018-11-13 19:14:16

mmmh


jerome.martin.dev
2018-11-13 19:14:55

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


soegaard2
2018-11-13 19:17:06

Maybe :slightly_smiling_face:


soegaard2
2018-11-13 19:17:28

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


soegaard2
2018-11-13 19:17:36

This will produce the same error.


soegaard2
2018-11-13 19:18:06

What would the corresponding JavaScript produce?


jerome.martin.dev
2018-11-13 19:18:30

it’ll hoist bar higher than foo


soegaard2
2018-11-13 19:18:54

No hoisting since foo is not a function.


soegaard2
2018-11-13 19:19:05

It should be:


soegaard2
2018-11-13 19:19:09

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


jerome.martin.dev
2018-11-13 19:19:21

right


soegaard2
2018-11-13 19:19:56

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


soegaard2
2018-11-13 19:20:09

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


jerome.martin.dev
2018-11-13 19:20:29

yep


soegaard2
2018-11-13 19:20:59

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


jerome.martin.dev
2018-11-13 19:21:04

definitely


soegaard2
2018-11-13 19:21:41

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


soegaard2
2018-11-13 19:22:10

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


jerome.martin.dev
2018-11-13 19:24:52

for now I’m trying using a simple urlang macro


jerome.martin.dev
2018-11-13 19:25:24
(define-urlang-macro function
  (lambda (stx)
    (syntax-parse stx
      [(function (func-name arg ...) body ...)
       (var [func-name (lambda (arg ...) body ...)])])))

(not tried yet)


soegaard2
2018-11-13 19:26:32

Looks okay to me.


jerome.martin.dev
2018-11-13 19:27:08

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


soegaard2
2018-11-13 19:27:18

Right.


soegaard2
2018-11-13 19:28:32

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


jerome.martin.dev
2018-11-13 19:31:47

oh


jerome.martin.dev
2018-11-13 19:32:11

I guess that will handle mutual recursion smoothly :slightly_smiling_face:


soegaard2
2018-11-13 19:32:20

Fingers crossed!


jerome.martin.dev
2018-11-13 19:32:25

ahah


jerome.martin.dev
2018-11-13 19:40:17

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]


jerome.martin.dev
2018-11-13 19:41:11

okay, got it


jerome.martin.dev
2018-11-13 19:41:19

i need to return something from a lambda


jerome.martin.dev
2018-11-13 19:41:58

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


soegaard2
2018-11-13 19:42:45

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


jerome.martin.dev
2018-11-13 21:51:00

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


jerome.martin.dev
2018-11-13 21:51:30

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)))


jerome.martin.dev
2018-11-13 21:56:54

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;}))());


jerome.martin.dev
2018-11-13 21:58:51

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


jerome.martin.dev
2018-11-13 21:59:58

Oh damn


jerome.martin.dev
2018-11-13 22:00:02

I found out


jerome.martin.dev
2018-11-13 22:00:33

It’s actually complaining about urlang missing


jerome.martin.dev
2018-11-13 22:00:53

If I (require urlang), suddenly my macro works


soegaard2
2018-11-13 22:02:30

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


soegaard2
2018-11-13 22:02:32

gives


soegaard2
2018-11-13 22:02:39

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


jerome.martin.dev
2018-11-13 22:02:51

Yep, but it only works if I require urlang


jerome.martin.dev
2018-11-13 22:03:06

I thought my macro would have urlang in scope


jerome.martin.dev
2018-11-13 22:03:36

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


soegaard2
2018-11-13 22:04:10

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


jerome.martin.dev
2018-11-13 22:04:25

yes


soegaard2
2018-11-13 22:04:33

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


jerome.martin.dev
2018-11-13 22:04:40

oh, didn’t try that


jerome.martin.dev
2018-11-13 22:04:45

I tried for-syntax


jerome.martin.dev
2018-11-13 22:04:55

didn’t new about for-template


soegaard2
2018-11-13 22:05:09

Rarely used.


jerome.martin.dev
2018-11-13 22:05:33

nope, doesn’t change anything


soegaard2
2018-11-13 22:05:40

hmm


soegaard2
2018-11-13 22:07:05

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.


jerome.martin.dev
2018-11-13 22:07:34

yep, that’s what I thought…


jerome.martin.dev
2018-11-13 22:08:12

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


jerome.martin.dev
2018-11-13 22:08:45

yep, it works..


soegaard2
2018-11-13 22:08:58

Great.


jerome.martin.dev
2018-11-13 22:09:39

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


soegaard2
2018-11-13 22:10:44

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


soegaard2
2018-11-13 22:11:18

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


jerome.martin.dev
2018-11-13 22:11:54

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


jerome.martin.dev
2018-11-13 22:12:34

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


jerome.martin.dev
2018-11-13 22:13:31

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


jerome.martin.dev
2018-11-13 22:14:17

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


jerome.martin.dev
2018-11-13 22:14:47

didn’t got the time to check it out yet


soegaard2
2018-11-13 22:15:58

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


soegaard2
2018-11-13 22:16:20

Haven’t played with them yet though.


jerome.martin.dev
2018-11-13 22:16:54

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


soegaard2
2018-11-13 22:17:15

See you later.


soegaard2
2018-11-13 22:20:48

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))))]))