|
# Preliminary Design
|
|
# Preliminary Design
|
|
|
|
|
|
(note: some of this is already outdated, will revise)
|
|
|
|
|
|
|
|
## Reading
|
|
## Reading
|
|
|
|
|
|
To do macros we need to `read`. To `read` we need to match delimiters
|
|
To do macros we need to `read`. To `read` we need to match delimiters
|
... | @@ -17,29 +15,75 @@ So to handle the problem of `/` we can use "almost one" lookbehind to |
... | @@ -17,29 +15,75 @@ So to handle the problem of `/` we can use "almost one" lookbehind to |
|
disambiguate. Algorithm:
|
|
disambiguate. Algorithm:
|
|
|
|
|
|
skip over comments
|
|
skip over comments
|
|
|
|
|
|
if tok is /
|
|
if tok is /
|
|
if tok-1 is )
|
|
if tok-1 is ()
|
|
look back to matching (
|
|
if tok-2 in "if" "while" "for" "with"
|
|
if identifier before ( in "if" "while" "for" "with"
|
|
|
|
tok is start of regex literal
|
|
tok is start of regex literal
|
|
else
|
|
else
|
|
tok is divide
|
|
tok is divide
|
|
if tok-1 is }
|
|
else if tok-1 is {}
|
|
if end of function expression // described below
|
|
if isBlock(tok-1)
|
|
tok is start of divide
|
|
// named or anonymous function
|
|
|
|
if tok-2 is () and tok-3 is "function" or tok-4 is "function"
|
|
|
|
if function expression // how to determine is described below
|
|
|
|
tok is divide
|
|
|
|
else
|
|
|
|
tok is start of regex literal
|
|
|
|
else
|
|
|
|
tok is start of regex literal
|
|
else
|
|
else
|
|
tok is start of regex literal
|
|
tok is divide
|
|
|
|
else if tok-1 in punctuator // e.g. ";", "==", ">", "/", "+", etc.
|
|
if tok-1 in punctuator // e.g. ";", "==", ">", "/", "+", etc.
|
|
|
|
tok is start of regex literal
|
|
tok is start of regex literal
|
|
|
|
else if tok-1 in keywords
|
|
if tok-1 in keywords // though some keywords will eventually result in a parse error
|
|
// though some keywords will eventually result in a parse error (eg. debugger, break)
|
|
tok is start of regex literal
|
|
tok is start of regex literal
|
|
|
|
|
|
else
|
|
else
|
|
tok is divide
|
|
tok is divide
|
|
|
|
|
|
|
|
|
|
|
|
assignOps = ["=", "+=", "-=", "*=", "/=", "%=",
|
|
|
|
"<<=", ">>=", ">>>=", "&=", "|=", "^=", ","];
|
|
|
|
|
|
|
|
binaryOps = ["+", "-", "*", "/", "%","<<", ">>", ">>>",
|
|
|
|
"&", "|", "^","&&", "||", "?", ":",
|
|
|
|
"instanceof"
|
|
|
|
"===", "==", ">=", "<=", "<", ">", "!=", "!=="];
|
|
|
|
|
|
|
|
unaryOps = ["++", "--", "~", "!", "delete", "void", "typeof", "throw", "new"];
|
|
|
|
|
|
|
|
function isBlock(tok)
|
|
|
|
if tok-1 is ( or [
|
|
|
|
// ... ({...} ...)
|
|
|
|
return false
|
|
|
|
else if tok-1 is ":" and parent token is {}
|
|
|
|
// ... {a:{...} ...}
|
|
|
|
return isBlock(the parent {})
|
|
|
|
else if tok-1 is one of assignOps unaryOps binaryOps
|
|
|
|
// ... + {...}
|
|
|
|
// ... typeof {...}
|
|
|
|
return false
|
|
|
|
else if tok-1 is "return"
|
|
|
|
// handle ASI
|
|
|
|
if lineNumber(tok) isnt lineNumber(tok-1)
|
|
|
|
// return
|
|
|
|
// {...}
|
|
|
|
return true
|
|
|
|
else
|
|
|
|
// return {...}
|
|
|
|
return false
|
|
|
|
else if tok-1 is "yield" // could also put "yield" in unaryOps
|
|
|
|
return false
|
|
|
|
else if tok-1 is one of "debugger" "break" "continue" "throw"
|
|
|
|
parse error
|
|
|
|
else if tok-1 is one of "void" "typeof" "in" "case" "delete"
|
|
|
|
// ... in {...}
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
Depending on context, `function name() {}` is either a function declaration or a
|
|
Depending on context, `function name() {}` is either a function declaration or a
|
|
function expression. If it's a function expression then
|
|
function expression. If it's a function expression then
|
|
a following `/` will be interpreted as a divide but if it's a
|
|
a following `/` will be interpreted as a divide but if it's a
|
... | @@ -57,13 +101,13 @@ following imply it is a function declaration: |
... | @@ -57,13 +101,13 @@ following imply it is a function declaration: |
|
|
|
|
|
; } ) ] ident literal (including regex literal so need to be careful about /)
|
|
; } ) ] ident literal (including regex literal so need to be careful about /)
|
|
debugger break continue else
|
|
debugger break continue else
|
|
|
|
|
|
And these imply it is an function expression.
|
|
|
|
|
|
|
|
( { [ , (assignment operators) (binary operators) (unary operators)
|
|
And these imply it is a function expression.
|
|
|
|
|
|
|
|
( [ , (assignment operators) (binary operators) (unary operators)
|
|
in typeof instanceof new return case delete
|
|
in typeof instanceof new return case delete
|
|
throw void
|
|
throw void
|
|
|
|
|
|
And these will result in a parse error:
|
|
And these will result in a parse error:
|
|
|
|
|
|
do break default finally for function if switch this
|
|
do break default finally for function if switch this
|
... | @@ -139,45 +183,6 @@ etc.) as really reserved. Macros can't override their meaning. |
... | @@ -139,45 +183,6 @@ etc.) as really reserved. Macros can't override their meaning. |
|
Should we disallow FutureReservedWords too (`class`, `enum`, etc.)?
|
|
Should we disallow FutureReservedWords too (`class`, `enum`, etc.)?
|
|
|
|
|
|
|
|
|
|
## Scope
|
|
|
|
|
|
|
|
Macro definitions should be scoped appropriately.
|
|
|
|
What should we do about hoisting? For example,
|
|
|
|
|
|
|
|
macro foo { ... }
|
|
|
|
|
|
|
|
function bar() {
|
|
|
|
foo(...)
|
|
|
|
|
|
|
|
if (x) {
|
|
|
|
var foo = function () { ... }
|
|
|
|
foo(...)
|
|
|
|
} else {
|
|
|
|
var foo = function () { ... }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Because of hoisting, the variable `foo` will shadow the macro definition of
|
|
|
|
`foo` in this code. But this is complex and annoying. Can we just say
|
|
|
|
that hoisting always happens after macro expansion? So the first
|
|
|
|
`foo(...)` is a macro invocation and the second `foo(...)` is a
|
|
|
|
function call? Does this cause any problems?
|
|
|
|
|
|
|
|
So I think we have to have hoisting happen after macro expansion. But what does this mean for hygiene?
|
|
|
|
|
|
|
|
macro foo { ... }
|
|
|
|
|
|
|
|
foo { bar = 4 }
|
|
|
|
|
|
|
|
var bar = 5;
|
|
|
|
|
|
|
|
## Hygiene
|
|
|
|
|
|
|
|
Should be [fun](http://www.quotationspage.com/quote/26964.html)...
|
|
|
|
|
|
|
|
## Modules
|
|
|
|
|
|
|
|
Details about importing macros from another module...
|
|
|
|
|
|
|
|
## Example Code
|
|
## Example Code
|
|
|
|
|
... | @@ -445,25 +450,10 @@ These names aren't quite right. What is JavaScripty? |
... | @@ -445,25 +450,10 @@ These names aren't quite right. What is JavaScripty? |
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
## Misc
|
|
|
|
|
|
|
|
* sub-form expansion? MTWT says parse and expand must be separate to do sub-form expansion.
|
|
|
|
|
|
|
|
## potential macros
|
|
|
|
|
|
|
|
heredoc or multi line strings.
|
|
|
|
|
|
|
|
s = heredoc {this is a nice
|
|
|
|
multiline string
|
|
|
|
that keeps spaces
|
|
|
|
the only problem is it can't break tokenization...
|
|
|
|
}
|
|
|
|
|
|
|
|
string templates
|
|
|
|
|
|
|
|
## Papers
|
|
## Papers
|
|
|
|
|
|
Papers I've been looking through:
|
|
Useful papers on macros:
|
|
|
|
|
|
* Macros that work
|
|
* Macros that work
|
|
* Macros that work together
|
|
* Macros that work together
|
... | @@ -475,7 +465,4 @@ Papers I've been looking through: |
... | @@ -475,7 +465,4 @@ Papers I've been looking through: |
|
* Refining Syntactic Sugar: Tools for Supporting Macro Development
|
|
* Refining Syntactic Sugar: Tools for Supporting Macro Development
|
|
* Fortifying Macros
|
|
* Fortifying Macros
|
|
* Composable and Compilable Macros
|
|
* Composable and Compilable Macros
|
|
|
|
|
|
What others might be helpful?
|
|
|
|
|
|
|
|
|
|
|