GithubHelp home page GithubHelp logo

anko / eslisp Goto Github PK

View Code? Open in Web Editor NEW
528.0 528.0 31.0 519 KB

un-opinionated S-expression syntax and macro system for JavaScript

License: ISC License

JavaScript 0.41% Makefile 0.82% LiveScript 98.77%
compiler javascript lisp macro

eslisp's People

Contributors

anko avatar machineloop avatar waldyrious 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eslisp's Issues

Why do source maps fail on Windows?

Hi,

I did two simple tests with source maps. Since I am new to working with source maps, I did one test with coffeescript and one test with eslisp. The coffee script test worked right away. With eslisp, I can't seem to get it working at all in Chrome. Can somebody confirm that it is working? Has anyone else had difficulty?

Thanks,
Kerry

capmacro doesn't capture subsequent macros

This code:

(capmacro Var (lambda (val)
  (return `((require "observ") ,val))))

(capmacro Arr (lambda ()
  (var args (Array.prototype.slice.call arguments 0))
  (return `((require "observ-array") (array ,@args)))))

(capmacro Obj (lambda ()
  (var args (Array.prototype.slice.call arguments 0))
  (return `((require "observ-struct") (object ,@args)))))

(Obj "results" (Arr (Var (Obj "foo" "bar"))))

compiles to:

require('observ-struct')({ 'results': require('observ-array')([require('observ')(Arr(Obj('foo', 'bar')))]) });

rather than:

require('observ-struct')({ 'results': require('observ-array')([require('observ')(require('observ-array')[(require('observ-struct')({ 'foo': 'bar'})]))]) });

What do I do?

Try-catch with anything other than an s-expression throws a TypeError

That stack trace should be descriptive enough. I tried every possible atom that wasn't an s-expression, and it always throws.

$ eslc <<<'(try foo (catch (e)))'

${NPM_DIR}/eslisp/lib/cli.js:73
      throw err;
      ^

TypeError: Cannot read property '0' of undefined
    at isPart (${NPM_DIR}/eslisp/lib/built-in-macros.js:506:28)
    at ${NPM_DIR}/eslisp/lib/built-in-macros.js:513:11
    at Array.forEach (native)
    at contents.try (${NPM_DIR}/eslisp/lib/built-in-macros.js:512:10)
    at list.prototype.compile (${NPM_DIR}/eslisp/lib/ast.js:188:14)
    at ${NPM_DIR}/eslisp/lib/translate.js:34:17
    at ${NPM_DIR}/eslisp/node_modules/prelude-ls/lib/List.js:158:21
    at ${NPM_DIR}/eslisp/node_modules/prelude-ls/lib/List.js:161:4
    at ${NPM_DIR}/eslisp/node_modules/prelude-ls/lib/List.js:665:42
    at module.exports (${NPM_DIR}/eslisp/lib/translate.js:35:7)

I expected it to print out something like this:

try {
  foo;
} catch (e) {
  bar;
}

eslisp 0.7.5 breaks eslisp-camelify and eslisp-propertify (and maybe eslisp-fancy-function)

Hello, I recently upgraded my project's dependencies and noticed this error when trying to compile *.esl files.

eslisp-camelify is checkout from the official Git repo.

It seems like something has changed, and this is no longer bound to the eslisp object, so that this.multi() fails because this is null?

git clean -fxd
npm install
make clean
make
make test 
node test.js
TAP version 13
# var
/home/rko/src/eslisp-camelify/node_modules/eslisp/lib/compile.js:189
        throw e;
        ^

TypeError: Error evaluating macro `camelify` (called at line 1, column 40): Cannot read property 'apply' of undefined
    at env.module.exports (/home/rko/src/eslisp-camelify/index.js:35:22)
    at /home/rko/src/eslisp-camelify/node_modules/eslisp/lib/compile.js:184:35
    at listToEstree (/home/rko/src/eslisp-camelify/node_modules/eslisp/lib/compile.js:191:6)
    at astToEstree (/home/rko/src/eslisp-camelify/node_modules/eslisp/lib/compile.js:250:25)
    at /home/rko/src/eslisp-camelify/node_modules/eslisp/lib/translate.js:36:14
    at /home/rko/src/eslisp-camelify/node_modules/prelude-ls/lib/List.js:158:21
    at /home/rko/src/eslisp-camelify/node_modules/prelude-ls/lib/List.js:161:4
    at /home/rko/src/eslisp-camelify/node_modules/prelude-ls/lib/List.js:665:42
    at module.exports (/home/rko/src/eslisp-camelify/node_modules/eslisp/lib/translate.js:37:7)
    at toEstree (/home/rko/src/eslisp-camelify/node_modules/eslisp/lib/index.js:9:10)
make: *** [makefile:12: test] Error 1

How to convert JS into eslisp syntax?

I would like to reimplement some infrastructure like browserify over eslisp syntax, because it's common for such utilities to have imperative API that is very hard to extend, and I don't trust their conversion methods. But this would require not only JS pretty printer, but a JS parser too.

Is there already such an utility?

Issue using atoms with macros

I'm writing a macro to convert a hex atom into a decimal literal since eslisp doesn't seem to like hex literals

Current macro code looks like this:

(= (. module exports)
    (lambda (num)
    (return ((. this list)
        ((. this atom) "parseInt")
        ((. this string) (. num value))
        ((. this string) "16")))))

And the compiled JS looks like this:

module.exports = function (num) {
    return this.list(this.atom('parseInt'), this.string(num.value), this.string('16'));
};

Calling it with (macro test (require "./mac.js")) (test 'ff) returns the following error:

TypeError: Cannot read property 'type' of undefined
    at astToEstree (/home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:244:15)
    at /home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:275:51
    at Array.map (<anonymous>)
    at listToEstree (/home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:238:23)
    at astToEstree (/home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:250:25)
    at /home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:218:16
    at listToEstree (/home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:234:6)
    at astToEstree (/home/taylor/.npm-global/lib/node_modules/eslisp/lib/compile.js:250:25)
    at /home/taylor/.npm-global/lib/node_modules/eslisp/lib/translate.js:36:14

Am I doing anything wrong, or is this some weird issue with eslisp?

Export ast-to-estree?

Hi,

I'd like to use eslisp to evaluate eslisp, so that I can do things like:

(eval (quote (+ 1 2 3)))

eval, of course, takes Javascript so I'd like a way to transform the estree (as returned by quote) to javascript. Looking at https://github.com/anko/eslisp/blob/master/src/index.ls, it appears that we might be able to export ast-to-estree such that something like

(eval (ast-to-estree (quote (+ 1 2 3))))

could work (edit: not quite, the AST used internally seems to be different from what quote returns). Another option would be to make compileOnce, etc default to this behavior if the input was an estree instead of a string.

What do you think?

Thanks,
Ben

The `object` macro is not ES6-friendly

ES6 introduces dynamic property names in object expressions:

var x = 42;
var obj = {
  [ 'prop_' + x ]: 42
};

At the moment, eslisp's object macro can't unambiguously accommodate that. Given that (object a b) compiles to { a : b }, what should compile to { [a] : b }?

Similarly to previously in #13, this can't simply be solved by having (object "a" b) compile to { a : b } instead and (object a b) to { [a] : b }, because it must continue to be possible to express both { a : b } and { "a" : b } for stuff like Google's closure compiler, and for when it's necessary to ensure that part of the code is also valid JSON.

Source maps are unhelpful when used with transform macros

At the moment, the compiler cannot resolve source locations beyond where macros are called. It deliberately treats macros as black boxes, to insulate user macros from having to know anything about the location data stored on S-expression nodes.

Example: If someone has defined a macro hello and call it as (hello there a b c d), currently anything at all that hello call returns is just sourcemapped to the macro's call site.

This backfires majestically with transform macros.

Transform macros are just like user macros that take the whole program as their arguments and return whatever they feel like. The calls to them have no in-source location, and they are not necessarily location-data aware, so anything they emit (which is the whole program) is sourcemapped to… nowhere. You just get an empty source map. It sucks.


Some possibilities:

  • Let macros return whatever location data they want to—it's up to its author to decide if it's worth it for their specific one. Wait for transform macros to be replaced with reader macros (and have those return location data), so the effect of other macros not deciding to return location data is minimal.
  • Require all macros to be aware of location data and to emit location data on the nodes they return, and have the compiler enforce this. (This makes macros more complex to write.)

I like the first better.

Recursive macros

I might be missing something, but a simple recursive macro call

(macro test (lambda () (test)))
(test)

leads to the error

/home/lennu/code/lci/node_modules/eslisp/lib/cli.js:103
      throw err;
      ^

ReferenceError: Error evaluating macro `test` (called at line 27, column 0): test is not defined
    at ctor$.eval (eval at <anonymous> (/home/lennu/code/lci/node_modules/eslisp/lib/built-in-macros.js:610:16), <anonymous>:2:5)
    at /home/lennu/code/lci/node_modules/eslisp/lib/compile.js:184:35
    at listToEstree (/home/lennu/code/lci/node_modules/eslisp/lib/compile.js:191:6)
    at astToEstree (/home/lennu/code/lci/node_modules/eslisp/lib/compile.js:250:25)
    at /home/lennu/code/lci/node_modules/eslisp/lib/translate.js:35:14
    at /home/lennu/code/lci/node_modules/prelude-ls/lib/List.js:158:21
    at /home/lennu/code/lci/node_modules/prelude-ls/lib/List.js:161:4
    at /home/lennu/code/lci/node_modules/prelude-ls/lib/List.js:665:42
    at module.exports (/home/lennu/code/lci/node_modules/eslisp/lib/translate.js:36:7)
    at toEstree (/home/lennu/code/lci/node_modules/eslisp/lib/index.js:9:10)

Calling arguments.callee works, however it is neither recommended or elegant. Is there any alternative?

(- 3 2 1) !== 3 - (2 - 1)

I expect (- 3 2 1) to be translated to 3 - 2 - 1 which evaluates to 0 in javascript
while 3 - (2 - 1) evaluates to 2

im to fix it

Building on Windows

It has come to my attention that eslisp doesn't build on Windows.

I don't have a Windows machine to test on, but from what I can gather, these are the points of friction, in the form of a todo list:

  • CI-test it, so users on other platforms also know it works. (Travis doesn't do Windows. Alt: AppVeyor?)
  • Remove the make dependency. (Alt: Jake? Gulp?)
  • Some code has file paths fixed to being /-delimited. (Should use path.join.)
  • Explicitly call node <filename> in tests. (Windows doesn't recognise the shebang line. @isiahmeadows' #43 addresses this.)

Anything I've missed? Opinions? Volunteers?

Source maps?

Requested by @IMPinball and @Paraknight on separate occasions.

Source maps are supplementary info attached to JS code that lets debuggers point at what other-language file some piece of JavaScript was compiled from, so humans can find what to debug. More here.

Related to #9, as both require a way to track where in the input source an internal AST node came from.

The (.) macro's design is broken

In the README, you imply that (. a 3) compiles to a[3] (good), but then state that (. a b) compiles to a.b (in other words, for all but the first argument atoms are converted to symbols/strings). This means it's impossible to access into a data structure with a variable; there's no way to write the equivalent of JS's a[b].

To do this properly, named property access needs to always be done with strings, like (. a 'b'), or at least with self-evaluating symbols, like (. a :b). This way everything that looks like variables is actually variables.

Question: docs/tips for syntax highlighting?

Which VS Code extension to you recommend to use for eslisp? I started using Lisp by Yasuhiro Matsumoto, but I'm unsure if better ones are available. What are your thoughts on this?

I think it adds value to recommend an existing VS Code extension in the readme or the docs. Why single out VS Code? Because it's the biggest one out there (especially in the JS community), and it's not a bad choice. (source)

Ideally, a specific eslisp syntax highlighter extension is created, but that would be a significant undertaking, and I can imagine you may have already had thoughts about this?

Replace transform macros with proper reader macros

At the moment, there is no programmatic access to the parser (currently sexpr-plus), which would be necessary to customise syntax (e.g. for square-bracket array notation).

This can to some degree be done by abusing transform macros (which @whacked has toyed with), but it's cumbersome and fragile, and all whitespace or other characters that the default parser swallows are inaccessible to them.

@lhorie has written a read-table-based parser that we could first modify to pass sexpr-plus' tests, then allow user code to register new read table entries.

Documentation code example for “mean” macro fails with TypeError

Hi,

I've been getting some errors in my macros. It's likely I'm making mistakes, so I tried an example from the documentation. I seem to be having trouble with @,

This macro:

(macro mean
 (lambda ()
  ; convert arguments to Array
  (var args ((. Array prototype slice call) arguments 0))
  (var total ((. this atom) (. args length)))
  (return `(/ (+ ,@args) ,total))))

