From 5f7f58483965c154bac1716ac17eace6c670bc76 Mon Sep 17 00:00:00 2001 From: impinball Date: Sat, 26 Dec 2015 06:24:38 -0500 Subject: [PATCH 1/5] Betterify property access, implement ES5 getters and ES6 syntax for objects This intentionally reserves generators, because those haven't been implemented in functions yet. It will be easy to add when they are implemented elsewhere, though. --- doc/basics-reference.markdown | 77 ++++++++-- doc/how-macros-work.markdown | 32 ++-- package.json | 5 +- readme.markdown | 38 ++--- src/built-in-macros.ls | 215 ++++++++++++++++++-------- src/translate.ls | 10 +- test.ls | 282 +++++++++++++++++++++++----------- 7 files changed, 453 insertions(+), 206 deletions(-) diff --git a/doc/basics-reference.markdown b/doc/basics-reference.markdown index 9fbe8e4..9d9fa90 100644 --- a/doc/basics-reference.markdown +++ b/doc/basics-reference.markdown @@ -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 | @@ -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. (object) - (object a 1) - (object "a" 1 "b" 2) + (object (:a 1)) + (object ("a" 1) ("b" 2)) @@ -308,13 +307,63 @@ parameters to be alternating keys and values. 'b': 2 }); +ES5 getters and setters can be used. + + + + (var data 0) + (object + (get :data () (return data)) + (set :data (value) (= data value))) + + + + 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. + + + + (var prop 2) + (var data (Symbol "data")) + (object + (:prop) + ((. Symbol :toStringTag) "foo") + (:method (arg) (return (+ arg 1))) + (get data () (return 1))) + + + + 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. (. a 1) - (. a b (. c d)) - (. a 1 "b" c) + (. a :b (. c :d)) + (. a 1 "b" :c) @@ -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. - (get a b) - (get a b c 1) - (= (get a b) 5) + (. a b) + (. a b c 1) + (= (. a b) 5) @@ -398,9 +447,9 @@ the `default`-case clause. (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"))) @@ -499,7 +548,7 @@ header, the second to be the right, and the rest to be body statements. (forin (var x) xs - ((. console log) (get xs x))) + ((. console :log) (. xs x))) @@ -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"))) diff --git a/doc/how-macros-work.markdown b/doc/how-macros-work.markdown index 9dd8604..d42ad72 100644 --- a/doc/how-macros-work.markdown +++ b/doc/how-macros-work.markdown @@ -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 @@ -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 @@ -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" } ] ) }; }; @@ -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)))) @@ -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)) @@ -266,7 +266,7 @@ call it with multiple arguments and return that. (macro incrementTwice - (lambda (x) (return ((. this multi) `(++ ,x) `(++ ,x))))) + (lambda (x) (return ((. this :multi) `(++ ,x) `(++ ,x))))) (incrementTwice hello) @@ -290,9 +290,9 @@ compile-time: (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 diff --git a/package.json b/package.json index 1ea694d..cd60989 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,14 @@ "source-map": "^0.5.3", "tape": "^4.0.0", "tests-ex-markdown": "^2.0.0", - "tmp": "0.0.28" + "tmp": "0.0.28", + "uuid": "^2.0.1" }, "dependencies": { "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", diff --git a/readme.markdown b/readme.markdown index 422c756..b9ef2cf 100644 --- a/readme.markdown +++ b/readme.markdown @@ -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)) @@ -132,8 +132,9 @@ arguments as the rest: ; 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) @@ -143,10 +144,11 @@ arguments as the rest: 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: @@ -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") @@ -293,7 +295,7 @@ Macros can use [`quasiquote`][36] (`` ` ``), `unquote` (`,`) and (macro m (lambda (x) (return `(+ ,x 2)))) - ((. console log) (m 40)) + ((. console :log) (m 40)) @@ -307,9 +309,9 @@ S-expression atom. (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)) @@ -320,8 +322,8 @@ You can return multiple statements from a macro with `this.multi`. (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) @@ -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) @@ -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) @@ -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`. diff --git a/src/built-in-macros.ls b/src/built-in-macros.ls index d98d8c3..755fedd 100644 --- a/src/built-in-macros.ls +++ b/src/built-in-macros.ls @@ -6,6 +6,21 @@ statementify = require \./es-statementify multiple-statements } = require \./import-macro +# Any static identifier is prefixed with `:`, like `(. Math :random)` +is-static-identifier = (node) -> node.type is \Identifier and node.name[0] is \: + +# Returns true if this is computed. +fix-identifier = (node) -> + if is-static-identifier node + node.name .= slice 1 + false + else + true + +filter-identifier = (node) -> + fix-identifier node + node + chained-binary-expr = (type, operator) -> macro = (...args) -> env = this @@ -183,24 +198,127 @@ contents = type : \ArrayExpression elements : elements.map compile - \object : (...args) -> + \object : do + check-list = (list, i) -> + | list? and list.type is \list => list.values + | otherwise => throw Error "Expected property #i to be a list" - { compile } = env = this - if args.length % 2 isnt 0 - throw Error "Expected even number of arguments to object macro, but \ - got #{args.length}" - - keys-values = do # [ [k1, v1], [k2, v2] , ... ] - keys = [] ; values = [] - args.for-each (a, i) -> (if i % 2 then values else keys).push a - zip keys, values - - type : \ObjectExpression - properties : - keys-values.map ([k, v]) -> - type : \Property kind : \init - value : compile v - key : compile k + infer-name = (prefix, name, computed) -> + if computed + prefix + else if typeof name.type is \Literal + "#prefix #{name.value}" + else + "#prefix #{name.name}" + + compile-get-set = (i, type, [name, params, ...body]) -> + if not name? + throw Error "Expected #{type}ter in property #i to have a name" + + name = @compile name + computed = fix-identifier name and typeof name.type isnt \Literal + kind = infer-name "#{type}ter", name, computed + + if not params? or params.type isnt \list + throw Error "Expected #{kind} in property #i to have a parameter list" + + params .= values + + # Catch this error here, to return a more sensible, helpful error message + # than merely an InvalidAstError referencing property names from the + # stringifier itself. + if type is \get + if params.length isnt 0 + throw Error "Expected #{kind} in property #i to have no parameters" + else # type is \set + if params.length isnt 1 + throw Error "Expected #{kind} in property #i to have exactly one \ + parameter" + param = params.0 + if param.type isnt \atom or param.value.0 is \: + throw Error "Expected parameter for #{kind} in property #i to be an \ + identifier" + params = [ + type : \Identifier + name : param.value + ] + + type : \Property + kind : type + key : name + computed : computed + value : + type : \FunctionExpression + id : null + params : params + body : optionally-implicit-block-statement this, body + expression : false + + compile-method = (i, [name, params, ...body]) -> + if not name? + throw Error "Expected method in property #i to have a name" + + name = @compile name + computed = fix-identifier name and name.type isnt \Literal + method = infer-name 'method', name, computed + + if not params? or params.type isnt \list + throw Error "Expected #method in property #i to have a parameter \ + list" + + params = for param, j in params.values + if param.type isnt \atom or param.value.0 is \: + throw Error "Expected parameter #j for #method in property #i to be \ + an identifier" + type : \Identifier + name : param.value + + type : \Property + kind : \init + method : true + computed : computed + key : name + value : + type : \FunctionExpression + id : null + params : params + body : optionally-implicit-block-statement this, body + expression : false + + compile-list = (i, args) -> + | args.length is 0 => + throw Error "Expected at least two arguments in property #i" + | args.length is 1 => + name = @compile args.0 + if fix-identifier name + throw Error "Expected name in property #i to be a symbol identifier" + type : \Property + kind : \init + key : name + value : name + shorthand : true + | args.length is 2 => + key = @compile args.0 + computed = fix-identifier key and key.type isnt \Literal + type : \Property + kind : \init + computed : computed + key : key + value : filter-identifier @compile args.1 + # Check this before compilation and macro resolution to ensure that + # neither can affect this, but that it can be avoided in the edge case if + # needed with `(id get)` or `(id set)`, where `(macro id (lambda (x) x))`. + | args.0.type is \atom and args.0.value in <[get set]> => + compile-get-set.call this, i, args.0.value, args[1 til] + # Reserve this for future generator use. + | args.0.type is \atom and args.0.value is \* => + throw Error "Unexpected generator method in property #i" + | otherwise => compile-method.call this, i, args + + -> + type : \ObjectExpression + properties : for args, i in arguments + compile-list.call this, i, (check-list args, i) \var : do declaration = (...args) -> @@ -309,55 +427,24 @@ contents = argument : compile arg \. : do - - is-computed-property = (ast-node) -> - switch ast-node.type - | \Identifier => false - | otherwise => true - - dot = (...args) -> - - { compile } = env = this - + join-members = (host, prop) -> + property-compiled = @compile prop + computed = fix-identifier property-compiled + type : \MemberExpression + computed : computed + object : host + property : property-compiled + + (host) -> switch - | args.length is 1 => compile args.0 - | args.length is 2 - property-compiled = compile args.1 - type : \MemberExpression - computed : is-computed-property property-compiled - object : compile args.0 - property : property-compiled - | arguments.length > 2 - [ ...initial, last ] = args - dot.call do - env - dot.apply env, initial - dot.call env, compile last + | &length is 0 => throw Error "dot called with no arguments" + | &length is 1 => @compile host + | &length is 2 => join-members.call this, (@compile host), &1 | otherwise => - throw Error "dot called with no arguments" - - \get : do - get = (...args) -> - - { compile } = env = this - - switch - | args.length is 1 => compile args.0 - | args.length is 2 - property-compiled = compile args.1 - type : \MemberExpression - computed : true # `get` is always computed - object : compile args.0 - property : property-compiled - | arguments.length > 2 - [ ...initial, last ] = args - get.call do - env - get.apply env, initial - get.call env, compile last - | otherwise => - throw Error "dot called with no arguments" - + host = @compile host + for i from 1 til &length + host = join-members.call this, host, &[i] + host \lambda : function-type \FunctionExpression diff --git a/src/translate.ls b/src/translate.ls index 46e76e3..54116ad 100644 --- a/src/translate.ls +++ b/src/translate.ls @@ -1,7 +1,7 @@ # Turns an internal AST form into an estree object with reference to the given # root environment. Throws error unless the resulting estree AST is valid. -{ concat-map } = require \prelude-ls +{ concat-map, reject } = require \prelude-ls root-macro-table = require \./built-in-macros statementify = require \./es-statementify environment = require \./env @@ -30,7 +30,13 @@ module.exports = (root-env, ast, options={}) -> |> (.filter (isnt null)) # because macro definitions emit null |> (.map statementify) - err = errors program-ast + err = errors program-ast |> reject ({node}) -> + # These are valid ES6 nodes, and their errors need to be ignored. See + # https://github.com/estools/esvalid/issues/7. + | node.type is \Property => + node.computed and node.key?.type not in <[Identifier Literal]> + | otherwise => false + if err.length first-error = err.0 throw first-error diff --git a/test.ls b/test.ls index d1e4178..eed3897 100755 --- a/test.ls +++ b/test.ls @@ -239,7 +239,7 @@ test "return statement" -> ..`@equals` "(function () {\n return 'hello there';\n});" test "member expression" -> - esl "(. console log)" + esl "(. console :log)" ..`@equals` "console.log;" test "explicit block statement" -> @@ -251,17 +251,17 @@ test "call expression" -> ..`@equals` "f();" test "member, then call with arguments" -> - esl '((. console log) "hi")' + esl '((. console :log) "hi")' ..`@equals` "console.log('hi');" test "func with member and call in it" -> - esl "(lambda (x) ((. console log) x))" + esl "(lambda (x) ((. console :log) x))" ..`@equals` "(function (x) {\n console.log(x);\n});" test "switch statement" -> esl ''' (switch (y) - ((== x 5) ((. console log) "hi") (break)) + ((== x 5) ((. console :log) "hi") (break)) (default (yes))) ''' ..`@equals` """ @@ -275,7 +275,7 @@ test "switch statement" -> """ test "if-statement with blocks" -> - esl '(if (+ 1 0) (block ((. console log) "yes") (x)) (block 0))' + esl '(if (+ 1 0) (block ((. console :log) "yes") (x)) (block 0))' ..`@equals` """ if (1 + 0) { console.log(\'yes\'); @@ -295,7 +295,7 @@ test "if-statement with expressions" -> """ test "if-statement without alternate" -> - esl '(if (+ 1 0) (block ((. console log) "yes") (x)))' + esl '(if (+ 1 0) (block ((. console :log) "yes") (x)))' ..`@equals` """ if (1 + 0) { console.log(\'yes\'); @@ -309,8 +309,8 @@ test "ternary expression" -> test "while loop with explicit body" -> esl '(while (-- n) (block - ((. console log) "ok") - ((. console log) "still ok")))' + ((. console :log) "ok") + ((. console :log) "still ok")))' ..`@equals` "while (--n) {\n console.log('ok');\n console.log('still ok');\n}" test "while loop with explicit body that contains a block" -> @@ -319,29 +319,29 @@ test "while loop with explicit body that contains a block" -> ..`@equals` "while (--n) {\n {\n a;\n }\n}" test "while loop with implicit body" -> - esl '(while (-- n) ((. console log) "ok") - ((. console log) "still ok"))' + esl '(while (-- n) ((. console :log) "ok") + ((. console :log) "still ok"))' ..`@equals` "while (--n) {\n console.log('ok');\n console.log('still ok');\n}" test "do/while loop with implicit body" -> - esl '(dowhile (-- n) ((. console log) "ok") - ((. console log) "still ok"))' + esl '(dowhile (-- n) ((. console :log) "ok") + ((. console :log) "still ok"))' ..`@equals` "do {\n console.log('ok');\n console.log('still ok');\n} while (--n);" test "do/while loop with explicit body" -> esl '(dowhile (-- n) (block - ((. console log) "ok") - ((. console log) "still ok")))' + ((. console :log) "ok") + ((. console :log) "still ok")))' ..`@equals` "do {\n console.log('ok');\n console.log('still ok');\n} while (--n);" test "for loop with implicit body" -> - esl '(for (var x 1) (< x 10) (++ x) ((. console log) "ok") - ((. console log) "still ok"))' + esl '(for (var x 1) (< x 10) (++ x) ((. console :log) "ok") + ((. console :log) "still ok"))' ..`@equals` "for (var x = 1; x < 10; ++x) {\n console.log('ok');\n console.log('still ok');\n}" test "for loop with explicit body" -> - esl '(for (var x 1) (< x 10) (++ x) (block ((. console log) "ok") - ((. console log) "still ok")))' + esl '(for (var x 1) (< x 10) (++ x) (block ((. console :log) "ok") + ((. console :log) "still ok")))' ..`@equals` "for (var x = 1; x < 10; ++x) {\n console.log('ok');\n console.log('still ok');\n}" test "for loop with no body" -> @@ -349,36 +349,36 @@ test "for loop with no body" -> ..`@equals` "for (var x = 1; x < 10; ++x) {\n}" test "for loop with null update" -> - esl '(for (var x 1) (< x 10) () ((. console log) "ok") - ((. console log) "still ok"))' + esl '(for (var x 1) (< x 10) () ((. console :log) "ok") + ((. console :log) "still ok"))' ..`@equals` "for (var x = 1; x < 10;) {\n console.log('ok');\n console.log('still ok');\n}" test "for loop with null init, update and test" -> - esl '(for () () () ((. console log) "ok") - ((. console log) "still ok"))' + esl '(for () () () ((. console :log) "ok") + ((. console :log) "still ok"))' ..`@equals` "for (;;) {\n console.log('ok');\n console.log('still ok');\n}" test "for-in loop with implicit body" -> - esl '(forin (var x) xs ((. console log) x))' + esl '(forin (var x) xs ((. console :log) x))' ..`@equals` "for (var x in xs) {\n console.log(x);\n}" test "for-in loop with explicit body" -> - esl '(forin (var x) xs (block ((. console log) x)))' + esl '(forin (var x) xs (block ((. console :log) x)))' ..`@equals` "for (var x in xs) {\n console.log(x);\n}" test "multiple statements in program" -> - esl '((. console log) "hello") ((. console log) "world")' + esl '((. console :log) "hello") ((. console :log) "world")' ..`@equals` "console.log('hello');\nconsole.log('world');" test "function with implicit block body" -> - esl '(lambda (x) ((. console log) "hello") \ - ((. console log) "world"))' + esl '(lambda (x) ((. console :log) "hello") \ + ((. console :log) "world"))' ..`@equals` "(function (x) {\n console.log(\'hello\');\n console.log(\'world\');\n});" test "function with explicit block body" -> esl '(lambda (x) (block - ((. console log) "hello") \ - ((. console log) "world")))' + ((. console :log) "hello") \ + ((. console :log) "world")))' ..`@equals` "(function (x) {\n console.log(\'hello\');\n console.log(\'world\');\n});" test "new statement" -> @@ -546,14 +546,14 @@ test "quoting atoms produces an object representing it" -> ..value `@equals` "fun" test "simple quoting macro" -> - esl "(macro random (lambda () (return '((. Math random))))) + esl "(macro random (lambda () (return '((. Math :random))))) (+ (random) (random))" ..`@equals` "Math.random() + Math.random();" test "macro constructor given object imports properties as macros" -> esl ''' - (macro (object a (lambda () (return '"hi a")) - b (lambda () (return '"hi b")))) + (macro (object (:a (lambda () (return '"hi a"))) + (:b (lambda () (return '"hi b"))))) (a) (b) ''' ..`@equals` "'hi a';\n'hi b';" @@ -580,7 +580,7 @@ test "nothing-returning macro" -> test "macros mask others defined before with the same name" -> esl "(macro m (lambda () (return ()))) - (macro m (lambda () (return '((. console log) \"hi\")))) + (macro m (lambda () (return '((. console :log) \"hi\")))) (m)" ..`@equals` "console.log('hi');" @@ -615,20 +615,20 @@ test "dead simple quasiquote" -> test "quasiquote is like quote if no unquotes contained" -> esl "(macro rand (lambda () (return `(* 5 - ((. Math random)))))) + ((. Math :random)))))) (rand)" ..`@equals` "5 * Math.random();" test "macros can quasiquote to unquote arguments into output" -> esl "(macro rand (lambda (upper) (return `(* ,upper - ((. Math random)))))) + ((. Math :random)))))) (rand 5)" ..`@equals` "5 * Math.random();" test "macro env can create atoms out of strings or numbers" -> esl """ - (macro m (lambda () (return ((. this atom) 42)))) + (macro m (lambda () (return ((. this :atom) 42)))) (m)""" ..`@equals` "42;" @@ -640,17 +640,17 @@ test "macro env can create sexpr AST nodes equivalently to quoting" -> with-construct = esl """ (macro m (lambda () - (return ((. this list) - ((. this atom) "a") - ((. this string) "b"))))) + (return ((. this :list) + ((. this :atom) "a") + ((. this :string) "b"))))) (m)""" with-quote `@equals` with-construct test "macros can evaluate number arguments to JS and convert them back again" -> esl """ (macro incrementedTimesTwo (lambda (x) - (var y (+ 1 ((. this evaluate) x))) - (var xAsSexpr ((. this atom) ((. y toString)))) + (var y (+ 1 ((. this :evaluate) x))) + (var xAsSexpr ((. this :atom) ((. y :toString)))) (return `(* ,xAsSexpr 2)))) (incrementedTimesTwo 5) """ @@ -661,9 +661,9 @@ test "macros can evaluate object arguments" -> # to an object, then stringifies it. esl """ (macro objectAsString (lambda (input) - (= obj ((. this evaluate) input)) - (return ((. this string) ((. JSON stringify) obj))))) - (objectAsString (object a 1)) + (= obj ((. this :evaluate) input)) + (return ((. this :string) ((. JSON :stringify) obj))))) + (objectAsString (object (:a 1))) """ ..`@equals` "'{\"a\":1}';" @@ -672,10 +672,10 @@ test "macros can evaluate statements" -> # statement does not evaluate to a value, so we check for undefined. esl """ (macro evalThis (lambda (input) - (= obj ((. this evaluate) input)) + (= obj ((. this :evaluate) input)) (if (=== obj undefined) - (return ((. this atom) "yep")) - (return ((. this atom) "nope"))))) + (return ((. this :atom) "yep")) + (return ((. this :atom) "nope"))))) (evalThis (if 1 (block) (block))) """ ..`@equals` "yep;" @@ -702,8 +702,8 @@ test "quasiquote can contain nested lists" -> (lambda () ; Convert arguments into array (var args - ((. this list apply) null ((. Array prototype slice call) arguments 0))) - (var total ((. this atom) ((. (. args values length) toString)))) + ((. this :list :apply) null ((. Array :prototype :slice :call) arguments 0))) + (var total ((. this :atom) ((. (. args :values :length) :toString)))) (return `(/ (+ ,@args) ,total)))) (mean 1 2 3) ''' @@ -718,20 +718,118 @@ test "array macro can be empty" -> ..`@equals` "[];" test "object macro produces object expression" -> - esl "(object a 1 b 2)" + esl "(object (:a 1) (:b 2))" ..`@equals` "({\n a: 1,\n b: 2\n});" test "object macro can be passed strings as keys too" -> - esl '(object "a" 1 "b" 2)' + esl '(object ("a" 1) ("b" 2))' ..`@equals` "({\n 'a': 1,\n 'b': 2\n});" test "object macro's value parts can be expressions" -> - esl '(object "a" (+ 1 2) "b" (f x))' + esl '(object ("a" (+ 1 2)) ("b" (f x)))' ..`@equals` "({\n 'a': 1 + 2,\n 'b': f(x)\n});" -# dynamic *keys* would be ES6 + +test "object macro's parts can be ES6 shorthands" -> + esl '(object (:a) (:b))' + ..`@equals` "({\n a,\n b\n});" + +test "object macro's key parts can be computed ES6 values" -> + esl '(object (a (+ 1 2)) (b (f x)))' + ..`@equals` "({\n [a]: 1 + 2,\n [b]: f(x)\n});" + +test "object macro can create getters" -> + esl '(object (get :a () (return 1)))' + ..`@equals` '({\n get a() {\n return 1;\n }\n});' + +test "object macro can create setters" -> + esl '(object (set :a (x) (return 1)))' + ..`@equals` '({\n set a(x) {\n return 1;\n }\n});' + +test "object macro can create computed getters and setters" -> + esl '(object (get a ()) (set a (x)))' + ..`@equals` ''' + ({ + get [a]() { + }, + set [a](x) { + } + }); + ''' + +test "object macro's parts can be ES6 methods" -> + esl ''' + (object + (:a () (return 1)) + (:b (x) (return (+ x 1))) + (c (x y) (return (+ x y 1)))) + ''' + ..`@equals` """ + ({ + a() { + return 1; + }, + b(x) { + return x + 1; + }, + [c](x, y) { + return x + (y + 1); + } + }); + """ + +test "object macro compiles complex ES6 object" -> + esl ''' + (object + (:prop) + (:_foo 1) + ((. Symbol :toStringTag) "Foo") + + (get :foo () + (return (. this :_foo))) + + (set :foo (value) + (= (. this :_foo) value)) + + (get (. syms :Sym) () + (return ((. wm :get) this))) + + (set (. syms :Sym) (value) + ((. wm :set) this value)) + + (:printFoo () + ((. console :log) (. this :foo))) + + (:concatFoo (value) + (return (+ (. this :foo) value)))) + ''' + ..`@equals` ''' + ({ + prop, + _foo: 1, + [Symbol.toStringTag]: 'Foo', + get foo() { + return this._foo; + }, + set foo(value) { + this._foo = value; + }, + get [syms.Sym]() { + return wm.get(this); + }, + set [syms.Sym](value) { + wm.set(this, value); + }, + printFoo() { + console.log(this.foo); + }, + concatFoo(value) { + return this.foo + value; + } + }); + ''' test "macro producing an object literal" -> - esl "(macro obj (lambda () (return '(object a 1)))) + esl "(macro obj (lambda () (return '(object (:a 1))))) (obj)" ..`@equals` "({ a: 1 });" @@ -742,29 +840,33 @@ test "macro producing a function" -> ..`@equals` "(function (x) {\n return x + 3;\n});" test "property access (dotting) chains identifiers" -> - esl "(. a b c)" + esl "(. a :b :c)" ..`@equals` "a.b.c;" +test "property access (dotting) chains computed identifiers" -> + esl "(. a b c)" + ..`@equals` "a[b][c];" + test "property access (dotting) chains literals" -> esl "(. a 1 2)" ..`@equals` "a[1][2];" test "property access (dotting) can be nested" -> - esl "(. a (. a (. b name)))" + esl "(. a (. a (. b :name)))" ..`@equals` "a[a[b.name]];" test "property access (dotting) chains mixed literals and identifiers" -> - esl "(. a b 2 a)" + esl "(. a :b 2 :a)" ..`@equals` "a.b[2].a;" +test "property access (dotting) chains mixed literals and values" -> + esl "(. a b 2 :a)" + ..`@equals` "a[b][2].a;" + test "property access (dotting) treats strings as literals, not identifiers" -> esl "(. a \"hi\")" ..`@equals` "a['hi'];" -test "computed member expression (\"square brackets\")" -> - esl "(get a b 5)" - ..`@equals` "a[b][5];" - test "regex literal" -> esl '(regex ".*")' ..`@equals` "/.*/;" @@ -788,7 +890,7 @@ test "regex can be given atoms with escaped spaces and slashes" -> test "macro deliberately breaking hygiene for function argument anaphora" -> esl "(macro : (lambda (body) (return `(lambda (it) ,body)))) - (: (return (. it x)))" + (: (return (. it :x)))" ..`@equals` "(function (it) {\n return it.x;\n});" test "macro given nothing produces no output" -> @@ -802,12 +904,12 @@ test "when returned from an IIFE, macros can share state" -> (macro ((lambda () (var x 0) (return (object - plusPrev (lambda (n) - (+= x ((. this evaluate) n)) - (return ((. this atom) ((. x toString))))) - timesPrev (lambda (n) - (*= x ((. this evaluate) n)) - (return ((. this atom) ((. x toString)))))))))) + (:plusPrev (lambda (n) + (+= x ((. this :evaluate) n)) + (return ((. this :atom) ((. x :toString)))))) + (:timesPrev (lambda (n) + (*= x ((. this :evaluate) n)) + (return ((. this :atom) ((. x :toString))))))))))) (plusPrev 2) (timesPrev 2) """ ..`@equals` "2;\n4;" @@ -823,17 +925,17 @@ test "macro constructor loading from IIFE can load nothing" -> ..`@equals` "" test "macro can return multiple statements with `multi`" -> - esl "(macro declareTwo (lambda () (return ((. this multi) '(var x 0) '(var y 1))))) + esl "(macro declareTwo (lambda () (return ((. this :multi) '(var x 0) '(var y 1))))) (declareTwo)" ..`@equals` "var x = 0;\nvar y = 1;" test "macro can check argument type and get its value" -> esl ''' (macro stringy (lambda (x) - (if (== (. x type) "atom") - (return ((. this string) (+ "atom:" (. x value)))) + (if (== (. x :type) "atom") + (return ((. this :string) (+ "atom:" (. x :value)))) (block - (if (== (. x type) "string") + (if (== (. x :type) "string") (return x) (return "An unexpected development!")))))) (stringy a) @@ -846,7 +948,7 @@ test "macro returning atom with empty or null name fails" -> <[ "" null undefined ]>.for-each -> self.throws do -> esl """ - (macro mac (lambda () (return ((. this atom) #it)))) + (macro mac (lambda () (return ((. this :atom) #it)))) (mac) """ Error @@ -868,7 +970,7 @@ test "macros can be required relative to root directory" -> main-path = path.join dir-name, main-basename main-fd = fs.open-sync main-path, \a+ fs.write-sync main-fd, """ - (macro (object x (require "./#module-basename"))) + (macro (object (:x (require "./#module-basename")))) (x) """ @@ -905,7 +1007,7 @@ test "macros can be required from node_modules relative to root directory" -> # Attempt to require it and use it as a macro esl """ - (macro (object x (require "#module-name"))) + (macro (object (:x (require "#module-name")))) (x) """ ..`@equals` "" @@ -927,7 +1029,7 @@ test "macros required from separate modules can access complation env" -> """ code = esl """ - (macro (object x (require "#name"))) + (macro (object (:x (require "#name")))) (x) """ @@ -972,7 +1074,7 @@ test "macro-generating macro" -> # yes srsly test "macro generating macro and macro call" -> # yes srsly squared esl ''' (macro define-and-call (lambda (x) - (return ((. this multi) `(macro what (lambda () (return `(hello)))) + (return ((. this :multi) `(macro what (lambda () (return `(hello)))) `(what))))) (define-and-call) ''' @@ -1011,7 +1113,7 @@ test "invalid AST returned by macro throws error" -> test "macro multi-returning with bad values throws descriptive error" -> try esl ''' - (macro breaking (lambda () (return ((. this multi) null)))) + (macro breaking (lambda () (return ((. this :multi) null)))) (breaking) ''' catch e @@ -1033,8 +1135,8 @@ test "macro return intermediates may be invalid if fixed by later macro" -> test "macro can return estree object" -> esl ''' (macro identifier (lambda () - (return (object "type" "Identifier" - "name" "x")))) + (return (object ("type" "Identifier") + ("name" "x"))))) (identifier) ''' ..`@equals` "x;" @@ -1042,11 +1144,11 @@ test "macro can return estree object" -> test "macro can multi-return estree objects" -> esl ''' (macro identifiers (lambda () - (return ((. this multi) - (object "type" "Identifier" - "name" "x") - (object "type" "Identifier" - "name" "y"))))) + (return ((. this :multi) + (object ("type" "Identifier") + ("name" "x")) + (object ("type" "Identifier") + ("name" "y")))))) (identifiers) ''' ..`@equals` "x;\ny;" @@ -1054,9 +1156,9 @@ test "macro can multi-return estree objects" -> test "macro can multi-return a combination of estree and sexprs" -> esl ''' (macro identifiers (lambda () - (return ((. this multi) - (object "type" "Identifier" - "name" "x") + (return ((. this :multi) + (object ("type" "Identifier") + ("name" "x")) 'x)))) (identifiers) ''' @@ -1065,11 +1167,11 @@ test "macro can multi-return a combination of estree and sexprs" -> test "macro can compile and return parameter as estree" -> esl ''' (macro that (lambda (x) - (return ((. this compile) x)))) + (return ((. this :compile) x)))) (that 3) (that "hi") (that (c)) - (that (object a b)) + (that (object (:a b))) ''' ..`@equals` "3;\n'hi';\nc();\n({ a: b });" From 38b1cebbae20aeab26e89a956a1cef0206a0dfa2 Mon Sep 17 00:00:00 2001 From: impinball Date: Sat, 2 Jan 2016 10:34:06 -0500 Subject: [PATCH 2/5] Add `test-all` target, resolve rest of #42's changes. --- makefile | 2 ++ src/built-in-macros.ls | 59 +++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/makefile b/makefile index 353da54..73e27ca 100644 --- a/makefile +++ b/makefile @@ -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 diff --git a/src/built-in-macros.ls b/src/built-in-macros.ls index 7525832..37bd1e8 100644 --- a/src/built-in-macros.ls +++ b/src/built-in-macros.ls @@ -478,9 +478,7 @@ contents = finalizer : finally-clause \macro : -> - env = this - - compile-as-macro = (es-ast) -> + compile-as-macro = (es-ast) ~> # This hack around require makes loading macros from relative paths work. # @@ -501,7 +499,7 @@ contents = root-require = main.require.bind main let require = root-require - eval "(#{env.compile-to-js es-ast})" + eval "(#{@compile-to-js es-ast})" switch &length | 1 => @@ -511,21 +509,21 @@ contents = # Mask any macro of that name in the current scope - import-compilerspace-macro env, form.value, null + import-compilerspace-macro this, form.value, null | otherwise # Attempt to compile the argument, hopefully into an object, # define macros from its keys - es-ast = env.compile form + es-ast = @compile form result = compile-as-macro es-ast switch typeof! result | \Object => for k, v of result - import-compilerspace-macro env, k, v + import-compilerspace-macro this, k, v | \Null => fallthrough | \Undefined => # do nothing | otherwise => @@ -540,19 +538,19 @@ contents = name = name.value target-name = form.value - alias-target-macro = env.find-macro target-name + alias-target-macro = @find-macro target-name if not alias-target-macro throw Error "Macro alias target `#target-name` is not defined" - import-compilerspace-macro env, name, alias-target-macro + import-compilerspace-macro this, name, alias-target-macro | form.type is \list - userspace-macro = form |> env.compile |> compile-as-macro + userspace-macro = form |> @compile |> compile-as-macro name .= value - import-compilerspace-macro env, name, userspace-macro + import-compilerspace-macro this, name, userspace-macro | otherwise => throw Error "Bad number of arguments to macro constructor \ @@ -567,23 +565,24 @@ contents = # means we have to resolve lists which first atom is `unquote` or # `unquote-splicing` into either an array of values or an identifier to # an array of values. - qq-body = (env, ast) -> - recurse-on = (ast-list) -> + qq-body = (ast) -> + + recurse-on = (ast-list) ~> ast-list.values - |> map qq-body env, _ + |> map qq-body.bind this |> generate-concat - unquote = -> - if arguments.length isnt 1 + unquote = ~> + if &length isnt 1 throw Error "Expected 1 argument to unquote but got #{rest.length}" # Unquoting should compile to just the thing separated with an array # wrapper. - [ env.compile it ] + [ @compile it ] - unquote-splicing = -> - if arguments.length isnt 1 + unquote-splicing = ~> + if &length isnt 1 throw Error "Expected 1 argument to unquoteSplicing but got #{rest.length}" @@ -592,8 +591,7 @@ contents = type : \MemberExpression computed : false - object : - env.compile it + object : @compile it property : type : \Identifier name : \values @@ -604,19 +602,19 @@ contents = switch | not head? # quote an empty list - [ quote.call env, { + [ quote.call this, { type : \list values : [] - location :"returned from macro" + location : "returned from macro" } ] | head.type is \atom => switch head.value - | \unquote => unquote .apply null rest - | \unquote-splicing => unquote-splicing.apply null rest + | \unquote => unquote ...rest + | \unquote-splicing => unquote-splicing ...rest | _ => [ recurse-on ast ] | _ => [ recurse-on ast ] - | _ => [ quote.call env, ast ] + | _ => [ quote.call this, ast ] generate-concat = (concattable-things) -> @@ -668,9 +666,6 @@ contents = arguments : it ] qq = (arg) -> - - env = this - if &length > 1 throw Error "Too many arguments to quasiquote (`); \ expected 1, got #{&length}" @@ -681,14 +676,14 @@ contents = if first-arg.type is \atom and first-arg.value is \unquote rest = arg.values.slice 1 .0 - env.compile rest + @compile rest else arg.values - |> map qq-body env, _ + |> map qq-body.call this, _ |> generate-concat - else quote.call env, arg # act like regular quote + else quote.call this, arg # act like regular quote module.exports = parent : null From 48e9f92f8002a510a538e1654d6c8e1776851b06 Mon Sep 17 00:00:00 2001 From: impinball Date: Tue, 26 Jan 2016 05:34:15 -0500 Subject: [PATCH 3/5] Use single quote instead of colon --- src/built-in-macros.ls | 137 ++++++++++++++++++----------- test.ls | 192 ++++++++++++++++++++--------------------- 2 files changed, 184 insertions(+), 145 deletions(-) diff --git a/src/built-in-macros.ls b/src/built-in-macros.ls index 37bd1e8..231f5ca 100644 --- a/src/built-in-macros.ls +++ b/src/built-in-macros.ls @@ -6,21 +6,6 @@ statementify = require \./es-statementify multiple-statements } = require \./import-macro -# Any static identifier is prefixed with `:`, like `(. Math :random)` -is-static-identifier = (node) -> node.type is \Identifier and node.name[0] is \: - -# Returns true if this is computed. -fix-identifier = (node) -> - if is-static-identifier node - node.name .= slice 1 - false - else - true - -filter-identifier = (node) -> - fix-identifier node - node - chained-binary-expr = (type, operator) -> macro = -> | &length is 0 => @@ -114,6 +99,31 @@ function-type = (type) -> (params, ...rest) -> params : params body : optionally-implicit-block-statement this, rest +is-atom = (node, name) -> node.type is \atom and node.value is name + +unwrap-quote = (node, string-is-computed) -> + | node.type is \list and node.values.0 `is-atom` \quote => + computed : false + node : node.values.1 + | otherwise => + computed : true + node : node + +# For some final coercion after compilation, when building the ESTree AST. +coerce-property = (node, computed, string-is-computed) -> + # This should be explicitly overridden and unconditional. Helps with minifiers + # and other things. + | string-is-computed and + node.type is \Literal and + typeof node.value isnt \object => + node : + type : \Literal + value : node.value + '' + computed : false + | otherwise => + node : node + computed : computed + contents = \+ : n-ary-expr \+ \- : n-ary-expr \- @@ -194,11 +204,16 @@ contents = if not name? throw Error "Expected #{type}ter in property #i to have a name" - name = @compile name - computed = fix-identifier name and typeof name.type isnt \Literal + {node, computed} = unwrap-quote name, true + + unless computed or node.type is \atom + throw Error "Expected name of #{type}ter in property #i to be a quoted + atom or an expression" + + {node : name, computed} = coerce-property (@compile node), computed, true kind = infer-name "#{type}ter", name, computed - if not params? or params.type isnt \list + unless params?.type is \list throw Error "Expected #{kind} in property #i to have a parameter list" params .= values @@ -214,7 +229,7 @@ contents = throw Error "Expected #{kind} in property #i to have exactly one \ parameter" param = params.0 - if param.type isnt \atom or param.value.0 is \: + if param.type isnt \atom throw Error "Expected parameter for #{kind} in property #i to be an \ identifier" params = [ @@ -225,6 +240,7 @@ contents = type : \Property kind : type key : name + # The initial check doesn't cover the compiled case. computed : computed value : type : \FunctionExpression @@ -237,8 +253,13 @@ contents = if not name? throw Error "Expected method in property #i to have a name" - name = @compile name - computed = fix-identifier name and name.type isnt \Literal + {node, computed} = unwrap-quote name, true + + unless computed or node.type is \atom + throw Error "Expected name of method in property #i to be a quoted atom + or an expression" + + {node : name, computed} = coerce-property (@compile node), computed, true method = infer-name 'method', name, computed if not params? or params.type isnt \list @@ -246,7 +267,7 @@ contents = list" params = for param, j in params.values - if param.type isnt \atom or param.value.0 is \: + if param.type isnt \atom throw Error "Expected parameter #j for #method in property #i to be \ an identifier" type : \Identifier @@ -267,31 +288,53 @@ contents = compile-list = (i, args) -> | args.length is 0 => throw Error "Expected at least two arguments in property #i" + | args.length is 1 => - name = @compile args.0 - if fix-identifier name - throw Error "Expected name in property #i to be a symbol identifier" + node = args.0 + + if node.type isnt \list + throw Error "Expected name in property #i to be a quoted atom" + + [type, node] = node.values + + unless type `is-atom` \quote and node.type is \atom + throw Error "Expected name in property #i to be a quoted atom" + type : \Property kind : \init - key : name - value : name + key : + type : \Identifier + name : node.value + value : + type : \Identifier + name : node.value shorthand : true + | args.length is 2 => - key = @compile args.0 - computed = fix-identifier key and key.type isnt \Literal + {node, computed} = unwrap-quote args.0, true + + if not computed and node.type isnt \atom + throw Error "Expected name of property #i to be an expression or + quoted atom" + + {node : key, computed} = coerce-property (@compile node), computed, true + type : \Property kind : \init computed : computed key : key - value : filter-identifier @compile args.1 + value : @compile args.1 + # Check this before compilation and macro resolution to ensure that # neither can affect this, but that it can be avoided in the edge case if # needed with `(id get)` or `(id set)`, where `(macro id (lambda (x) x))`. - | args.0.type is \atom and args.0.value in <[get set]> => + | args.0 `is-atom` \get or args.0 `is-atom` \set => compile-get-set.call this, i, args.0.value, args[1 til] + # Reserve this for future generator use. - | args.0.type is \atom and args.0.value is \* => + | args.0.type `is-atom` \* => throw Error "Unexpected generator method in property #i" + | otherwise => compile-method.call this, i, args -> @@ -390,12 +433,17 @@ contents = \. : do join-members = (host, prop) -> - property-compiled = @compile prop - computed = fix-identifier property-compiled + {node, computed} = unwrap-quote prop, false + + if not computed and node.type isnt \atom + throw Error "Expected quoted name of property getter to be an atom" + + {node : prop, computed} = coerce-property (@compile node), computed, false + type : \MemberExpression computed : computed object : host - property : property-compiled + property : prop (host) -> switch @@ -441,9 +489,7 @@ contents = \try : do is-part = (thing, clause-name) -> - if not (thing.type is \list) then return false - first = thing.values.0 - (first.type is \atom) && (first.value is clause-name) + thing.type is \list and thing.values.0 `is-atom` clause-name (...args) -> catch-part = null @@ -524,8 +570,7 @@ contents = | \Object => for k, v of result import-compilerspace-macro this, k, v - | \Null => fallthrough - | \Undefined => # do nothing + | \Null, \Undefined => # do nothing | otherwise => throw Error "Invalid macro source #that (expected to get an Object, \ or a name argument and a Function)" @@ -607,11 +652,8 @@ contents = values : [] location : "returned from macro" } ] - | head.type is \atom => - switch head.value - | \unquote => unquote ...rest - | \unquote-splicing => unquote-splicing ...rest - | _ => [ recurse-on ast ] + | head `is-atom` \unquote => unquote ...rest + | head `is-atom` \unquote-splicing => unquote-splicing ...rest | _ => [ recurse-on ast ] | _ => [ quote.call this, ast ] @@ -671,10 +713,7 @@ contents = expected 1, got #{&length}" if arg.type is \list and arg.values.length - - first-arg = arg.values.0 - - if first-arg.type is \atom and first-arg.value is \unquote + if arg.values.0 `is-atom` \unquote rest = arg.values.slice 1 .0 @compile rest diff --git a/test.ls b/test.ls index eed3897..f1dfa06 100755 --- a/test.ls +++ b/test.ls @@ -239,7 +239,7 @@ test "return statement" -> ..`@equals` "(function () {\n return 'hello there';\n});" test "member expression" -> - esl "(. console :log)" + esl "(. console 'log)" ..`@equals` "console.log;" test "explicit block statement" -> @@ -251,17 +251,17 @@ test "call expression" -> ..`@equals` "f();" test "member, then call with arguments" -> - esl '((. console :log) "hi")' + esl '((. console \'log) "hi")' ..`@equals` "console.log('hi');" test "func with member and call in it" -> - esl "(lambda (x) ((. console :log) x))" + esl "(lambda (x) ((. console 'log) x))" ..`@equals` "(function (x) {\n console.log(x);\n});" test "switch statement" -> esl ''' (switch (y) - ((== x 5) ((. console :log) "hi") (break)) + ((== x 5) ((. console 'log) "hi") (break)) (default (yes))) ''' ..`@equals` """ @@ -275,7 +275,7 @@ test "switch statement" -> """ test "if-statement with blocks" -> - esl '(if (+ 1 0) (block ((. console :log) "yes") (x)) (block 0))' + esl '(if (+ 1 0) (block ((. console \'log) "yes") (x)) (block 0))' ..`@equals` """ if (1 + 0) { console.log(\'yes\'); @@ -295,7 +295,7 @@ test "if-statement with expressions" -> """ test "if-statement without alternate" -> - esl '(if (+ 1 0) (block ((. console :log) "yes") (x)))' + esl '(if (+ 1 0) (block ((. console \'log) "yes") (x)))' ..`@equals` """ if (1 + 0) { console.log(\'yes\'); @@ -309,8 +309,8 @@ test "ternary expression" -> test "while loop with explicit body" -> esl '(while (-- n) (block - ((. console :log) "ok") - ((. console :log) "still ok")))' + ((. console \'log) "ok") + ((. console \'log) "still ok")))' ..`@equals` "while (--n) {\n console.log('ok');\n console.log('still ok');\n}" test "while loop with explicit body that contains a block" -> @@ -319,29 +319,29 @@ test "while loop with explicit body that contains a block" -> ..`@equals` "while (--n) {\n {\n a;\n }\n}" test "while loop with implicit body" -> - esl '(while (-- n) ((. console :log) "ok") - ((. console :log) "still ok"))' + esl '(while (-- n) ((. console \'log) "ok") + ((. console \'log) "still ok"))' ..`@equals` "while (--n) {\n console.log('ok');\n console.log('still ok');\n}" test "do/while loop with implicit body" -> - esl '(dowhile (-- n) ((. console :log) "ok") - ((. console :log) "still ok"))' + esl '(dowhile (-- n) ((. console \'log) "ok") + ((. console \'log) "still ok"))' ..`@equals` "do {\n console.log('ok');\n console.log('still ok');\n} while (--n);" test "do/while loop with explicit body" -> esl '(dowhile (-- n) (block - ((. console :log) "ok") - ((. console :log) "still ok")))' + ((. console \'log) "ok") + ((. console \'log) "still ok")))' ..`@equals` "do {\n console.log('ok');\n console.log('still ok');\n} while (--n);" test "for loop with implicit body" -> - esl '(for (var x 1) (< x 10) (++ x) ((. console :log) "ok") - ((. console :log) "still ok"))' + esl '(for (var x 1) (< x 10) (++ x) ((. console \'log) "ok") + ((. console \'log) "still ok"))' ..`@equals` "for (var x = 1; x < 10; ++x) {\n console.log('ok');\n console.log('still ok');\n}" test "for loop with explicit body" -> - esl '(for (var x 1) (< x 10) (++ x) (block ((. console :log) "ok") - ((. console :log) "still ok")))' + esl '(for (var x 1) (< x 10) (++ x) (block ((. console \'log) "ok") + ((. console \'log) "still ok")))' ..`@equals` "for (var x = 1; x < 10; ++x) {\n console.log('ok');\n console.log('still ok');\n}" test "for loop with no body" -> @@ -349,36 +349,36 @@ test "for loop with no body" -> ..`@equals` "for (var x = 1; x < 10; ++x) {\n}" test "for loop with null update" -> - esl '(for (var x 1) (< x 10) () ((. console :log) "ok") - ((. console :log) "still ok"))' + esl '(for (var x 1) (< x 10) () ((. console \'log) "ok") + ((. console \'log) "still ok"))' ..`@equals` "for (var x = 1; x < 10;) {\n console.log('ok');\n console.log('still ok');\n}" test "for loop with null init, update and test" -> - esl '(for () () () ((. console :log) "ok") - ((. console :log) "still ok"))' + esl '(for () () () ((. console \'log) "ok") + ((. console \'log) "still ok"))' ..`@equals` "for (;;) {\n console.log('ok');\n console.log('still ok');\n}" test "for-in loop with implicit body" -> - esl '(forin (var x) xs ((. console :log) x))' + esl '(forin (var x) xs ((. console \'log) x))' ..`@equals` "for (var x in xs) {\n console.log(x);\n}" test "for-in loop with explicit body" -> - esl '(forin (var x) xs (block ((. console :log) x)))' + esl '(forin (var x) xs (block ((. console \'log) x)))' ..`@equals` "for (var x in xs) {\n console.log(x);\n}" test "multiple statements in program" -> - esl '((. console :log) "hello") ((. console :log) "world")' + esl '((. console \'log) "hello") ((. console \'log) "world")' ..`@equals` "console.log('hello');\nconsole.log('world');" test "function with implicit block body" -> - esl '(lambda (x) ((. console :log) "hello") \ - ((. console :log) "world"))' + esl '(lambda (x) ((. console \'log) "hello") \ + ((. console \'log) "world"))' ..`@equals` "(function (x) {\n console.log(\'hello\');\n console.log(\'world\');\n});" test "function with explicit block body" -> esl '(lambda (x) (block - ((. console :log) "hello") \ - ((. console :log) "world")))' + ((. console \'log) "hello") \ + ((. console \'log) "world")))' ..`@equals` "(function (x) {\n console.log(\'hello\');\n console.log(\'world\');\n});" test "new statement" -> @@ -546,14 +546,14 @@ test "quoting atoms produces an object representing it" -> ..value `@equals` "fun" test "simple quoting macro" -> - esl "(macro random (lambda () (return '((. Math :random))))) + esl "(macro random (lambda () (return '((. Math 'random))))) (+ (random) (random))" ..`@equals` "Math.random() + Math.random();" test "macro constructor given object imports properties as macros" -> esl ''' - (macro (object (:a (lambda () (return '"hi a"))) - (:b (lambda () (return '"hi b"))))) + (macro (object ('a (lambda () (return '"hi a"))) + ('b (lambda () (return '"hi b"))))) (a) (b) ''' ..`@equals` "'hi a';\n'hi b';" @@ -580,7 +580,7 @@ test "nothing-returning macro" -> test "macros mask others defined before with the same name" -> esl "(macro m (lambda () (return ()))) - (macro m (lambda () (return '((. console :log) \"hi\")))) + (macro m (lambda () (return '((. console 'log) \"hi\")))) (m)" ..`@equals` "console.log('hi');" @@ -615,20 +615,20 @@ test "dead simple quasiquote" -> test "quasiquote is like quote if no unquotes contained" -> esl "(macro rand (lambda () (return `(* 5 - ((. Math :random)))))) + ((. Math 'random)))))) (rand)" ..`@equals` "5 * Math.random();" test "macros can quasiquote to unquote arguments into output" -> esl "(macro rand (lambda (upper) (return `(* ,upper - ((. Math :random)))))) + ((. Math 'random)))))) (rand 5)" ..`@equals` "5 * Math.random();" test "macro env can create atoms out of strings or numbers" -> esl """ - (macro m (lambda () (return ((. this :atom) 42)))) + (macro m (lambda () (return ((. this 'atom) 42)))) (m)""" ..`@equals` "42;" @@ -640,17 +640,17 @@ test "macro env can create sexpr AST nodes equivalently to quoting" -> with-construct = esl """ (macro m (lambda () - (return ((. this :list) - ((. this :atom) "a") - ((. this :string) "b"))))) + (return ((. this 'list) + ((. this 'atom) "a") + ((. this 'string) "b"))))) (m)""" with-quote `@equals` with-construct test "macros can evaluate number arguments to JS and convert them back again" -> esl """ (macro incrementedTimesTwo (lambda (x) - (var y (+ 1 ((. this :evaluate) x))) - (var xAsSexpr ((. this :atom) ((. y :toString)))) + (var y (+ 1 ((. this 'evaluate) x))) + (var xAsSexpr ((. this 'atom) ((. y 'toString)))) (return `(* ,xAsSexpr 2)))) (incrementedTimesTwo 5) """ @@ -661,9 +661,9 @@ test "macros can evaluate object arguments" -> # to an object, then stringifies it. esl """ (macro objectAsString (lambda (input) - (= obj ((. this :evaluate) input)) - (return ((. this :string) ((. JSON :stringify) obj))))) - (objectAsString (object (:a 1))) + (= obj ((. this 'evaluate) input)) + (return ((. this 'string) ((. JSON 'stringify) obj))))) + (objectAsString (object ('a 1))) """ ..`@equals` "'{\"a\":1}';" @@ -672,10 +672,10 @@ test "macros can evaluate statements" -> # statement does not evaluate to a value, so we check for undefined. esl """ (macro evalThis (lambda (input) - (= obj ((. this :evaluate) input)) + (= obj ((. this 'evaluate) input)) (if (=== obj undefined) - (return ((. this :atom) "yep")) - (return ((. this :atom) "nope"))))) + (return ((. this 'atom) "yep")) + (return ((. this 'atom) "nope"))))) (evalThis (if 1 (block) (block))) """ ..`@equals` "yep;" @@ -702,8 +702,8 @@ test "quasiquote can contain nested lists" -> (lambda () ; Convert arguments into array (var args - ((. this :list :apply) null ((. Array :prototype :slice :call) arguments 0))) - (var total ((. this :atom) ((. (. args :values :length) :toString)))) + ((. this 'list 'apply) null ((. Array 'prototype 'slice 'call) arguments 0))) + (var total ((. this 'atom) ((. (. args 'values 'length) 'toString)))) (return `(/ (+ ,@args) ,total)))) (mean 1 2 3) ''' @@ -718,7 +718,7 @@ test "array macro can be empty" -> ..`@equals` "[];" test "object macro produces object expression" -> - esl "(object (:a 1) (:b 2))" + esl "(object ('a 1) ('b 2))" ..`@equals` "({\n a: 1,\n b: 2\n});" test "object macro can be passed strings as keys too" -> @@ -730,7 +730,7 @@ test "object macro's value parts can be expressions" -> ..`@equals` "({\n 'a': 1 + 2,\n 'b': f(x)\n});" test "object macro's parts can be ES6 shorthands" -> - esl '(object (:a) (:b))' + esl '(object (\'a) (\'b))' ..`@equals` "({\n a,\n b\n});" test "object macro's key parts can be computed ES6 values" -> @@ -738,11 +738,11 @@ test "object macro's key parts can be computed ES6 values" -> ..`@equals` "({\n [a]: 1 + 2,\n [b]: f(x)\n});" test "object macro can create getters" -> - esl '(object (get :a () (return 1)))' + esl '(object (get \'a () (return 1)))' ..`@equals` '({\n get a() {\n return 1;\n }\n});' test "object macro can create setters" -> - esl '(object (set :a (x) (return 1)))' + esl '(object (set \'a (x) (return 1)))' ..`@equals` '({\n set a(x) {\n return 1;\n }\n});' test "object macro can create computed getters and setters" -> @@ -759,8 +759,8 @@ test "object macro can create computed getters and setters" -> test "object macro's parts can be ES6 methods" -> esl ''' (object - (:a () (return 1)) - (:b (x) (return (+ x 1))) + ('a () (return 1)) + ('b (x) (return (+ x 1))) (c (x y) (return (+ x y 1)))) ''' ..`@equals` """ @@ -780,27 +780,27 @@ test "object macro's parts can be ES6 methods" -> test "object macro compiles complex ES6 object" -> esl ''' (object - (:prop) - (:_foo 1) - ((. Symbol :toStringTag) "Foo") + ('prop) + ('_foo 1) + ((. Symbol 'toStringTag) "Foo") - (get :foo () - (return (. this :_foo))) + (get 'foo () + (return (. this '_foo))) - (set :foo (value) - (= (. this :_foo) value)) + (set 'foo (value) + (= (. this '_foo) value)) - (get (. syms :Sym) () - (return ((. wm :get) this))) + (get (. syms 'Sym) () + (return ((. wm 'get) this))) - (set (. syms :Sym) (value) - ((. wm :set) this value)) + (set (. syms 'Sym) (value) + ((. wm 'set) this value)) - (:printFoo () - ((. console :log) (. this :foo))) + ('printFoo () + ((. console 'log) (. this 'foo))) - (:concatFoo (value) - (return (+ (. this :foo) value)))) + ('concatFoo (value) + (return (+ (. this 'foo) value)))) ''' ..`@equals` ''' ({ @@ -829,7 +829,7 @@ test "object macro compiles complex ES6 object" -> ''' test "macro producing an object literal" -> - esl "(macro obj (lambda () (return '(object (:a 1))))) + esl "(macro obj (lambda () (return '(object ('a 1))))) (obj)" ..`@equals` "({ a: 1 });" @@ -840,7 +840,7 @@ test "macro producing a function" -> ..`@equals` "(function (x) {\n return x + 3;\n});" test "property access (dotting) chains identifiers" -> - esl "(. a :b :c)" + esl "(. a 'b 'c)" ..`@equals` "a.b.c;" test "property access (dotting) chains computed identifiers" -> @@ -852,15 +852,15 @@ test "property access (dotting) chains literals" -> ..`@equals` "a[1][2];" test "property access (dotting) can be nested" -> - esl "(. a (. a (. b :name)))" + esl "(. a (. a (. b 'name)))" ..`@equals` "a[a[b.name]];" test "property access (dotting) chains mixed literals and identifiers" -> - esl "(. a :b 2 :a)" + esl "(. a 'b 2 'a)" ..`@equals` "a.b[2].a;" test "property access (dotting) chains mixed literals and values" -> - esl "(. a b 2 :a)" + esl "(. a b 2 'a)" ..`@equals` "a[b][2].a;" test "property access (dotting) treats strings as literals, not identifiers" -> @@ -890,7 +890,7 @@ test "regex can be given atoms with escaped spaces and slashes" -> test "macro deliberately breaking hygiene for function argument anaphora" -> esl "(macro : (lambda (body) (return `(lambda (it) ,body)))) - (: (return (. it :x)))" + (: (return (. it 'x)))" ..`@equals` "(function (it) {\n return it.x;\n});" test "macro given nothing produces no output" -> @@ -904,12 +904,12 @@ test "when returned from an IIFE, macros can share state" -> (macro ((lambda () (var x 0) (return (object - (:plusPrev (lambda (n) - (+= x ((. this :evaluate) n)) - (return ((. this :atom) ((. x :toString)))))) - (:timesPrev (lambda (n) - (*= x ((. this :evaluate) n)) - (return ((. this :atom) ((. x :toString))))))))))) + ('plusPrev (lambda (n) + (+= x ((. this 'evaluate) n)) + (return ((. this 'atom) ((. x 'toString)))))) + ('timesPrev (lambda (n) + (*= x ((. this 'evaluate) n)) + (return ((. this 'atom) ((. x 'toString))))))))))) (plusPrev 2) (timesPrev 2) """ ..`@equals` "2;\n4;" @@ -925,17 +925,17 @@ test "macro constructor loading from IIFE can load nothing" -> ..`@equals` "" test "macro can return multiple statements with `multi`" -> - esl "(macro declareTwo (lambda () (return ((. this :multi) '(var x 0) '(var y 1))))) + esl "(macro declareTwo (lambda () (return ((. this 'multi) '(var x 0) '(var y 1))))) (declareTwo)" ..`@equals` "var x = 0;\nvar y = 1;" test "macro can check argument type and get its value" -> esl ''' (macro stringy (lambda (x) - (if (== (. x :type) "atom") - (return ((. this :string) (+ "atom:" (. x :value)))) + (if (== (. x 'type) "atom") + (return ((. this 'string) (+ "atom:" (. x 'value)))) (block - (if (== (. x :type) "string") + (if (== (. x 'type) "string") (return x) (return "An unexpected development!")))))) (stringy a) @@ -948,7 +948,7 @@ test "macro returning atom with empty or null name fails" -> <[ "" null undefined ]>.for-each -> self.throws do -> esl """ - (macro mac (lambda () (return ((. this :atom) #it)))) + (macro mac (lambda () (return ((. this 'atom) #it)))) (mac) """ Error @@ -970,7 +970,7 @@ test "macros can be required relative to root directory" -> main-path = path.join dir-name, main-basename main-fd = fs.open-sync main-path, \a+ fs.write-sync main-fd, """ - (macro (object (:x (require "./#module-basename")))) + (macro (object ('x (require "./#module-basename")))) (x) """ @@ -1007,7 +1007,7 @@ test "macros can be required from node_modules relative to root directory" -> # Attempt to require it and use it as a macro esl """ - (macro (object (:x (require "#module-name")))) + (macro (object ('x (require "#module-name")))) (x) """ ..`@equals` "" @@ -1029,7 +1029,7 @@ test "macros required from separate modules can access complation env" -> """ code = esl """ - (macro (object (:x (require "#name")))) + (macro (object ('x (require "#name")))) (x) """ @@ -1074,7 +1074,7 @@ test "macro-generating macro" -> # yes srsly test "macro generating macro and macro call" -> # yes srsly squared esl ''' (macro define-and-call (lambda (x) - (return ((. this :multi) `(macro what (lambda () (return `(hello)))) + (return ((. this 'multi) `(macro what (lambda () (return `(hello)))) `(what))))) (define-and-call) ''' @@ -1113,7 +1113,7 @@ test "invalid AST returned by macro throws error" -> test "macro multi-returning with bad values throws descriptive error" -> try esl ''' - (macro breaking (lambda () (return ((. this :multi) null)))) + (macro breaking (lambda () (return ((. this 'multi) null)))) (breaking) ''' catch e @@ -1144,7 +1144,7 @@ test "macro can return estree object" -> test "macro can multi-return estree objects" -> esl ''' (macro identifiers (lambda () - (return ((. this :multi) + (return ((. this 'multi) (object ("type" "Identifier") ("name" "x")) (object ("type" "Identifier") @@ -1156,7 +1156,7 @@ test "macro can multi-return estree objects" -> test "macro can multi-return a combination of estree and sexprs" -> esl ''' (macro identifiers (lambda () - (return ((. this :multi) + (return ((. this 'multi) (object ("type" "Identifier") ("name" "x")) 'x)))) @@ -1167,11 +1167,11 @@ test "macro can multi-return a combination of estree and sexprs" -> test "macro can compile and return parameter as estree" -> esl ''' (macro that (lambda (x) - (return ((. this :compile) x)))) + (return ((. this 'compile) x)))) (that 3) (that "hi") (that (c)) - (that (object (:a b))) + (that (object ('a b))) ''' ..`@equals` "3;\n'hi';\nc();\n({ a: b });" From 70a2391fa13ce9ca9b492e37ea2681d642dc75c1 Mon Sep 17 00:00:00 2001 From: impinball Date: Tue, 26 Jan 2016 05:41:39 -0500 Subject: [PATCH 4/5] Update docs to use single quote properties --- doc/basics-reference.markdown | 24 +++++++++++----------- doc/how-macros-work.markdown | 28 +++++++++++++------------- readme.markdown | 38 +++++++++++++++++------------------ 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/doc/basics-reference.markdown b/doc/basics-reference.markdown index 9d9fa90..0f3d6a8 100644 --- a/doc/basics-reference.markdown +++ b/doc/basics-reference.markdown @@ -295,7 +295,7 @@ parameters to be simple pairs keys and values. (object) - (object (:a 1)) + (object ('a 1)) (object ("a" 1) ("b" 2)) @@ -313,8 +313,8 @@ ES5 getters and setters can be used. (var data 0) (object - (get :data () (return data)) - (set :data (value) (= data value))) + (get 'data () (return data)) + (set 'data (value) (= data value))) @@ -337,9 +337,9 @@ implemented, though, so generator methods are not available. (var prop 2) (var data (Symbol "data")) (object - (:prop) - ((. Symbol :toStringTag) "foo") - (:method (arg) (return (+ arg 1))) + ('prop) + ((. Symbol 'toStringTag) "foo") + ('method (arg) (return (+ arg 1))) (get data () (return 1))) @@ -362,8 +362,8 @@ Property access uses the `.` macro. (. a 1) - (. a :b (. c :d)) - (. a 1 "b" :c) + (. a 'b (. c 'd)) + (. a 1 "b" 'c) @@ -447,9 +447,9 @@ the `default`-case clause. (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"))) @@ -548,7 +548,7 @@ header, the second to be the right, and the rest to be body statements. (forin (var x) xs - ((. console :log) (. xs x))) + ((. console 'log) (. xs x))) @@ -615,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"))) diff --git a/doc/how-macros-work.markdown b/doc/how-macros-work.markdown index d42ad72..27921d3 100644 --- a/doc/how-macros-work.markdown +++ b/doc/how-macros-work.markdown @@ -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 @@ -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 @@ -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)))) @@ -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)) @@ -266,7 +266,7 @@ call it with multiple arguments and return that. (macro incrementTwice - (lambda (x) (return ((. this :multi) `(++ ,x) `(++ ,x))))) + (lambda (x) (return ((. this 'multi) `(++ ,x) `(++ ,x))))) (incrementTwice hello) @@ -290,9 +290,9 @@ compile-time: (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 diff --git a/readme.markdown b/readme.markdown index b9ef2cf..2644575 100644 --- a/readme.markdown +++ b/readme.markdown @@ -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)) @@ -132,9 +132,9 @@ arguments as the rest: ; The "." macro compiles to property access. - (. a :b) - (. a :b c) - (. a :b 5 :c "yo") + (. a 'b) + (. a 'b c) + (. a 'b 5 'c "yo") ; The "+" macro compiles to addition. (+ 1 2) @@ -148,7 +148,7 @@ arguments as the rest: 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: @@ -173,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") @@ -295,7 +295,7 @@ Macros can use [`quasiquote`][36] (`` ` ``), `unquote` (`,`) and (macro m (lambda (x) (return `(+ ,x 2)))) - ((. console :log) (m 40)) + ((. console 'log) (m 40)) @@ -309,9 +309,9 @@ S-expression atom. (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)) @@ -322,8 +322,8 @@ You can return multiple statements from a macro with `this.multi`. (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) @@ -340,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) @@ -360,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) @@ -417,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`. From f242480575d59e0e1afe8ed6234fdbce5d0e4af3 Mon Sep 17 00:00:00 2001 From: impinball Date: Tue, 26 Jan 2016 06:04:17 -0500 Subject: [PATCH 5/5] Make Travis test against docs targets as well --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 88f9958..205c2c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,5 @@ node_js: - "iojs" - "4" - "5.1" + +script: make test-all