@bablr/language_enhancer-en-jsx
Version:
A BABLR grammar to enhance a JS-family language with JSX
211 lines (179 loc) • 6.33 kB
JavaScript
import { CoveredBy, InjectFrom, Node, UndefinedAttributes } from '@bablr/helpers/decorators';
import { spam as m } from '@bablr/helpers/shorthand';
import * as productions from '@bablr/helpers/productions';
import { o, eat, eatMatch, defineAttribute, shiftMatch, fail } from '@bablr/helpers/grammar';
import { triviaEnhancer } from '@bablr/helpers/trivia';
export const enhanceLanguageWithJSX = (language) => {
if (language.dependencies.JSX) throw new Error();
let jsxLanguage = Object.freeze({
canonicalURL: 'https://bablr.org/languages/universe/jsx',
dependencies: Object.freeze({
JS: language.canonicalURL,
}),
grammar: triviaEnhancer(
{
triviaIsAllowed: (s) => ['Tag', 'Interpolation'].includes(s.span),
triviaMatcher: m`#: :JS.Comment: <_Trivia /[ \n\r\t]|\/\/|\/\*/ />`,
},
class JSXGrammar {
('NodeChild')
*Node({ ctx }) {
let open = yield eat(m`open: <OpenTag />`);
let openType = open.get('type');
if (open.get('selfClosingTagToken')) {
yield eat(m`close: null`);
return;
}
yield eat(m`children[]$: <_Children />`, o({}), o({ allowEmpty: true }));
let close = yield eat(m`close: <CloseTag />`);
let closeType = close.get('type');
while (openType.type === closeType.type) {
if (openType.type === 'MemberExpression') {
if (
ctx.sourceTextFor(openType.get('property')) !==
ctx.sourceTextFor(closeType.get('property'))
) {
break;
}
openType = openType.get('object');
closeType = closeType.get('object');
} else {
if (
closeType.type === openType.type &&
ctx.sourceTextFor(openType.get('value')) ===
ctx.sourceTextFor(closeType.get('value'))
) {
return;
} else {
break;
}
}
}
yield fail();
}
*Children() {
while (yield eatMatch(m`<__NodeChild />`)) {}
}
*NodeChild() {
(yield eatMatch(m`<Interpolation '{' />`)) ||
(yield eatMatch(m`<Node '<' />`)) ||
(yield eat(m`<Text />`));
}
('NodeChild')
*Interpolation() {
yield eat(
m`openToken: <*Punctuator '{' { balanced: '}', balancedSpan: 'Interpolation' } />`,
);
yield eatMatch(m`value+$: :JS: <__Expression />`, o({}), o({ bind: true }));
yield eat(m`closeToken: <*Punctuator '}' { balancer: true } />`);
}
(['balanced', 'balancedSpan'])
*OpenTag({ s, props: { fragment } }) {
const outerSpan = s.span;
yield eat(m`openToken: <*Punctuator '<' { balancedSpan: 'Tag', balanced: '>' } />`);
let type;
type = !fragment && (yield eatMatch(m`type+$: <__NodeType />`));
if (type) {
while (yield eatMatch(m`attributes[]$: <Attribute />`)) {}
} else {
yield eat(m`attributes$: null`);
}
let sc;
if (type) {
sc = yield eatMatch(
m`selfClosingTagToken: <*Punctuator '/' />`,
o({}),
o({ bind: true }),
);
} else {
sc = yield eat(m`selfClosingTagToken: null`);
}
const balanced = !sc && (s.depths.path > 0 || outerSpan !== 'Bare');
yield defineAttribute('balanced', balanced);
yield defineAttribute('balancedSpan', balanced ? 'NodeChildren' : null);
yield eat(m`closeToken: <*Punctuator '>' { balancer: true } />`);
}
*NodeType({ s }) {
let res;
if (!s.holding) {
res = yield eat(m`<Identifier />`);
} else {
res = yield eatMatch(m`<MemberExpression /\g\./ />`);
}
if (res) {
return shiftMatch(m`<__NodeType />`);
}
}
*CloseTag() {
yield eat(m`openToken: <*Punctuator '</' { balanced: '>', balancedSpan: 'Tag' } />`);
yield eatMatch(m`type+$: <__NodeType />`);
yield eat(m`closeToken: <*Punctuator '>' { balancer: true } />`);
}
('NodeChild')
*Text() {
yield eat(m`value: <*Literal /[^{<>}\g]+/ />`);
}
*Attribute() {
yield eat(m`name$: <Identifier />`, o({ scoped: false }));
yield eat(m`sigilToken: <*Punctuator '=' />`);
yield eat(m`value$: <__AttributeValue />`);
}
*AttributeValue() {
if (yield eatMatch(m`:JS: <String /['"]/ />`)) {
} else if (yield eatMatch(m`<Interpolation '{' />`)) {
}
}
('NodeType')
*Identifier({ props: { scoped = true } }) {
yield eat(m`:JS: <_Identifier />`, o({ scoped }));
}
('NodeType')
*MemberExpression() {
yield eat(m`:JS: <_MemberExpression /\g\./ />`);
}
(productions)
*Punctuator() {}
(productions)
*Literal() {}
},
),
});
return Object.freeze({
...language,
dependencies: Object.freeze({ ...language.dependencies, JSX: jsxLanguage }),
grammar: triviaEnhancer(
{
triviaIsAllowed: (s) => s.span === 'Bare',
triviaMatcher: m`#: :Comment: <_Trivia /[ \n\r\t]|\/\/|\/\*/ />`,
},
class extends language.atrivialGrammar {
*Expression(args) {
let {
s,
props: { power },
} = args;
if (!s.held) {
if (yield eatMatch(m`:JSX: <Node />`)) {
return shiftMatch(m`<__Expression />`, o({ power }));
} else {
return yield* super.Expression(args);
}
} else {
return yield* super.Expression(args);
}
}
},
),
});
};
export default enhanceLanguageWithJSX;