(mean 1 2 3)

Throws and error when I compile it. Here is the error:

$ eslc test2.lisp > test2.js

/usr/local/lib/node_modules/eslisp/lib/cli.js:102
      throw err;
            ^
TypeError: Cannot read property 'type' of undefined
    at astToEstree (/usr/local/lib/node_modules/eslisp/lib/compile.js:230:14)
    at env.prototype.compile (/usr/local/lib/node_modules/eslisp/lib/env.js:106:12)
    at /usr/local/lib/node_modules/eslisp/lib/env.js:213:50
    at env.<anonymous> (/usr/local/lib/node_modules/eslisp/lib/built-in-macros.js:49:17)
    at env.<anonymous> (/usr/local/lib/node_modules/eslisp/lib/built-in-macros.js:70:10)
    at listToEstree (/usr/local/lib/node_modules/eslisp/lib/compile.js:177:27)
    at astToEstree (/usr/local/lib/node_modules/eslisp/lib/compile.js:236:25)
    at env.prototype.compile (/usr/local/lib/node_modules/eslisp/lib/env.js:106:12)
    at env.compile (/usr/local/lib/node_modules/eslisp/lib/env.js:213:50)
    at env.macro (/usr/local/lib/node_modules/eslisp/lib/built-in-macros.js:20:19)

