Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Betterify property access, implement ES5 getters and ES6 syntax for objects #41

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ node_js:
- "iojs"
- "4"
- "5.1"

script: make test-all
77 changes: 63 additions & 14 deletions doc/basics-reference.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ generate arbitrary JavaScript are built in to eslisp.
| `regex` | regular expression literal |
| `var` | variable declaration |
| `.` | member expression |
| `get` | *computed* member expression |
| `switch` | switch statement |
| `if` | conditional statement |
| `?:` | ternary expression |
Expand Down Expand Up @@ -291,13 +290,13 @@ elements.
];

Object literals are created with the `object` macro which expects its
parameters to be alternating keys and values.
parameters to be simple pairs keys and values.

<!-- !test in object macro -->

(object)
(object a 1)
(object "a" 1 "b" 2)
(object ('a 1))
(object ("a" 1) ("b" 2))

<!-- !test out object macro -->

Expand All @@ -308,13 +307,63 @@ parameters to be alternating keys and values.
'b': 2
});

ES5 getters and setters can be used.

<!-- !test in object getter setter -->

(var data 0)
(object
(get 'data () (return data))
(set 'data (value) (= data value)))

<!-- !test out object getter setter -->

var data = 0;
({
get data() {
return data;
},
set data(value) {
data = value;
}
});

ES6 methods, property shorthand, computed properties, etc. can be used. Computed
properties can even be used with getters. Generators have not yet been
implemented, though, so generator methods are not available.

<!-- !test in object es6 -->

(var prop 2)
(var data (Symbol "data"))
(object
('prop)
((. Symbol 'toStringTag) "foo")
('method (arg) (return (+ arg 1)))
(get data () (return 1)))

<!-- !test out object es6 -->

var prop = 2;
var data = Symbol('data');
({
prop,
[Symbol.toStringTag]: 'foo',
method(arg) {
return arg + 1;
},
get [data]() {
return 1;
}
});

Property access uses the `.` macro.

<!-- !test in property access macro -->

(. a 1)
(. a b (. c d))
(. a 1 "b" c)
(. a 'b (. c 'd))
(. a 1 "b" 'c)

<!-- !test out property access macro -->

Expand All @@ -325,13 +374,13 @@ Property access uses the `.` macro.
If you wish you could just write those as `a.b.c` in eslisp code, use the
[*eslisp-propertify*][10] user-macro.

For *computed* property access, use the `get` macro.
For *computed* property access, omit the leading colon.

<!-- !test in computed property access macro -->

(get a b)
(get a b c 1)
(= (get a b) 5)
(. a b)
(. a b c 1)
(= (. a b) 5)

<!-- !test out computed property access macro -->

Expand Down Expand Up @@ -398,9 +447,9 @@ the `default`-case clause.
<!-- !test in switch statement -->

(switch x
(1 ((. console log) "it is 1")
(1 ((. console 'log) "it is 1")
(break))
(default ((. console log) "it is not 1")))
(default ((. console 'log) "it is not 1")))

<!-- !test out switch statement -->

Expand Down Expand Up @@ -499,7 +548,7 @@ header, the second to be the right, and the rest to be body statements.
<!-- !test in for-in loop -->

(forin (var x) xs
((. console log) (get xs x)))
((. console 'log) (. xs x)))

<!-- !test out for-in loop -->

Expand Down Expand Up @@ -566,7 +615,7 @@ or `finally`, in which case they are treated as the catch- or finally-clause.
(catch err
(logError err)
(f a b))
(finally ((. console log) "done")))
(finally ((. console 'log) "done")))

<!-- !test out try-catch -->

Expand Down
32 changes: 16 additions & 16 deletions doc/how-macros-work.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ Yey!

We could of course have written the macro function in eslisp instead:

(= (. module exports)
(= (. module 'exports)
(lambda (name)
(return ((. this list)
((. this atom) "=")
(return ((. this 'list)
((. this 'atom) "=")
name
((. this string) "hello")))))
((. this 'string) "hello")))))

That compiles to the same JS before. In fact, you can write macros in any
language you want, as long as you can compile it to JS before `require`-ing it
Expand All @@ -74,12 +74,12 @@ syntax for *quoting*, which makes macro return values much easier to read:
To make macros clearer to read, eslisp has special syntax for returning stuff
that represents code. Let's rewrite the previous hello-assigning macro:

(= (. module exports) (lambda (name) (return `(var ,name "hello"))))
(= (. module 'exports) (lambda (name) (return `(var ,name "hello"))))

That does exactly the same thing, but it contains less of the
`atom`/`list`/`string` constructor fluff, so it's clearer to read. The `(.
this list)` constructor is replaced with a `` ` `` (backtick). The `var` atom
no longer needs to be written explicitly as `((. this atom) var)` and there's
no longer needs to be written explicitly as `((. this 'atom) var)` and there's
now a `,` (comma) before `name`.

In various other Lisp family languages that eslisp is inspired by, the backtick
Expand All @@ -97,10 +97,10 @@ like
module.exports = function (name) {
return {
type : "list",
values : Array.prototype.concat(
values : [].concat(
[ { type : "atom", value : "var" } ],
[ name ],
[ { type : "string" value : "hello" ]
[ { type : "string", value : "hello" } ]
)
};
};
Expand All @@ -117,13 +117,13 @@ expression necessary to calculate the mean of some variables, you could do
(lambda ()

; Convert arguments object to an array
(var argumentsAsArray ((. Array prototype slice call) arguments 0))
(var argumentsAsArray ((. Array 'prototype 'slice 'call) arguments 0))

; Make an eslisp list object from the arguments
(var args ((. this list apply) null argumentsAsArray))
(var args ((. this 'list 'apply) null argumentsAsArray))

; Make an eslisp atom representing the number of arguments
(var total ((. this atom) (. arguments length)))
(var total ((. this 'atom) (. arguments 'length)))

; Return a division of the sum of the arguments by the total
(return `(/ (+ ,@args) ,total))))
Expand Down Expand Up @@ -179,9 +179,9 @@ list.
; Redefine the macro in an inner scope
(macro one (lambda () (return '1.1))) ; "very large value of 1"

((. console log) (one)))
((. console 'log) (one)))

((. console log) (one))
((. console 'log) (one))

<!-- !test out function-expression scope macro -->

Expand Down Expand Up @@ -266,7 +266,7 @@ call it with multiple arguments and return that.
<!-- !test in increment twice -->

(macro incrementTwice
(lambda (x) (return ((. this multi) `(++ ,x) `(++ ,x)))))
(lambda (x) (return ((. this 'multi) `(++ ,x) `(++ ,x)))))

(incrementTwice hello)

Expand All @@ -290,9 +290,9 @@ compile-time:
<!-- !test in precompute -->

(macro precompute
(lambda (list) (return ((. this atom) ((. this evaluate) list)))))
(lambda (list) (return ((. this 'atom) ((. this 'evaluate) list)))))

(precompute (+ 1 2 (* 5 (. Math PI))))
(precompute (+ 1 2 (* 5 (. Math 'PI))))

compiles to

Expand Down
2 changes: 2 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ test-docs: all doc/how-macros-work.markdown doc/basics-reference.markdown
@txm doc/how-macros-work.markdown
@txm doc/basics-reference.markdown

test-all: test test-readme test-docs

.PHONY: all clean test test-readme test-docs
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"chalk": "^1.1.1",
"concat-stream": "^1.4.7",
"convert-source-map": "^1.1.2",
"escodegen": "^1.4.1",
"escodegen": "^1.7.1",
"esutils": "^2.0.2",
"esvalid": "1.1.0",
"nopt": "^3.0.3",
Expand Down
38 changes: 20 additions & 18 deletions readme.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ your own language features, [like this][9].
; Only include given statement if `$DEBUG` environment variable is set
(macro debug
(lambda (statement)
(return (?: (. process env DEBUG)
(return (?: (. process 'env 'DEBUG)
statement
null))))

(var fib ; Fibonacci number sequence
(lambda (x)
(debug ((. console log) (+ "resolving number " x)))
(debug ((. console 'log) (+ "resolving number " x)))
(switch x
(0 (return 0))
(1 (return 1))
Expand Down Expand Up @@ -132,8 +132,9 @@ arguments as the rest:
<!-- !test in simple macros -->

; The "." macro compiles to property access.
(. a b)
(. a b 5 c "yo")
(. a 'b)
(. a 'b c)
(. a 'b 5 'c "yo")

; The "+" macro compiles to addition.
(+ 1 2)
Expand All @@ -143,10 +144,11 @@ arguments as the rest:
<!-- !test out simple macros -->

a.b;
a.b[c];
a.b[5].c['yo'];
1 + 2;

If the `(. a b)` syntax feels tedious, you might like the [eslisp-propertify][34] transform macro, which lets you write `a.b` instead.
If the `(. a 'b)` syntax feels tedious, you might like the [eslisp-propertify][34] transform macro, which lets you write `a.b` instead.

If the first element of a list isn't the name of a macro which is in scope, it
compiles to a function call:
Expand All @@ -171,7 +173,7 @@ These can of course be nested:
(var x (+ 1 (* 2 3)))

; Calling the result of a property access expression
((. console log) "hi")
((. console 'log) "hi")

<!-- !test out nested macros -->

Expand Down Expand Up @@ -293,7 +295,7 @@ Macros can use [`quasiquote`][36] (`` ` ``), `unquote` (`,`) and
<!-- !test in macro and call -->

(macro m (lambda (x) (return `(+ ,x 2))))
((. console log) (m 40))
((. console 'log) (m 40))

<!-- !test out macro and call -->

Expand All @@ -307,9 +309,9 @@ S-expression atom.
<!-- !test in evaluate in macro -->

(macro add2 (lambda (x)
(var xPlusTwo (+ ((. this evaluate) x) 2))
(return ((. this atom) xPlusTwo))))
((. console log) (add2 40))
(var xPlusTwo (+ ((. this 'evaluate) x) 2))
(return ((. this 'atom) xPlusTwo))))
((. console 'log) (add2 40))

<!-- !test out evaluate in macro -->

Expand All @@ -320,8 +322,8 @@ You can return multiple statements from a macro with `this.multi`.
<!-- !test in multiple-return macro -->

(macro log-and-delete (lambda (varName)
(return ((. this multi)
`((. console log) ((. JSON stringify) ,varName))
(return ((. this 'multi)
`((. console 'log) ((. JSON 'stringify) ,varName))
`(delete ,varName)))))

(log-and-delete someVariable)
Expand All @@ -338,9 +340,9 @@ compilation side-effects and conditional compilation.

; Only include statement if `$DEBUG` environment variable is set
(macro debug (lambda (statement)
(return (?: (. process env DEBUG) statement null))))
(return (?: (. process 'env 'DEBUG) statement null))))

(debug ((. console log) "debug output"))
(debug ((. console 'log) "debug output"))
(yep)

<!-- !test out nothing-returning macro -->
Expand All @@ -358,9 +360,9 @@ and the variables in the IIFE are shared between them.
(macro ((lambda ()
(var x 0) ; visible to all of the macro functions
(return
(object increment (lambda () (return ((. this atom) (++ x))))
decrement (lambda () (return ((. this atom) (-- x))))
get (lambda () (return ((. this atom) x))))))))
(object ('increment (lambda () (return ((. this 'atom) (++ x)))))
('decrement (lambda () (return ((. this 'atom) (-- x)))))
('get (lambda () (return ((. this 'atom) x)))))))))

(increment)
(increment)
Expand Down Expand Up @@ -415,7 +417,7 @@ The compiler runs as a [REPL][42] if given no arguments, though it doesn't

You can also just pipe data to it to compile it if you want.

echo '((. console log) "Yo!")' | eslc
echo '((. console 'log) "Yo!")' | eslc

Or pass a filename, like `eslc myprogram.esl`.

Expand Down
Loading