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

Refactored your PR #1

Open
wants to merge 27 commits into
base: property
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
08ff68e
Doc typo: colon → quote
anko Jan 26, 2016
8ac0772
Refactor: Remove unused 2nd arg from unwrap-quote
anko Jan 26, 2016
675fa24
Refactor: rename unwrap-quote → maybe-unwrap-quote
anko Jan 26, 2016
911f846
Refactor: Extract is-list check, clarify is-atom
anko Jan 26, 2016
df3476d
Refactor: Remove redundant case in `.` macro
anko Jan 27, 2016
0e9ca6a
Refactor: Simplify `.` macro implementation
anko Jan 27, 2016
307490b
Refactor: Remove redundant stringIsComputed arg
anko Jan 27, 2016
45fb7c9
Refactor: Simplify `coerce-property` logic
anko Jan 27, 2016
c940043
Refactor: Localise maybeUnwrapQuote error handling
anko Jan 27, 2016
4c2c454
Refactor: Inline "coerce property" checks
anko Jan 27, 2016
63723a5
Refactor: Inline checkList function
anko Jan 27, 2016
bd3e921
Refactor: Catch+rethrow instead of passing arg i
anko Jan 27, 2016
c01cd6a
Distinguish pattern errors from generic ones
anko Jan 27, 2016
2d1f21e
Add TODO for implement generator method property
anko Jan 27, 2016
9ca20fd
Refactor naming of parameters in errors
anko Jan 27, 2016
c57fa2b
Use function macro to compile get/set
anko Jan 27, 2016
c81e1ca
Clarify get/set parameters error and test it
anko Jan 27, 2016
b4f51cb
Test that empty setter body works
anko Jan 27, 2016
a36923c
Test for get/set parameter count errors
anko Jan 27, 2016
6190f17
Remove get/set parameter type check
anko Jan 27, 2016
081b57e
Simplify get/set error checking conditional
anko Jan 27, 2016
d4f9acd
Use function macro to compile methods
anko Jan 27, 2016
cea6415
Don't set `expression` prop on get/set or methods
anko Jan 27, 2016
4320a45
Clarify what esvalid limit we're working around
anko Jan 27, 2016
98ddff3
Use "method" atom to denote method properties
anko Jan 28, 2016
faa44c4
Make test-all a .PHONY make-target
anko Jan 28, 2016
6a82f4d
Throw descriptive errors for method properties
anko Jan 28, 2016
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
6 changes: 3 additions & 3 deletions doc/basics-reference.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ implemented, though, so generator methods are not available.
(object
('prop)
((. Symbol 'toStringTag) "foo")
('method (arg) (return (+ arg 1)))
(method 'methodName (arg) (return (+ arg 1)))
(get data () (return 1)))

<!-- !test out object es6 -->
Expand All @@ -349,7 +349,7 @@ implemented, though, so generator methods are not available.
({
prop,
[Symbol.toStringTag]: 'foo',
method(arg) {
methodName(arg) {
return arg + 1;
},
get [data]() {
Expand All @@ -374,7 +374,7 @@ 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, omit the leading colon.
For *computed* property access, omit the quote.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably better in another PR. It's an oversight independent of this PR, and is better judged separately.


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

Expand Down
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ test-docs: all doc/how-macros-work.markdown doc/basics-reference.markdown

test-all: test test-readme test-docs

.PHONY: all clean test test-readme test-docs
.PHONY: all clean test test-readme test-docs test-all
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

268 changes: 137 additions & 131 deletions src/built-in-macros.ls
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ map, zip, concat-map } = require \prelude-ls
{ map, zip, concat-map, fold1 } = require \prelude-ls
{ is-expression } = require \esutils .ast
statementify = require \./es-statementify
{
Expand Down Expand Up @@ -99,30 +99,29 @@ 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
is-atom = (node, name) ->
type-ok = node.type is \atom
value-ok = if name then (node.value is name) else true

type-ok and value-ok

is-list = (node) -> node.type is \list

maybe-unwrap-quote = (node) ->
if (is-list node) and (is-atom node.values.0, \quote)

quoted-thing = node.values.1

unless is-atom quoted-thing
throw Error "Unexpected quoted property #{quoted-thing.type}: \
expected atom"

unwrap-quote = (node, string-is-computed) ->
| node.type is \list and node.values.0 `is-atom` \quote =>
computed : false
node : node.values.1
| otherwise =>
node : quoted-thing

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

contents =
\+ : n-ary-expr \+
Expand Down Expand Up @@ -188,117 +187,122 @@ contents =
elements : elements.map @compile

\object : do
check-list = (list, i) ->
| list? and list.type is \list => list.values
| otherwise => throw Error "Expected property #i to be a list"

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]) ->
# This macro needs to detect patterns in its arguments, e.g.
#
# (object (get 'a () (return 1)))
#
# where (get <something> <parameters> <body...>) is a pattern. It is split
# into methods that handle different kinds of patterns.

# Specific Error type, to be thrown just in this macro when the user has
# provided an invalid argument pattern.
#
# This makes error handling neater: the top-level macro function catches
# this type of Errors and prepends information about which parameter was
# being processed when it occurred.
class ObjectParamError extends Error
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also like to mention that this really needs to be done everywhere, not just within the (object) macro. But I'll save that for a later exercise.

Also, please include the stack. It makes for easier debugging. (Code generation serves as a good example here.)

# This is sufficient. Error.captureStackTrace is V8-specific, but nearly every
# implementation has a `stack` instance property on Errors.
if Error.captureStackTrace
  Error.captureStackTrace this, ObjectParamError
else
  @stack = new Error!stack

(@message) ~>

compile-get-set = (kind, [name, ...function-macro-arguments-part]) ->
# kind is either "get" or "set"

# What the thing being compiled is called in human-readable errors
readable-kind-name = kind + "ter"

if not name?
throw Error "Expected #{type}ter in property #i to have a name"
throw ObjectParamError "No #readable-kind-name name"

{node, computed} = unwrap-quote name, true
{node, computed} = maybe-unwrap-quote name

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"
name = @compile node
if name.kind is \Literal
computed := false

{node : name, computed} = coerce-property (@compile node), computed, true
kind = infer-name "#{type}ter", name, computed
# We'll only check the parameters here; the function expression macro can
# check the body.
[ params, _ ] = function-macro-arguments-part

unless params?.type is \list
throw Error "Expected #{kind} in property #i to have a parameter list"
unless is-list params
throw ObjectParamError "Unexpected #readable-kind-name part \
(got #{params.type}; \
expected list of parameters)"

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
throw Error "Expected parameter for #{kind} in property #i to be an \
identifier"
params = [
type : \Identifier
name : param.value
]
switch
| kind is \get and params.length isnt 0
throw ObjectParamError "Expected #readable-kind-name to have \
no parameters (got #{params.length})"
| kind is \set and params.length isnt 1
throw ObjectParamError "Expected #readable-kind-name to have \
exactly one parameter (got #{params.length})"

type : \Property
kind : type
kind : kind
key : name
# The initial check doesn't cover the compiled case.
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"
value : do
(function-type \FunctionExpression)
.apply this, function-macro-arguments-part

compile-method = (args) ->

if args.length is 0
throw ObjectParamError "Method has no name or argument list"

{node, computed} = unwrap-quote name, true
[name, ...function-macro-arguments-part] = args

unless computed or node.type is \atom
throw Error "Expected name of method in property #i to be a quoted atom
or an expression"
if not name? then throw ObjectParamError "Method has no name"

{node : name, computed} = coerce-property (@compile node), computed, true
method = infer-name 'method', name, computed
[ params, _ ] = function-macro-arguments-part

if not params? or params.type isnt \list
throw Error "Expected #method in property #i to have a parameter \
list"
{node, computed} = maybe-unwrap-quote name

params = for param, j in params.values
if param.type isnt \atom
throw Error "Expected parameter #j for #method in property #i to be \
an identifier"
type : \Identifier
name : param.value
name := @compile node
if name.type is \Literal
computed := false

readable-kind-name = 'method'+ switch name.type is \Identifier
| true => " '#{name.name}'"
| false => ""

if not params? or not is-list params
throw ObjectParamError "Expected #readable-kind-name to have an \
argument list"

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) ->
value : do
(function-type \FunctionExpression)
.apply this, function-macro-arguments-part

compile-property-list = (args) ->
| args.length is 0 =>
throw Error "Expected at least two arguments in property #i"
throw ObjectParamError "Got empty list (expected list to have contents)"

| is-atom args.0 and (args.0.value is \method) =>
compile-method.call this, args[1 til]

| is-atom args.0 and (args.0.value in <[ get set ]>) =>
compile-get-set.call this, args.0.value, args[1 til]

| args.0 `is-atom` \* =>
# TODO Implement
throw ObjectParamError "Unexpected '*' (generator methods not yet implemented)"
| args.length is 1 =>
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"
unless (is-atom type, \quote) and is-atom node
throw ObjectParamError "Invalid single-element list (expected a pattern of (quote <atom>))"

type : \Property
kind : \init
Expand All @@ -310,37 +314,42 @@ contents =
name : node.value
shorthand : true

| args.length is 2 =>
{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"
| otherwise # Assume a key-value pair for a normal object Property
if args.length isnt 2
throw Error "Not getter, setter, method, or shorthand property, \
but length is #{args.length} \
(expected 2: key and value)"

{node : key, computed} = coerce-property (@compile node), computed, true
{node, computed} = maybe-unwrap-quote args.0

key = @compile node
if key.type is \Literal
computed := false

type : \Property
kind : \init
computed : computed
key : key
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 `is-atom` \get or args.0 `is-atom` \set =>
compile-get-set.call this, i, args.0.value, args[1 til]
(...args) ->
type : \ObjectExpression
properties : args.map (arg, i) ~>

# Reserve this for future generator use.
| args.0.type `is-atom` \* =>
throw Error "Unexpected generator method in property #i"
if is-list arg
try
compile-property-list.call @, arg.values
catch e
# To object parameter errors, prepend the argument index.
if e instanceof ObjectParamError
e.message = "Unexpected object macro argument #i: " + e.message

| otherwise => compile-method.call this, i, args
throw e

->
type : \ObjectExpression
properties : for args, i in arguments
compile-list.call this, i, (check-list args, i)
else
throw Error "Unexpected object macro argument #i: \
Got #{arg.type} (expected list)"

\var : (name, value) ->
if &length > 2
Expand Down Expand Up @@ -432,29 +441,26 @@ contents =
argument : @compile arg

\. : do
join-members = (host, prop) ->
{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"
join-as-member-expression = (host-node, prop-node) ->

{node : prop, computed} = coerce-property (@compile node), computed, false
host = @compile host-node

{ node : prop-node, computed } = maybe-unwrap-quote prop-node

prop = @compile prop-node

type : \MemberExpression
computed : computed
object : host
property : prop

(host) ->
switch
->
| &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
| &length is 1 => @compile &0
| otherwise =>
host = @compile host
for i from 1 til &length
host = join-members.call this, host, &[i]
host
[].slice.call arguments
|> fold1 join-as-member-expression.bind @

\lambda : function-type \FunctionExpression

Expand Down
Loading