Whole-program-wrapping macros are awkward to use

The eslisp-camelify macro makes sense to wrap around your whole program if you like to use this-kind-of-variable-names and have them converted to thisKindOfVariableNames. Likewise, eslisp-propertify lets you use a.b.1 as shorthand for property access, instead of (. a b 1).

Such global transform macros could in the future include e.g. accessor function sugar for turning (.name) into (function (x) (return (. x name))) like LiveScript's accessor shorthand, et cetera.

However, using them is currently cumbersome, requiring e.g. piping eslisp files through sed as a build step, wrapping the entire contents in appropriate macro calls before passing to eslc, which feels like a hack.


Ideas:

  1. Document and recommend the sed hack. Dirty, but then it's the users' problem. ;)
  2. Provide a --wrap compiler flag. Something like—
eslc --wrap="eslisp-propertify,eslisp-camelify" < index.esl > index.js
  1. Write a separate module/program eslisp-wrap which job is to wrap its input file in a list of macros. This separates concerns better, but is a tiny bit less convenient.

Error on fresh install: Cannot find module 'concat-stream'

Steps to reproduce:

mkdir esl
cd esl
npm init -y
npm i eslisp
echo '((. console log) "Yo!")' | npx eslc

Output:

Cannot find module 'concat-stream'

Workaround:

