Please direct all comments to this issue. I won't receive notifications if you comment here, but I will receive them if you comment there.
This is an attempt to create a more cohesive, easy-to-implement DSL proposal for JavaScript, attempting to work around most of the issues described in this original proposal. Of course, this is pretty poorly formatted and it's missing a few edge cases, but it's just a strawman.
- Blocks close over environment, but have phantom
@@this
context (syntax error outside DSL) @foo(...) do { ... }
- Invoke@@this.foo(..., block)
@['foo'](...) do { ... }
- Invoke@@this['foo'](..., block)
foo(...) do { ... }
- Invokefoo(..., block)
foo(...) do (...args) { ... }
- Accept arguments for the block@foo do { ... }
is equivalent to@foo() do { ... }
, etc.@foo(...) async do { ... }
makes the block async, like an async function@foo
/@['foo']
- Equivalent to@@this.foo
/@@this['foo']
outside calls@foo = bar
/@['foo'] = bar
- Perform@@this.foo = bar
/@@this['foo'] = bar
@foo
as a statement is equivalent to@@this.foo
, not@@this.foo()
- DSLs return their completion value like
do
blocks - Normal blocks always return or throw their value
- DSL functions are passed a wrapper callback that can be called with
@@this
and arguments - DSLs are invoked via this:
foo.call(@@this, ...args, block?)
- Class decorators have precedence over DSL markers (but reinterpretation between the two is trivial)
Notes for all the examples, including in other sections:
$var
s are unique variables not actually exposed to codefoo.$prop
s refer to internal slots, not actual properties$func()
s refer to internal operations, not actual builtin functions
GraphQL builders
import {query} from "graphql-builders" // hypothetical
let notes = query do {
@notes do {
@id
@createdDate
@content
@author do {
@name
@avatarUrl({size: 100})
}
}
}
Desugared:
query(function () {
return this.notes(function () {
this.id
this.createdDate
this.content
return this.author(function () {
this.name
return this.avatarURL({size: 100})
})
})
})
Framework concept
import * as m from "my-framework/block" // hypothetical
import * as ref from "my-framework/ref" // hypothetical
import marked from "marked"
const Sizes = {
Small: "0.8em",
Medium: "1em",
Large: "1.2em",
}
const Toggle = m.component do ({change, name}) {
@h("label[style.padding=20px]", {onclick() { change.send(name) }}) do {
@h("input[type=radio][name=font-size]")
@t(name)
}
}
const MyApp = m.element do ({update}) {
const change = ref.cell("Medium")
@h("fieldset") do {
@c(Toggle, {change, name: "Small"})
@c(Toggle, {change, name: "Medium"})
@c(Toggle, {change, name: "Large"})
}
@h("div", {
style: {fontSize: change.pipe(
ref.map(name => Sizes[name])
)},
props: {innerHTML: update.pipe(
ref.map(update => marked(update.content))
)},
})
}
m.render(document.getElementById("app")) do {
const Intro = `
# Anna Karenina
## Chapter 1
Happy families are all alike; every unhappy family is unhappy in its own way.
Everything was in confusion in the Oblonskys’ house. The wife had discovered
that the husband was carrying on an intrigue with a French girl, who had been a
governess in their family, and she had announced to her husband that she could
not go on living in the same house with him...
`
@c(MyApp, {update: ref.of({content: Intro})})
}
Desugared:
import * as m from "my-framework/block" // hypothetical
import * as ref from "my-framework/ref" // hypothetical
import marked from "marked"
const Sizes = {
Small: "0.8em",
Medium: "1em",
Large: "1.2em",
}
const Toggle = m.component(function ({change, name}) {
this.h("label[style.padding=20px]", {onclick() { change.send(name) }}, function () {
this.h("input[type=radio][name=font-size]")
this.t(name)
})
})
const MyApp = m.element(function ({update}) {
const change = ref.cell("Medium")
this.h("fieldset", function () {
this.c(Toggle, {change, name: "Small"})
this.c(Toggle, {change, name: "Medium"})
this.c(Toggle, {change, name: "Large"})
})
this.h("div", {
style: {fontSize: change.pipe(
ref.map(name => Sizes[name])
)},
props: {innerHTML: update.pipe(
ref.map(update => marked(update.content))
)},
})
})
m.render(document.getElementById("app"), function () {
const Intro = `
# Anna Karenina
## Chapter 1
Happy families are all alike; every unhappy family is unhappy in its own way.
Everything was in confusion in the Oblonskys’ house. The wife had discovered
that the husband was carrying on an intrigue with a French girl, who had been a
governess in their family, and she had announced to her husband that she could
not go on living in the same house with him...
`
c(MyApp, {update: ref.of({content: Intro})})
})
Testing
describe("a calculator") do {
const calculator = new Calculator()
@on("calling sum with two numbers") do {
@it("should return the sum of the two numbers") do {
const sum = calculator.sum(2, 3)
shouldEqual(5, sum)
}
}
}
Desugared:
describe("a calculator", function () {
const calculator = new Calculator()
this.on("calling sum with two numbers", function () {
this.it("should return the sum of the two numbers", function () {
const sum = calculator.sum(2, 3)
shouldEqual(5, sum)
})
})
})