Open samuelgoto opened 7 years ago
I'm excited you're looking into this! Definitely a perfect area to explore with macros.
Unless you want to go down exactly the JSX route I don't think you necessarily need readtables. My understanding is the only reason @jlongster used readtables back when he did jsx-reader is because JSX introduces some specific lexical problems like </div>
confusing the regular expression tokenizer.
Does {} conflict with object literals or with for-if-switch-{}-like statements? Would it be impossible to disambiguate?
Yep:
let x = { a };
is that a doctree or an object literal shorthand?
Bare delimiters are tricky, both from a macro implementation perspective and an ambiguity perspective.
I like your:
doc {
div {
span {
"hello world"
}
}
}
syntax best. The doc
identifier makes it pretty straightforward to implement as a macro. The doc
macro can just consume everything inside its delimiters and parse appropriately.
If you go with this syntax you might have some fun issues with ASI inside a doctree to figure out:
doc {
div
{
"hello"
}
}
// should you interpret as
doc {
div;
{
"hello"
}
}
// or
doc {
div {
"hello"
}
}
Otherwise, if I'm understanding your thinking correctly something like that syntax should work out.
The doc identifier makes it pretty straightforward to implement as a macro.
I generally the doc {}-like syntax too, but I wasn't sure how to go to code and come back to trees. For example:
doc {
div {
for (user in users) {
// is something as short as this possible?
span { `${user.name}` }
// or do i need to use the doc {} be recursively?
doc { span { `${user.name}` } }
}
}
}
Yep: let x = { a }; is that a doctree or an object literal shorthand?
Yep, I was a little worried about that. Note that there is a token that goes before {} that could disambiguate, i.e. I'd think one can make a distinction between:
let x = {a}; // object literal
let y = span { a }; // doctree
A couple of questions:
(a) Wouldn't the span
token before the {}
give me the ability to differentiate between the two?
(b) How does kotlin/groovy get away being able to parse builders?
fun result(args: Array<String>) =
html {
head {
title {+"XML encoding with Kotlin"}
}
body {
h1 {+"XML encoding with Kotlin"}
p {+"this format can be used as an alternative markup to XML"}
// an element with attributes and text content
a(href = "http://kotlinlang.org") {+"Kotlin"}
// mixed content
p {
+"This is some"
b {+"mixed"}
+"text. For more see the"
a(href = "http://kotlinlang.org") {+"Kotlin"}
+"project"
}
p {+"some text"}
// content generated by
p {
for (arg in args)
+arg
}
}
}
(b) How does kotlin/groovy get away being able to parse builders?
Wow, just reading more closely what Kotlin seems to be doing, it seems like the trick here is a syntactic sugar that allows some higher-order functions (specifically, high-order functions that takes functions as the last parameter) to be called in the form {}. Kinda of like if this was typescript, something along the lines:
// typescript
function a(init: () => void) {
}
// traditional function call with arrow functions:
a(() => {
// hello world
});
// but kotlin adds this syntactical extension to also allow:
a {
// hello world
}
// Both of these calls are semantically equivalent.
Since the last parameter is a function, you add nesting by biding the parent to "this". For example:
function a(init: () => void) {
var result = new A();
var result = new A();
// calls the lambda function that was passed as a parameter to an instance
// of the class A, which has a b() method.
init.call(a);
return result;
}
class A {
b(init: () => void) {
}
}
// such that
a {
// implicitly, this is this.b(() => {})
b {
}
}
// is equivalent to
a(() => {
this.b(() => {
});
});
Because the lambdas are functions, you can embed whichever statements you'd like (e.g. for loops).
Kind of a neat trick. Wondering: is this something that can be prototyped with sweet.js?
is this something that can be prototyped with sweet.js?
Absolutely. Seems like you could just define a bunch of macros and dispatch to functions based on MacroContext#name
. However, I don't know that I would create a bunch of classes. Functions alone should do what you want.
Absolutely. Seems like you could just define a bunch of macros and dispatch to functions based on MacroContext#name. However, I don't know that I would create a bunch of classes. Functions alone should do what you want.
hummm unclear to me how I'd accomplish that with macros ...
Are you saying that the syntactical simplification provided by this:
https://kotlinlang.org/docs/reference/lambdas.html#higher-order-functions
Would be possible to implement with sweet.js?
Specifically, are you saying that it would be possible for me to, with sweet.js, enable the following syntax?
a {
// foobar
}
to be transpiled into
a(() => {
// foobar
})
For any a
(i.e. without hard-coding a
)?
Would it be possible to make a distinction too when parameters are available? E.g.
a(b) {
// foobar
}
// to be transpiled as
a(b, () => {
// foobar
});
Can you be more specific by what you mean by MacroContext#name?
from the little that i'm playing with the editor and the reference documentation, it doesn't seem like i'd be able to accomplish this with the current affordances. it seems sweet.js offers me two matching opportunities:
I'd think I'd need something along the lines of
syntax (IdentifierExpression, BlockStatement) =>
// transform the AST
to add the ability to open a BlockStatement after an IdentifierExpression to legitimize:
a {
}
right?
On the other hand, JSX managed to get access to the internals of sweet and get into somewhat what i think would make things possible.
let _DOM = macro {
rule { { $a . $b $expr ... } } => {
_DOM_member { $a . $b $expr ... }
}
rule { { $el $attrs } } => {
$el($attrs)
}
rule { { $el $attrs , } } => {
$el($attrs)
}
rule { { $elStart $attrs $($children:expr,) ... } } => {
$elStart($attrs, $children (,) ...)
}
rule { } => { _DOM }
}
is this the macro that was referred to earlier? and if so, (a) is there documentation that i could use to follow how this works (doesn't seem supported here?) and (b) does that mean that I won't be able to use the online editor?
@samuelgoto what you need is something along the lines of
import {unwrap, fromStringLiteral} from 'sweet.js/helpers' for syntax;
syntax a = ctx => {
const name = unwrap(ctx.name()).value;
const dummy = #`d`.get(0);
const nameStr = fromStringLiteral(dummy, name);
const body = ctx.next().value;
return #`
tags[${nameStr}](() => ${body});
`;
}
a { console.log('in a') }
You can also check to see whether the first value returned from a call to ctx.next
is an argument list or a body and build the template conditionally
return #`tags[${nameStr}](${args}, () => ${body})`;
If you haven't already done so, go through the tutorial https://www.sweetjs.org/doc/tutorial
And feel free to ask questions in the gitter room https://gitter.im/sweet-js/sweet.js
There's also nothing in the a
macro specific to a
so you could make this more generic with a helper imported from another file.
// tagHelper.js
'lang sweet.js';
export const tagMacro = ctx => {
const name = unwrap(ctx.name()).value;
const dummy = #`d`.get(0);
const nameStr = fromStringLiteral(dummy, name);
const body = ctx.next().value;
return #`
tags[${nameStr}](() => ${body});
`;
}
export const tags = {
a: (fn, ...props) => ...,
div: (fn, ...props) => ...,
...
};
// tags.js
import { tagMacro } from './tagHelper' for syntax;
syntax a = tagMacro;
syntax div = tagMacro;
...
export { a, div, ... };
@disnet please correct me if I've missed something.
There's also nothing in the a macro specific to a so you could make this more generic with a helper imported from another file.
I think this is the challenging part: I think this does make it specific to a
(even if you refactor the code to loop through tags). This approach still requires me to enumerate all of the possible tags, right?
syntax a = tagMacro;
syntax div = tagMacro;
...
But what if one wants to invent new tags? Or compose them? For example, both JSX as well as Kotlin enables you to write:
var doc = MyOwnTag {
AnotherTagThatDidNotExistPreviously {
ThisOneIsntAvailableAtCompilineTime {
}
}
}
Along the lines of web-components:
<X-MyOwnTag>
<X-AnotherTagThatDidNotExistPreviously>
<X-ThisOneIsntAvailableAtCompilineTime/>
</X-AnotherTagThatDidNotExistPreviously>
</X-MyOwnTag>
You'd either have to create a macro
syntax MyOwnTag = tagMacro;
tags.MyOwnTag = ...
or create a new language once readtables are exposed as discussed in #687
For custom tags (tags that aren't pre-defined) you could have a parameterized macro:
import X_ from 'tag-macros';
var doc = X_(MyOwnTag) {
X_(AnotherTagThatDidNotExistPreviously) {
X_(ThisOneIsntAvailableAtCompilineTime) {
}
}
}
``
Per discussion on this issue, kicking off a separate one to ask advice on syntax feasibility and implementation strategies.
I started exploring a DST for javascript along the lines of kotlin builders, JFX and JSX. I've only written some high level ideas and started kicking off a sweet.js macro implementation [prototype](http://www.sweetjs.org/browser/editor.html#/*%0AWelcome%20to%20sweet.js!%0A%0AYou%20can%20play%20around%20with%20macro%20writing%20here%20on%20the%20left%20side%20and%0Ayour%20code%20will%20automatically%20be%20compiled%20on%20the%20right.%20This%20page%0Awill%20also%20save%20your%20code%20to%20localStorage%20on%20every%20successful%0Acompile%20so%20feel%20free%20to%20close%20the%20page%20and%20come%20back%20later!%0A*/%0A%0A//%20The%20%60syntax%60%20keyword%20is%20used%20to%20create%20and%20name%20new%20macros.%0Asyntax%20doc%20=%20function%20(ctx)%20%7B%0A%20%20let%20root%20=%20ctx.next().value;%0A%20%20console.log(root);%0A%20%20console.log(root.kind%20==%20%22braces%22);%0A%20%20console.log(root.inner);%0A%0A%20%20if%20(root.inner.size%20%3C%202%20%7C%7C%0A%20%20%20%20%20%20root.inner.get(0).value.token.value%20!=%20%22%7B%22%20%7C%7C%0A%20%20%20%20%20%20root.inner.get(root.inner.size%20-%201).value.token.value%20!=%20%22%7D%22)%20%7B%0A%20%20%20%20throw%20new%20Error(%22Syntax%20Error%22);%0A%20%20%7D%0A%20%20%0A%20%20debugger;%0A%20%20%0A%20%20let%20result%20=%20#%60%60;%0A%20%20let%20child%20=%20#%60var%20a%20=%201;%60;%0A%20%20var%20children%20=%20%5Bchild%5D;%0A%20%20result%20=%20result.concat(#%60function()%20%7B%20$%7Bchildren%5B0%5D%7D%20%7D();%60);%0A%20%20//%20result%20=%20result.concat(#%60%7D();%60);%0A%20%20//result%20=%20result.concat(#%60function()%20%7B%60);%0A%20%20//result%20=%20result.concat(#%60%7D();%60);%0A%20%20//result.concat(#%60%20%20var%20d%20=%20new%20Doc();%60);%0A%20%20%0A%20%20console.log(%22hi%22);%0A%20%20var%20children%20=%20%5B%5D;%0A%20%20for%20(var%20i%20=%201;%20i%20%3C%20(root.inner.size%20-%201);%20i++)%20%7B%0A%20%20%20%20console.log(root.inner.get(i));%0A%20%20%20%20%0A%20%20%7D%0A%20%20%0A%20%20//%20result.concat(#%60%20return%20d;%60);%0A%20%20%0A%20%20//%20debugger;%0A%20%20//%20return%20#%60$%7Bx%7D%20+%202%60%20+%20#%60%20+%203%60;%0A%20%20return%20result;%0A%20%20//%20return%20#%60function()%20%7B%20var%20d%20=%20new%20Doc();%20return%20d;%20%7D%20()%60;%0A%7D%0A%0Aclass%20Doc%20%7B%0A%7D%0A%0Avar%20a%20=%20doc%20%7B%0A%20%20div%20%7B%0A%20%20%7D%0A%7D%0A%0Aconsole.log(a);%0A%0A/**%0Avar%20a%20=%20doc%20%7B%20%0A%20%20div%20%7B%20%0A%20%20%20%20%0A%20%20%20%20if%20(true)%20%7B%0A%20%20%20%20%20%20div%20%7B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20div%20%7B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20for%20(i%20=%200;%20i%20%3C%2010;%20i++)%20%7B%0A%20%20%20%20%20%20span%20%7B%0A%20%20%20%20%20%20%20%20a%20%7B%20href:%20%60/%7B%7Bi%7D%7D%60,%20i%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20b%0A%20%20%20%20%0A%20%20%20%20%0A%20%20%7D%20%0A%7D;%0A*/%0A%0Avar%20hello%20=%20%22world%22;%0A).
Can you help me sanity check (a) whether the syntax here is feasible or not (i.e. does it create ambiguity with the JS grammar (you can make comments here directly if you'd like)? and if so, what are the most common strategies to avoid them) and (b) once we settle on a feasible design what's the best way to go about it wrt sweet.js?