npm i concat-stream

Potential fix: this seems like a simple missing dependency, which should just be added to the package.json.

export default?

Sorry for a (likely) newbie question: how does one export default {x, y, z}?

JQuery

I was wondering, how would you access JQuery elements in ESLisp?

Thanks,
Kerry

Macro Hygiene

The documentation says lisp-like hygienic macros are supported but there isn't much else by way of documentation. Can you elaborate? Most of the time (Common) Lisp style macros are not called hygienic.

Error when requiring macros from relative paths

I'm not able to require macros from local files defined by a relative path. It seems like eslc tries to resolve the require paths relative to the location of the eslisp package.

[eslisp-require-bug] cat ok.esl
(= (. module exports)
  (lambda () (return 'true)))

[eslisp-require-bug] eslc ok.esl > ok.js

[eslisp-require-bug] cat ok.js
module.exports = function () {
    return { atom: 'true' };
};

[eslisp-require-bug] cat index.esl
(macro ok (require "./ok.js"))
(ok)

[eslisp-require-bug] eslc index.esl
/usr/lib/node_modules/eslisp/lib/cli.js:73
      throw err;
            ^
Error: Cannot find module './ok.js'
    at Function.Module._resolveFilename (module.js:336:15)
    at Function.Module._load (module.js:278:25)
    at Module.require (module.js:365:17)
    at eval (eval at <anonymous> (/usr/lib/node_modules/eslisp/lib/built-in-macros.js:550:50), <anonymous>:1:2)
    at /usr/lib/node_modules/eslisp/lib/built-in-macros.js:550:16
    at compileAsMacro (/usr/lib/node_modules/eslisp/lib/built-in-macros.js:551:9)
    at contents.macro (/usr/lib/node_modules/eslisp/lib/built-in-macros.js:592:26)
    at list.prototype.compile (/usr/lib/node_modules/eslisp/lib/ast.js:188:37)
    at /usr/lib/node_modules/eslisp/lib/translate.js:34:17
    at /usr/lib/node_modules/eslisp/node_modules/prelude-ls/lib/List.js:158:21

[eslisp-require-bug] eslc -v
0.6.0      

suggestion: rename `=` to `var`

I think (var a 1) and (var a) read closer to their intent than (= a 1) and (= a)

Also, ES6 has let, so including a let macro into eslisp later would be more orthogonal if the hoisted declaration identifier is var

And that would free up the = identifier so you could use that for assignment instead of :=, to keep things closer to js syntax

User-macro calls given as user macro arguments always compile to function calls

As @stasm mentions in anko/eslisp-fancy-function#1, if a call to a user-defined macro is given as a parameter of another user-defined macro, it always compiles to a function call, not to the results of that macro.

Example:

(macro fun0 (lambda (body)
  (return `(lambda () ,body))))
(macro ok (lambda ()
  (return '(return true))))
(fun0 (ok))

Expected output:

(function () {
    return true;
});

Actual output:

(function () {
    ok();
});

This does not affect built-in macros, because they can access the compilation environment with which they can optionally compile their arguments (for example like this) in a way that resolves and executes macros.

User macros currently don't have that choice. They operate on lists they've received as arguments, but the process by which they're compiled doesn't take macros into account. If it did, we'd have the opposite problem. There needs to be a user choice.

To give an example, if a is a user-defined macro and it is called with (a (b)), it receives 1 argument, which is an array containing an atom b. If it returns that argument as-is, it is interpreted as a function call b();, which the macro may have wanted to return. But another interpretation is to compile the list taking the macro table into account, in which case it may turn out that b is a macro that returns different code to be used there instead.


There obviously should be no such limitation. I could use some help from more experienced lispers here. What would Batman do? Is this what macroexpand/macroexpand-all are for?

Macros defined in a "macros"-block run without the environment a "macro" runs in

That means they can't call any of the expected environment functions evaluate, multi, is-atom, is-string, text-of, gensym or is-expr. That's pretty terrible, since it cripples the abilities of macros defined in separate modules.

It might make sense to pass all that environment to macro functions on this, because

  • the compiler can then choose that environment,
  • avoids macros having to import a module to get that functionality,
  • keeps all of the function arguments freed up their expected purpose (receiving the rest of the S-expression form),
  • calling this.evaluate(...)
    • feels right in JavaScript-land (it's like a method defined on the macro),
    • dispels mysteries (of where these magical functions are even coming from),
    • removes the possibility of namespace collision (if a macros block defines another function called the same thing).

Yep.

`require` doesn't work in REPL

require doesn't work in the REPL:

> (require "./whatever.js")
evalmachine.<anonymous>:1
require('./whatever.js');
^
ReferenceError: require is not defined

However the binding macro example (which is using require) in How eslisp macros work (a tutorial) works fine in the REPL.
(jisp already has require in the REPL, but it doesn't have quoting and other important stuff)

Numbers are very restricted, inconsistent

Numbers are crudely checked against the regex /-?\d+(\.\d+)/ (accounting for two regexes with identical meaning. This is very restrictive compared to most languages. Example of the full ES6 offering:

  • Binary: 0b1010 === 10
  • Octal: 0o17 ==== 13
  • Hex: 0xf0 === 240
  • Trailing dot: 42. === 42

This probably should be improved (and is on my TODO list if I can get time).

(quote atomName) compiles quoted atoms to this.atom('atomName'), which fails if used in a nested function

…because this is bound to the this-context of that inner function!

Example

(macro what ()
 (= innerFunc (function () (return 'someAtom)))
 (return (innerFunc)))

Errors with

undefined:3
        return this.atom('someAtom');
                    ^
TypeError: this.atom is not a function
    at innerFunc (eval at <anonymous> (/home/an/code/eslisp/src/translate.ls:182:46), <anonymous>:3:21)
    at Object.eval (eval at <anonymous> (/home/an/code/eslisp/src/translate.ls:182:46), <anonymous>:5:12)
    at compilerspaceMacro (/home/an/code/eslisp/src/translate.ls:231:33)
    at list.prototype.compile (/home/an/code/eslisp/src/ast.ls:179:19)
    [ snip ]

Inside innerFunc, this refers to innerFunc itself, which understandably doesn't have a property called atom.

This could be hacked around by modifying macros such that they assign this to some unpredictable generated variable name in the beginning of the macro function and compile (quote someAtom) to $thatWeirdGeneratedSymbolasdfasdfasfd.atom. However, that compiles to really confusing-looking JS where the terrible hack is evident.

Instead, it might be best to change the way macros return quoted atoms, such that a returned JS object of the form { atom : "someAtom" } is understood as an atom someAtom. Returned arrays are already understood as compiler list objects, and strings as string objects, which still leaves returned JS objects free for use as atoms. Returning a non-quoted JS object in any other form would then be an error, but it's pretty nonsensical to do that in first place.

Macros are still passed most arguments in compiler form

Now that macros can return anything they need to as plain JS types (arrays become lists, numbers and objects become atoms, strings become strings), it would be sensible for their arguments to be converted to those JS types when passed in. Then operating on them (such as by console.logging them) gives less surprising results.

Better separation of concerns too.

Lists already get this treatment; they're turned into arrays.

Other stuff is still handed over in compiler form. Returning those from a macro works correctly, because conversion of macro results recognises that they're already in "compiler form" and doesn't mess with them.

More helpful syntax error messages

Syntax errors look like this right now:

$ eslc <<< "("

/home/an/code/eslisp/node_modules/sexpr-plus/index.js:1054
      throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);
      ^
SyntaxError: Expected ")", comment or whitespace but end of input found.

The SyntaxError-object sexpr-plus returns has line and column properties. We should perhaps use them.


Invalid AST errors look like this:

$ eslc <<< "(+ x %)"
{ [InvalidAstError: Identifier `name` member must be a valid IdentifierName]
  node: { type: 'Identifier', name: '%' },
  message: 'Identifier `name` member must be a valid IdentifierName' }
x + %;

That's already getting there, but it would still be best if exact positions in code were passed on to AST nodes, so the error reporter could point out the exact in-source position that an error happened.

`()` throws compilation error

In eslisp, you can use () in the (for () () ()) special form to create an infinite loop for (;;) {}

But if you use () in an expression, it throws a compilation error, e.g. (+ 1 ()) -> BinaryExpression right member must be an expression node.

Typically () evaluates to nil, so I'd expect the output to be 1 + null with no compiler error.

It'd still need to keep current behavior in for-loops, since for (;;) {} is a different thing from for (null; null; null) {}.

Alternatively, () could always mean null, and there would be another way of expressing empty expressions in for loops.

Cannot Assign Arrow Function Macro

My arrow function macro is as follows:

(macro =>
 (function ()
           (= env this)
           (= atoms ((. Array from) (. arguments 0 values)))
              
  (= propertyIdentifiers
     ((. atoms map)
      (function (atom) (return ((. env compile) atom)))))

  (= properties ((. propertyIdentifiers map)
                   (function (identifier)
                    (return (object type "Property"
                                    key identifier
                                    value identifier
                                    shorthand true)))))
  
  (= estree (object 
                                  type "ArrowFunctionExpression"
                                  generator false
                                  async false
                                  params properties
                                  body ((. this compile) (. arguments 1))))

  (return estree)))

I show the Ecmascript it produces with:

(macro showEcma
 (lambda (expression)
    (return `((. console log)
              ,((. this string)
                ((. this compileToJs) 
                 ((. this compile) expression)))))
    (return null)))

And I show the AST representation with:

(macro showAST
       (lambda (expression)
               (return ((. console log)                         
                           ((. this compile) expression))))) 

When I run :

    (showEcma (= var1
             (=> (a b c)
                 (block ((. console log) a)
                        ((. console log) b)
                        ((. console log) c)))))

I get:

var1 = (a, b, c) => {
    console.log(a);
    console.log(b);
    console.log(c);
};

When I showAST I get:

{
  type: 'AssignmentExpression',
  operator: '=',
  left: {
    type: 'Identifier',
    name: 'var1',
    loc: { start: [Object], end: [Object] }
  },
  right: {
    type: 'ArrowFunctionExpression',
    generator: false,
    async: false,
    params: [ [Object], [Object], [Object] ],
    body: { type: 'BlockStatement', body: [Array], loc: [Object] },
    loc: { start: [Object], end: [Object] }
  },
  loc: {
    start: { offset: 22, line: 1, column: 22 },
    end: { offset: 23, line: 1, column: 23 }
  }
}

Looks good to me. But when I go live with it:

(= var1 (=> (a b c)
                 (block ((. console log) a)
                        ((. console log) b)
                        ((. console log) c))))

I get:

[Error] AssignmentExpression `right` member must be an expression node
At line 1, offset 1:

Any ideas?

Kevin

Also validate newer estree nodes

@vuolen noticed that newer estree nodes like ClassDeclaration were erroring when macros output them. I fixed that in a792d03 (published in 0.8.1) the best I can right now… which was by ignoring errors with estree nodes newer than esvalid understands. This should hold up fine technically, but it does mean newer estree nodes won't be sanity-checked before being passed to escodegen for code generation.

At time of writing, esvalid and esutils only understand nodes for standards up to and not including ECMAScript 2015 (i.e. ES6). Patches presumably welcome upstream at estools/esvalid#7 and estools/esutils#20.

`return` requires an argument

In Javascript, return; in a function returns undefined

In eslisp, the built-in return macro requires an argument, so returning without a value requires writing (return undefined)

Should invalid variable names be translated? How?

Observed:

eslc <<< "(hi-there)"

gives hi-there();.

which is obviously very wrong, because hi-there was a single atom, but JavaScript understands hyphens in variable names as the subtraction operator.

Expected: A valid variable name, by some deterministic rules, or a decision to treat invalid characters as undefined or erroneous behaviour.


What should that variable name be?

Other Lisps commonly allow hyphens in variable names, and they're often used to split words, so we could turn them into the equivalent JS idiom (which is camelCase):

hiThere();

LiveScript does this.

Some JS-lisps give other characters such a treatment too:

  • string?isString
  • a->baToB

It's arbitrary what gets converted, and definitely syntactic sugar, which I'd rather leave to user macros.


Could a user macro do that?

One could probably be written that when given an entire program as an argument, recursively translates all atoms according to its rules and returns it.

Needs some experimentation.

Macros aren't referentially transparent

Macros currently use the macro table of where they are called, not where they are defined. This can result in unforeseeable capture of user-defined macros.

Example:

(macro say () (return '(yes))) ; define macro "say"
(macro m () (return `(say)))   ; use "say" in another macro "m"
(macro say () (return '(no)))  ; redefine macro "say"
(m)                            ; call macro "m"

Returns no();. Should be yes();.

Macros should save the macro table when they're defined, and use that when called.

Wikipedia has a good explanation of this too.

Macros returned from a macro aren't in scope for other returned statements

Test patch:

diff --git a/test.ls b/test.ls
index f9fc5ef..610912f 100755
--- a/test.ls
+++ b/test.ls
@@ -815,6 +815,15 @@ test "macro-generating macro" -> # yes srsly
     '''
     ..`@equals` "hello();"

+test "macro generating macro and macro call" -> # yes srsly squared
+  esl '''
+    (macro define-and-call (function (x)
+      (return ((. this multi) `(macro what (function () (return `(hello))))
+                              `(what)))))
+    (define-and-call)
+    '''
+    ..`@equals` "hello();"
+
 test "macros do not capture macros from the outer env by default" ->
   # A macro's environment is ordinarily the clean root macro table; it ignores
   # user-defined macros.

Either the returned macro isn't being entered in the macro table at compilation or the rest of the returned statements can't see it for some reason. Will investigate.

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.