anko / eslisp Goto Github PK
View Code? Open in Web Editor NEWun-opinionated S-expression syntax and macro system for JavaScript
License: ISC License
un-opinionated S-expression syntax and macro system for JavaScript
License: ISC License
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
@jkleiser requested this on gitter and I agree. A read-eval-print-loop would be great to have especially for quick tests.
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?
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;
}
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
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?
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?
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
Is it possible to use ES6/7 features like async/await ? Can you show a working example?
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.
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:
I like the first better.
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?
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
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:
make
dependency. (Alt: Jake? Gulp?)/
-delimited. (Should use path.join
.)node <filename>
in tests. (Windows doesn't recognise the shebang line. @isiahmeadows' #43 addresses this.)Anything I've missed? Opinions? Volunteers?
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.
I was wondering, how would you write the livescript pipe operator |>, in eslisp?
4
|> (+ 1)
|> even
#=> false
Thanks,
Kerry
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.
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?
These currently can't be written:
function asd() { }
var f = (function asd() { });
Raised by @IMPinball in chat.
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.
I want to use macroexpand
and macroexpand-1
like common lisp.
Or eslc
compiler need to output s-expression source code after apply macro.
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)
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:
sed
hack. Dirty, but then it's the users' problem. ;)--wrap
compiler flag. Something like—eslc --wrap="eslisp-propertify,eslisp-camelify" < index.esl > index.js
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.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
.
Sorry for a (likely) newbie question: how does one export default {x, y, z}
?
I was wondering, how would you access JQuery elements in ESLisp?
Thanks,
Kerry
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.
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
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
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?
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
this.evaluate(...)
macros
block defines another function called the same thing).Yep.
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 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:
0b1010 === 10
0o17 ==== 13
0xf0 === 240
42. === 42
This probably should be improved (and is on my TODO list if I can get time).
…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.
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.log
ging 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.
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.
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.
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
<<
ClojureScript is a heavy approach; a full Clojure compiler targeting JavaScript. Unlike eslisp, it requires the JVM and totally overrides JS' semantics.
ClojureScript can be self hosted now, so it will run in the browser without compilation.
@quavmo raised this in chat: only the source code currently has an obvious "here you go, read this" list of all built-in macros.
It should exist in docs.
@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.
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)
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->b
→ aToB
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 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.
—since there are so few.
Eslisp is still 0.x.x, so any sort of stability is too early to expect, but it would still be a nice hack to catch borks (like #50) before they bork.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.