Major directive refactor (#195)

Fixes #188 #144 #76: major refactor of directive parsing, rendering, styling
This commit is contained in:
Zef Hemel
2022-12-14 20:04:20 +01:00
committed by GitHub
parent 79e1151ee6
commit aaebea5e54
44 changed files with 656 additions and 347 deletions
+20
View File
@@ -0,0 +1,20 @@
import { Tag } from "../deps.ts";
export const CommandLinkTag = Tag.define();
export const CommandLinkNameTag = Tag.define();
export const WikiLinkTag = Tag.define();
export const WikiLinkPageTag = Tag.define();
export const CodeInfoTag = Tag.define();
export const TaskTag = Tag.define();
export const TaskMarkerTag = Tag.define();
export const CommentTag = Tag.define();
export const CommentMarkerTag = Tag.define();
export const BulletList = Tag.define();
export const OrderedList = Tag.define();
export const Highlight = Tag.define();
export const HorizontalRuleTag = Tag.define();
export const DirectiveTag = Tag.define();
export const DirectiveStartTag = Tag.define();
export const DirectiveEndTag = Tag.define();
export const DirectiveProgramTag = Tag.define();
+68
View File
@@ -0,0 +1,68 @@
import { Tag } from "../deps.ts";
import type { MarkdownConfig } from "../deps.ts";
import { System } from "../../plugos/system.ts";
import { Manifest } from "../manifest.ts";
export type MDExt = {
// unicode char code for efficiency .charCodeAt(0)
firstCharCodes: number[];
regex: RegExp;
nodeType: string;
tag: Tag;
styles: { [key: string]: string };
className?: string;
};
export function mdExtensionSyntaxConfig({
regex,
firstCharCodes,
nodeType,
}: MDExt): MarkdownConfig {
return {
defineNodes: [nodeType],
parseInline: [
{
name: nodeType,
parse(cx, next, pos) {
if (!firstCharCodes.includes(next)) {
return -1;
}
let match = regex.exec(cx.slice(pos, cx.end));
if (!match) {
return -1;
}
return cx.addElement(cx.elt(nodeType, pos, pos + match[0].length));
},
// after: "Emphasis",
},
],
};
}
export function mdExtensionStyleTags({ nodeType, tag }: MDExt): {
[selector: string]: Tag | readonly Tag[];
} {
return {
[nodeType]: tag,
};
}
export function loadMarkdownExtensions(system: System<any>): MDExt[] {
const mdExtensions: MDExt[] = [];
for (const plug of system.loadedPlugs.values()) {
const manifest = plug.manifest as Manifest;
if (manifest.syntax) {
for (const [nodeType, def] of Object.entries(manifest.syntax)) {
mdExtensions.push({
nodeType,
tag: Tag.define(),
firstCharCodes: def.firstCharacters.map((ch) => ch.charCodeAt(0)),
regex: new RegExp("^" + def.regex),
styles: def.styles,
className: def.className,
});
}
}
}
return mdExtensions;
}
+16
View File
@@ -0,0 +1,16 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
export const parser = LRParser.deserialize({
version: 14,
states: "&`OVQPOOOmQQO'#C^QOQPOOOtQPO'#C`OyQPO'#ClO!OQPO'#CnO!TQPO'#CqO!YQPO'#CsOOQO'#Cv'#CvO!bQQO,58xO!iQQO'#CcO#WQQO'#CbOOQO,58z,58zOOQO,59W,59WO#oQQO,59YO$ZQQO'#D`OOQO,59],59]OOQO,59_,59_OOQO-E6t-E6tO$rQQO,58}OtQPO'#CxO%ZQQO,58|OOQO'#Cp'#CpOOQO1G.t1G.tO%rQPO'#CyO%wQQO,59zOOQO'#Cg'#CgO$rQQO'#CjOOQO'#Cd'#CdOOQO1G.i1G.iOOQO,59d,59dOOQO-E6v-E6vOOQO,59e,59eOOQO-E6w-E6wO&`QPO'#DRO&hQPO,59UO$rQQO'#CwO&mQPO,59mOOQO1G.p1G.pOOQO,59c,59cOOQO-E6u-E6u",
stateData: "&u~OpOS~ORPO~OTROaSOcTOfUOhVO~OnQX~P[ORYO~OX]O~OR^O~OR_O~OYaOiaO~OnQa~P[OqcOxcOycOzcO{cO|cO}cO!OcO!PcO~O_dOTUXaUXcUXfUXhUXnUX~O!QfO!RfOTbaabacbafbahbanba~OvhOT!SXa!SXc!SXf!SXh!SXn!SX~OXlOYlO[lO]lOrjOsjOtkO~O_dOTUaaUacUafUahUanUa~ORpO~OvhOT!Saa!Sac!Saf!Sah!San!Sa~OvtOwuX~OwvO~OvtOwua~O",
goto: "#g!TPP!UP!XP!]!`!fPP!oPP!oP!XP!XP!t!XP!XPP!w!}#T#ZPPPPPPP#aPPPPPPPPPPPP#dRQOTWPXR[RQZRRndQmcQrkRwtVlcktRg^QXPRbXQurRxuQeZRoeQi_RqiRskR`U",
nodeNames: "⚠ Program Query Name WhereClause Where LogicalExpr FilterExpr Value Number String Bool Regex Null List And LimitClause Limit OrderClause Order OrderDirection SelectClause Select RenderClause Render PageRef",
maxTerm: 50,
skippedNodes: [0],
repeatNodeCount: 4,
tokenData: "Ao~R|X^#{pq#{qr$prs%T|}%o}!O%t!P!Q&V!Q![&|!^!_'U!_!`'c!`!a'p!c!}%t!}#O'}#P#Q(n#R#S%t#T#U(s#U#W%t#W#X+Y#X#Y%t#Y#Z-U#Z#]%t#]#^/f#^#`%t#`#a0b#a#b%t#b#c2u#c#d4q#d#f%t#f#g7h#g#h:d#h#i=`#i#k%t#k#l?[#l#o%t#y#z#{$f$g#{#BY#BZ#{$IS$I_#{$Ip$Iq%T$Iq$Ir%T$I|$JO#{$JT$JU#{$KV$KW#{&FU&FV#{~$QYp~X^#{pq#{#y#z#{$f$g#{#BY#BZ#{$IS$I_#{$I|$JO#{$JT$JU#{$KV$KW#{&FU&FV#{~$sP!_!`$v~${Pz~#r#s%O~%TO!O~~%WUOr%Trs%js$Ip%T$Ip$Iq%j$Iq$Ir%j$Ir~%T~%oOY~~%tOv~P%ySRP}!O%t!c!}%t#R#S%t#T#o%t~&[V[~OY&VZ]&V^!P&V!P!Q&q!Q#O&V#O#P&v#P~&V~&vO[~~&yPO~&V~'RPX~!Q![&|~'ZPq~!_!`'^~'cOx~~'hPy~#r#s'k~'pO}~~'uP|~!_!`'x~'}O{~R(SPtQ!}#O(VP(YRO#P(V#P#Q(c#Q~(VP(fP#P#Q(iP(nOiP~(sOw~R(xWRP}!O%t!c!}%t#R#S%t#T#b%t#b#c)b#c#g%t#g#h*^#h#o%tR)gURP}!O%t!c!}%t#R#S%t#T#W%t#W#X)y#X#o%tR*QS_QRP}!O%t!c!}%t#R#S%t#T#o%tR*cURP}!O%t!c!}%t#R#S%t#T#V%t#V#W*u#W#o%tR*|S!RQRP}!O%t!c!}%t#R#S%t#T#o%tR+_URP}!O%t!c!}%t#R#S%t#T#X%t#X#Y+q#Y#o%tR+vURP}!O%t!c!}%t#R#S%t#T#g%t#g#h,Y#h#o%tR,_URP}!O%t!c!}%t#R#S%t#T#V%t#V#W,q#W#o%tR,xS!QQRP}!O%t!c!}%t#R#S%t#T#o%tR-ZTRP}!O%t!c!}%t#R#S%t#T#U-j#U#o%tR-oURP}!O%t!c!}%t#R#S%t#T#`%t#`#a.R#a#o%tR.WURP}!O%t!c!}%t#R#S%t#T#g%t#g#h.j#h#o%tR.oURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y/R#Y#o%tR/YSsQRP}!O%t!c!}%t#R#S%t#T#o%tR/kURP}!O%t!c!}%t#R#S%t#T#b%t#b#c/}#c#o%tR0US!PQRP}!O%t!c!}%t#R#S%t#T#o%tR0gURP}!O%t!c!}%t#R#S%t#T#]%t#]#^0y#^#o%tR1OURP}!O%t!c!}%t#R#S%t#T#a%t#a#b1b#b#o%tR1gURP}!O%t!c!}%t#R#S%t#T#]%t#]#^1y#^#o%tR2OURP}!O%t!c!}%t#R#S%t#T#h%t#h#i2b#i#o%tR2iSaQRP}!O%t!c!}%t#R#S%t#T#o%tR2zURP}!O%t!c!}%t#R#S%t#T#i%t#i#j3^#j#o%tR3cURP}!O%t!c!}%t#R#S%t#T#`%t#`#a3u#a#o%tR3zURP}!O%t!c!}%t#R#S%t#T#`%t#`#a4^#a#o%tR4eSRP]Q}!O%t!c!}%t#R#S%t#T#o%tR4vURP}!O%t!c!}%t#R#S%t#T#f%t#f#g5Y#g#o%tR5_URP}!O%t!c!}%t#R#S%t#T#W%t#W#X5q#X#o%tR5vURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y6Y#Y#o%tR6_URP}!O%t!c!}%t#R#S%t#T#f%t#f#g6q#g#o%tR6vTRPpq7V}!O%t!c!}%t#R#S%t#T#o%tQ7YP#U#V7]Q7`P#m#n7cQ7hOcQR7mURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y8P#Y#o%tR8UURP}!O%t!c!}%t#R#S%t#T#b%t#b#c8h#c#o%tR8mURP}!O%t!c!}%t#R#S%t#T#W%t#W#X9P#X#o%tR9UURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y9h#Y#o%tR9mURP}!O%t!c!}%t#R#S%t#T#f%t#f#g:P#g#o%tR:WSRPhQ}!O%t!c!}%t#R#S%t#T#o%tR:iURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y:{#Y#o%tR;QURP}!O%t!c!}%t#R#S%t#T#`%t#`#a;d#a#o%tR;iURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y;{#Y#o%tR<QURP}!O%t!c!}%t#R#S%t#T#V%t#V#W<d#W#o%tR<iURP}!O%t!c!}%t#R#S%t#T#h%t#h#i<{#i#o%tR=SSRPfQ}!O%t!c!}%t#R#S%t#T#o%tR=eURP}!O%t!c!}%t#R#S%t#T#f%t#f#g=w#g#o%tR=|URP}!O%t!c!}%t#R#S%t#T#i%t#i#j>`#j#o%tR>eURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y>w#Y#o%tR?OSrQRP}!O%t!c!}%t#R#S%t#T#o%tR?aURP}!O%t!c!}%t#R#S%t#T#[%t#[#]?s#]#o%tR?xURP}!O%t!c!}%t#R#S%t#T#X%t#X#Y@[#Y#o%tR@aURP}!O%t!c!}%t#R#S%t#T#f%t#f#g@s#g#o%tR@xURP}!O%t!c!}%t#R#S%t#T#X%t#X#YA[#Y#o%tRAcSRPTQ}!O%t!c!}%t#R#S%t#T#o%t",
tokenizers: [0, 1],
topRules: {"Program":[0,1]},
tokenPrec: 0
})
@@ -0,0 +1,27 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Program = 1,
Query = 2,
Name = 3,
WhereClause = 4,
Where = 5,
LogicalExpr = 6,
FilterExpr = 7,
Value = 8,
Number = 9,
String = 10,
Bool = 11,
Regex = 12,
Null = 13,
List = 14,
And = 15,
LimitClause = 16,
Limit = 17,
OrderClause = 18,
Order = 19,
OrderDirection = 20,
SelectClause = 21,
Select = 22,
RenderClause = 23,
Render = 24,
PageRef = 25
+64
View File
@@ -0,0 +1,64 @@
import type { ParseTree } from "$sb/lib/tree.ts";
import type { Language, SyntaxNode } from "../deps.ts";
export function lezerToParseTree(
text: string,
n: SyntaxNode,
offset = 0,
): ParseTree {
let children: ParseTree[] = [];
let nodeText: string | undefined;
let child = n.firstChild;
while (child) {
children.push(lezerToParseTree(text, child));
child = child.nextSibling;
}
if (children.length === 0) {
children = [
{
from: n.from + offset,
to: n.to + offset,
text: text.substring(n.from, n.to),
},
];
} else {
const newChildren: ParseTree[] = [];
let index = n.from;
for (const child of children) {
const s = text.substring(index, child.from);
if (s) {
newChildren.push({
from: index + offset,
to: child.from! + offset,
text: s,
});
}
newChildren.push(child);
index = child.to!;
}
const s = text.substring(index, n.to);
if (s) {
newChildren.push({ from: index + offset, to: n.to + offset, text: s });
}
children = newChildren;
}
const result: ParseTree = {
type: n.name,
from: n.from + offset,
to: n.to + offset,
};
if (children.length > 0) {
result.children = children;
}
if (nodeText) {
result.text = nodeText;
}
return result;
}
export function parse(language: Language, text: string): ParseTree {
const tree = lezerToParseTree(text, language.parser.parse(text).topNode);
return tree;
}
+91
View File
@@ -0,0 +1,91 @@
import { parse } from "./parse_tree.ts";
import buildMarkdown from "./parser.ts";
import {
collectNodesOfType,
findNodeOfType,
renderToText,
} from "../../plug-api/lib/tree.ts";
import { assertEquals, assertNotEquals } from "../../test_deps.ts";
const sample1 = `---
type: page
tags:
- hello
- world
---
# This is a doc
Here is a [[wiki link]] and a [[wiki link|alias]].
Supper`;
const sampleInvalid1 = `---
name: Zef
# This is a doc
Supper`;
Deno.test("Test parser", () => {
const lang = buildMarkdown([]);
let tree = parse(lang, sample1);
// console.log("tree", JSON.stringify(tree, null, 2));
// Check if rendering back to text works
assertEquals(renderToText(tree), sample1);
// Find wiki link and wiki link alias
const links = collectNodesOfType(tree, "WikiLink");
assertEquals(links.length, 2);
const nameNode = findNodeOfType(links[0], "WikiLinkPage");
assertEquals(nameNode?.children![0].text, "wiki link");
// Check if alias is parsed properly
const aliasNode = findNodeOfType(links[1], "WikiLinkAlias");
assertEquals(aliasNode?.children![0].text, "alias");
// Find frontmatter
let node = findNodeOfType(tree, "FrontMatter");
assertNotEquals(node, undefined);
tree = parse(lang, sampleInvalid1);
node = findNodeOfType(tree, "FrontMatter");
// console.log("Invalid node", node);
assertEquals(node, undefined);
});
const directiveSample = `
Before
<!-- #query page -->
Body line 1
Body line 2
<!-- /query -->
End
`;
const nestedDirectiveExample = `
Before
<!-- #query page -->
1
<!-- #eval 10 * 10 -->
100
<!-- /eval -->
3
<!-- /query -->
End
`;
Deno.test("Test directive parser", () => {
const lang = buildMarkdown([]);
let tree = parse(lang, directiveSample);
// console.log("tree", JSON.stringify(tree, null, 2));
assertEquals(renderToText(tree), directiveSample);
tree = parse(lang, nestedDirectiveExample);
// console.log("tree", JSON.stringify(tree, null, 2));
assertEquals(renderToText(tree), nestedDirectiveExample);
const orderByExample = `<!-- #query page order by lastModified -->
<!-- /query -->`;
tree = parse(lang, orderByExample);
console.log("Tree", JSON.stringify(tree, null, 2));
});
+374
View File
@@ -0,0 +1,374 @@
import {
BlockContext,
Language,
LeafBlock,
LeafBlockParser,
Line,
markdown,
MarkdownConfig,
StreamLanguage,
Strikethrough,
styleTags,
Table,
tags as t,
TaskList,
yamlLanguage,
} from "../deps.ts";
import * as ct from "./customtags.ts";
import {
MDExt,
mdExtensionStyleTags,
mdExtensionSyntaxConfig,
} from "./markdown_ext.ts";
export const pageLinkRegex = /^\[\[([^\]\|]+)(\|([^\]]+))?\]\]/;
const WikiLink: MarkdownConfig = {
defineNodes: [
{ name: "WikiLink", style: ct.WikiLinkTag },
{ name: "WikiLinkPage", style: ct.WikiLinkPageTag },
{ name: "WikiLinkAlias", style: ct.WikiLinkPageTag },
{ name: "WikiLinkMark", style: t.processingInstruction },
],
parseInline: [
{
name: "WikiLink",
parse(cx, next, pos) {
let match: RegExpMatchArray | null;
if (
next != 91 /* '[' */ ||
!(match = pageLinkRegex.exec(cx.slice(pos, cx.end)))
) {
return -1;
}
const [fullMatch, page, pipePart, label] = match;
const endPos = pos + fullMatch.length;
let aliasElts: any[] = [];
if (pipePart) {
const pipeStartPos = pos + 2 + page.length;
aliasElts = [
cx.elt("WikiLinkMark", pipeStartPos, pipeStartPos + 1),
cx.elt(
"WikiLinkAlias",
pipeStartPos + 1,
pipeStartPos + 1 + label.length,
),
];
}
return cx.addElement(
cx.elt("WikiLink", pos, endPos, [
cx.elt("WikiLinkMark", pos, pos + 2),
cx.elt("WikiLinkPage", pos + 2, pos + 2 + page.length),
...aliasElts,
cx.elt("WikiLinkMark", endPos - 2, endPos),
]),
);
},
after: "Emphasis",
},
],
};
export const commandLinkRegex = /^\{\[([^\]\|]+)(\|([^\]]+))?\]\}/;
const CommandLink: MarkdownConfig = {
defineNodes: [
{ name: "CommandLink", style: { "CommandLink/...": ct.CommandLinkTag } },
{ name: "CommandLinkName", style: ct.CommandLinkNameTag },
{ name: "CommandLinkAlias", style: ct.CommandLinkNameTag },
{ name: "CommandLinkMark", style: t.processingInstruction },
],
parseInline: [
{
name: "CommandLink",
parse(cx, next, pos) {
let match: RegExpMatchArray | null;
if (
next != 123 /* '{' */ ||
!(match = commandLinkRegex.exec(cx.slice(pos, cx.end)))
) {
return -1;
}
const [fullMatch, command, pipePart, label] = match;
const endPos = pos + fullMatch.length;
let aliasElts: any[] = [];
if (pipePart) {
const pipeStartPos = pos + 2 + command.length;
aliasElts = [
cx.elt("CommandLinkMark", pipeStartPos, pipeStartPos + 1),
cx.elt(
"CommandLinkAlias",
pipeStartPos + 1,
pipeStartPos + 1 + label.length,
),
];
}
return cx.addElement(
cx.elt("CommandLink", pos, endPos, [
cx.elt("CommandLinkMark", pos, pos + 2),
cx.elt("CommandLinkName", pos + 2, pos + 2 + command.length),
...aliasElts,
cx.elt("CommandLinkMark", endPos - 2, endPos),
]),
);
},
after: "Emphasis",
},
],
};
const HighlightDelim = { resolve: "Highlight", mark: "HighlightMark" };
export const Highlight: MarkdownConfig = {
defineNodes: [
{
name: "Highlight",
style: { "Highlight/...": ct.Highlight },
},
{
name: "HighlightMark",
style: t.processingInstruction,
},
],
parseInline: [
{
name: "Highlight",
parse(cx, next, pos) {
if (next != 61 /* '=' */ || cx.char(pos + 1) != 61) return -1;
return cx.addDelimiter(HighlightDelim, pos, pos + 2, true, true);
},
after: "Emphasis",
},
],
};
class CommentParser implements LeafBlockParser {
nextLine() {
return false;
}
finish(cx: BlockContext, leaf: LeafBlock) {
cx.addLeafElement(
leaf,
cx.elt("Comment", leaf.start, leaf.start + leaf.content.length, [
// cx.elt("CommentMarker", leaf.start, leaf.start + 3),
...cx.parser.parseInline(leaf.content.slice(3), leaf.start + 3),
]),
);
return true;
}
}
export const Comment: MarkdownConfig = {
defineNodes: [{ name: "Comment", block: true }],
parseBlock: [
{
name: "Comment",
leaf(_cx, leaf) {
return /^%%\s/.test(leaf.content) ? new CommentParser() : null;
},
after: "SetextHeading",
},
],
};
// Directive parser
const directiveStart = /^\s*<!--\s*#([a-z]+)\s*(.*?)-->\s*/;
const directiveEnd = /^\s*<!--\s*\/(.*?)-->\s*/;
import { parser as directiveParser } from "./parse-query.js";
const highlightingDirectiveParser = directiveParser.configure({
props: [
styleTags({
"Name": t.variableName,
"String PageRef": t.string,
"Number": t.number,
"Where Limit Select Render Order OrderDirection And": t.keyword,
}),
],
});
export const Directive: MarkdownConfig = {
defineNodes: [
{ name: "Directive", block: true, style: ct.DirectiveTag },
{ name: "DirectiveStart", style: ct.DirectiveStartTag, block: true },
{ name: "DirectiveEnd", style: ct.DirectiveEndTag },
{ name: "DirectiveBody", block: true },
],
parseBlock: [{
name: "Directive",
parse: (cx, line: Line) => {
const match = directiveStart.exec(line.text);
if (!match) {
return false;
}
// console.log("Parsing directive", line.text);
const frontStart = cx.parsedPos;
const [fullMatch, directive, arg] = match;
const elts = [];
if (directive === "query") {
const queryParseTree = highlightingDirectiveParser.parse(arg);
elts.push(cx.elt(
"DirectiveStart",
cx.parsedPos,
cx.parsedPos + line.text.length + 1,
[cx.elt(queryParseTree, frontStart + fullMatch.indexOf(arg))],
));
} else {
elts.push(cx.elt(
"DirectiveStart",
cx.parsedPos,
cx.parsedPos + line.text.length + 1,
));
}
// console.log("Query parse tree", queryParseTree.topNode);
cx.nextLine();
const startPos = cx.parsedPos;
let endPos = startPos;
let text = "";
let lastPos = cx.parsedPos;
let nesting = 0;
while (true) {
if (directiveEnd.exec(line.text) && nesting === 0) {
break;
}
text += line.text + "\n";
endPos += line.text.length + 1;
if (directiveStart.exec(line.text)) {
nesting++;
}
if (directiveEnd.exec(line.text)) {
nesting--;
}
cx.nextLine();
if (cx.parsedPos === lastPos) {
// End of file, no progress made, there may be a better way to do this but :shrug:
return false;
}
lastPos = cx.parsedPos;
}
const directiveBodyTree = cx.parser.parse(text);
elts.push(
cx.elt("DirectiveBody", startPos, endPos, [
cx.elt(directiveBodyTree, startPos),
]),
);
endPos = cx.parsedPos + line.text.length;
elts.push(cx.elt(
"DirectiveEnd",
cx.parsedPos,
cx.parsedPos + line.text.length,
));
cx.nextLine();
cx.addElement(cx.elt("Directive", frontStart, endPos, elts));
return true;
},
before: "HTMLBlock",
}],
};
// FrontMatter parser
const yamlLang = StreamLanguage.define(yamlLanguage);
export const FrontMatter: MarkdownConfig = {
defineNodes: [
{ name: "FrontMatter", block: true },
{ name: "FrontMatterMarker" },
{ name: "FrontMatterCode" },
],
parseBlock: [{
name: "FrontMatter",
parse: (cx, line: Line) => {
if (cx.parsedPos !== 0) {
return false;
}
if (line.text !== "---") {
return false;
}
const frontStart = cx.parsedPos;
const elts = [
cx.elt(
"FrontMatterMarker",
cx.parsedPos,
cx.parsedPos + line.text.length + 1,
),
];
cx.nextLine();
const startPos = cx.parsedPos;
let endPos = startPos;
let text = "";
let lastPos = cx.parsedPos;
do {
text += line.text + "\n";
endPos += line.text.length + 1;
cx.nextLine();
if (cx.parsedPos === lastPos) {
// End of file, no progress made, there may be a better way to do this but :shrug:
return false;
}
lastPos = cx.parsedPos;
} while (line.text !== "---");
const yamlTree = yamlLang.parser.parse(text);
elts.push(
cx.elt("FrontMatterCode", startPos, endPos, [
cx.elt(yamlTree, startPos),
]),
);
endPos = cx.parsedPos + line.text.length;
elts.push(cx.elt(
"FrontMatterMarker",
cx.parsedPos,
cx.parsedPos + line.text.length,
));
cx.nextLine();
cx.addElement(cx.elt("FrontMatter", frontStart, endPos, elts));
return true;
},
before: "HorizontalRule",
}],
};
export default function buildMarkdown(mdExtensions: MDExt[]): Language {
return markdown({
extensions: [
WikiLink,
CommandLink,
FrontMatter,
Directive,
TaskList,
Comment,
Highlight,
Strikethrough,
Table,
...mdExtensions.map(mdExtensionSyntaxConfig),
{
props: [
styleTags({
Task: ct.TaskTag,
TaskMarker: ct.TaskMarkerTag,
Comment: ct.CommentTag,
"TableDelimiter SubscriptMark SuperscriptMark StrikethroughMark":
t.processingInstruction,
"TableHeader/...": t.heading,
TableCell: t.content,
CodeInfo: ct.CodeInfoTag,
HorizontalRule: ct.HorizontalRuleTag,
}),
...mdExtensions.map((mdExt) =>
styleTags(mdExtensionStyleTags(mdExt))
),
],
},
],
}).language;
}
+68
View File
@@ -0,0 +1,68 @@
@precedence { logic @left }
@top Program { Query }
@skip { space }
Query {
Name ( WhereClause | LimitClause | OrderClause | SelectClause | RenderClause )*
}
commaSep<content> { content ("," content)* }
WhereClause { Where LogicalExpr }
LimitClause { Limit Number }
OrderClause { Order Name OrderDirection? }
SelectClause { Select commaSep<Name> }
RenderClause { Render (PageRef | String) }
OrderDirection {
"desc" | "asc"
}
Value { Number | String | Bool | Regex | Null | List }
LogicalExpr { FilterExpr (And FilterExpr)* }
FilterExpr {
Name "<" Value
| Name "<=" Value
| Name "=" Value
| Name "!=" Value
| Name ">=" Value
| Name ">" Value
| Name "=~" Value
| Name "!=~" Value
| Name "in" Value
}
List { "[" commaSep<Value> "]" }
Bool {
"true" | "false"
}
@tokens {
space { std.whitespace+ }
Name { (std.asciiLetter | "-" | "_")+ }
Where { "where" }
Order { "order by" }
Select { "select" }
Render { "render" }
Limit { "limit" }
And { "and" }
Null { "null" }
String {
("\"" | "“" | "”") ![\"”“]* ("\"" | "“" | "”")
}
PageRef {
"[" "[" ![\]]* "]" "]"
}
Regex { "/" ( ![/\\\n\r] | "\\" _ )* "/"? }
Number { std.digit+ }
// @precedence { Where, Sort, Select, Render, Limit, And, Null, Name }
}