Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap field/slot references in <var class="field"> #96

Merged
merged 8 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Inside a paragraph, list item, or header, the following inline formatting elemen

**Variables** are written as `_x_` and are translated to `<var>x</var>`. Variables cannot contain whitespace or other formatting characters.

**Fields** are written as `[[f]]` and are translated as `<var class="field">[[f]]</var>`. Field names must match regular expression `/^[a-zA-Z0-9_]+$/`.

**Values** are written as `*x*` and are translated to `<emu-val>x</emu-val>`. Values cannot contain asterisks.

**Code** is written as `` `x` `` and is translated to `<code>x</code>`. Code cannot contain backticks.
Expand Down
14 changes: 11 additions & 3 deletions src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
TagNode,
UnderscoreNode,
StarNode,
DoubleBracketsNode,
OrderedListItemNode,
UnorderedListItemNode,
OrderedListNode,
Expand Down Expand Up @@ -72,6 +73,9 @@ export class Emitter {
case 'tilde':
this.emitTilde(node);
break;
case 'double-brackets':
this.emitFieldOrSlot(node);
break;
case 'comment':
case 'tag':
case 'opaqueTag':
Expand Down Expand Up @@ -125,6 +129,10 @@ export class Emitter {
this.str += `<var>${node.contents}</var>`;
}

emitFieldOrSlot(node: DoubleBracketsNode) {
this.str += `<var class="field">[[${node.contents}]]</var>`;
}

emitTag(tag: OpaqueTagNode | CommentNode | TagNode) {
this.str += tag.contents;
}
Expand Down Expand Up @@ -159,9 +167,9 @@ export class Emitter {
this.str += '>' + pipe.nonTerminal + '</emu-nt>';
}

wrapFragment(wrapping: string, fragment: Node[]) {
this.str += `<${wrapping}>`;
wrapFragment(tagName: string, fragment: Node[], attrs: string = '') {
this.str += `<${tagName}${attrs}>`;
this.emitFragment(fragment);
this.str += `</${wrapping}>`;
this.str += `</${tagName}>`;
}
}
22 changes: 21 additions & 1 deletion src/node-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export type WhitespaceToken = {
location: LocationRange;
};

export type DoubleBracketsToken = {
name: 'double-brackets';
contents: string;
location: LocationRange;
};

export type TextToken = {
name: 'text';
contents: string;
Expand Down Expand Up @@ -96,6 +102,7 @@ export type Token =
| ParabreakToken
| LinebreakToken
| WhitespaceToken
| DoubleBracketsToken
| TextToken
| CommentToken
| TagToken
Expand Down Expand Up @@ -168,6 +175,12 @@ export type PipeNode = {
location: LocationRange;
};

export type DoubleBracketsNode = {
name: 'double-brackets';
contents: string;
location: LocationRange;
};

export type FormatNode = StarNode | UnderscoreNode | TickNode | TildeNode | PipeNode;

export type UnorderedListNode = {
Expand Down Expand Up @@ -201,7 +214,13 @@ export type OrderedListItemNode = {
location: LocationRange;
};

export type FragmentNode = TextNode | FormatNode | CommentNode | TagNode | OpaqueTagNode;
export type FragmentNode =
| TextNode
| FormatNode
| CommentNode
| TagNode
| OpaqueTagNode
| DoubleBracketsNode;

export type ListNode = UnorderedListNode | OrderedListNode;

Expand All @@ -211,6 +230,7 @@ export type Node =
| CommentNode
| AlgorithmNode
| TextNode
| DoubleBracketsNode
| StarNode
| UnderscoreNode
| TickNode
Expand Down
16 changes: 13 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,12 @@ export class Parser {
frag = frag.concat(f);
}
}
} else if (tok.name === 'comment' || tok.name === 'tag' || tok.name === 'opaqueTag') {
} else if (
tok.name === 'comment' ||
tok.name === 'tag' ||
tok.name === 'opaqueTag' ||
tok.name === 'double-brackets'
) {
frag.push(tok);
this._t.next();
} else if (isList(tok)) {
Expand Down Expand Up @@ -241,7 +246,12 @@ export class Parser {
lastRealTok = lastWsTok;
}

if (tok.name === 'opaqueTag' || tok.name === 'comment' || tok.name === 'tag') {
if (
tok.name === 'opaqueTag' ||
tok.name === 'comment' ||
tok.name === 'tag' ||
tok.name === 'double-brackets'
) {
break;
}

Expand Down Expand Up @@ -289,6 +299,7 @@ export class Parser {
opts: ParseFragmentOpts
): (TextNode | CommentNode | TagNode | FormatNode)[] {
const startTok = this._t.next() as FormatToken;
const start = this.getPos(startTok);
let contents: (TextNode | CommentNode | TagNode)[] = [];

if (format === 'underscore') {
Expand All @@ -300,7 +311,6 @@ export class Parser {
}

const nextTok = this._t.peek();
const start = this.getPos(startTok);

// fragment ended but we don't have a close format. Convert this node into a text node.
if (nextTok.name !== format) {
Expand Down
35 changes: 30 additions & 5 deletions src/tokenizer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Unlocated, Token, AttrToken, Position } from './node-types';

const fieldOrSlotRegexp = /^\[\[[a-zA-Z0-9_]+\]\]/;
const tagRegexp = /^<[/!]?(\w[\w-]*)(\s+\w[\w-]*(\s*=\s*("[^"]*"|'[^']*'|[^><"'=`]+))?)*\s*>/;
const commentRegexp = /^<!--[\w\W]*?-->/;
const attrRegexp = /^\[ *[\w-]+ *= *"(?:[^"\\\x00-\x1F]|\\["\\/bfnrt]|\\u[a-fA-F]{4})*" *(?:, *[\w-]+ *= *"(?:[^"\\\x00-\x1F]|\\["\\/bfnrt]|\\u[a-fA-F]{4})*" *)*] /;
Expand Down Expand Up @@ -79,6 +80,13 @@ export class Tokenizer {
if (chr === '\\') {
out += this.scanEscape();
} else if (isChars(chr)) {
out += chr;
this.pos++;
} else if (chr === '[') {
if (this.tryScanFieldOrSlot()) {
break;
}

out += chr;
this.pos++;
} else if (chr === '<') {
Expand Down Expand Up @@ -115,13 +123,20 @@ export class Tokenizer {
return this.str.slice(start, this.pos);
}

// does not actually consume the tag
// you should manually `this.pos += tag[0].length;` if you end up consuming it
tryScanTag() {
if (this.str[this.pos] !== '<') {
// does not actually consume the field/slot
// you should manually `this.pos += result.length;` if you end up consuming it
tryScanFieldOrSlot() {
const match = this.str.slice(this.pos).match(fieldOrSlotRegexp);
if (!match) {
return;
}

return match[0];
}

// does not actually consume the tag
// you should manually `this.pos += tag[0].length;` if you end up consuming it
tryScanTag() {
const match = this.str.slice(this.pos).match(tagRegexp);
if (!match) {
return;
Expand Down Expand Up @@ -297,6 +312,16 @@ export class Tokenizer {
} else if (isChars(chr)) {
this.enqueue({ name: 'text', contents: this.scanChars() }, start);
return;
} else if (chr === '[') {
const fieldOrSlot = this.tryScanFieldOrSlot();
if (fieldOrSlot) {
this.pos += fieldOrSlot.length;
this.enqueue({ name: 'double-brackets', contents: fieldOrSlot.slice(2, -2) }, start);
return;
}

// didn't find a valid field/slot, so fall back to text.
this.enqueue({ name: 'text', contents: this.scanChars() }, start);
} else if (chr === '<') {
if (
this.str[this.pos + 1] === '!' &&
Expand Down Expand Up @@ -432,7 +457,7 @@ function isWhitespace(chr: string) {
}

function isChars(chr: string) {
return !isFormat(chr) && chr !== '\n' && chr !== ' ' && chr !== '\t';
return !isFormat(chr) && chr !== '\n' && chr !== ' ' && chr !== '\t' && chr !== '[';
}

function isFormat(chr: string) {
Expand Down
1 change: 1 addition & 0 deletions src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const childKeys = {
comment: [],
algorithm: ['contents'],
text: [],
'double-brackets': [],
star: ['contents'],
underscore: [],
tick: ['contents'],
Expand Down
2 changes: 1 addition & 1 deletion test/cases/formats-in-text.fragment.ecmarkdown
Original file line number Diff line number Diff line change
@@ -1 +1 @@
*star*s s*tars* _var_s v_ars_ `tick`s t`icks` |pipe|s p|ipes| ~tilde~s t~ildes~
*star*s s*tars* _var_s v_ars_ `tick`s t`icks` |pipe|s p|ipes| ~tilde~s t~ildes~ a.[[b]] a.[[B]] a.[b] a.[[&lt;_b_>]] a.[[%b%]]
2 changes: 1 addition & 1 deletion test/cases/formats-in-text.fragment.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<emu-val>star</emu-val>s s*tars* <var>var</var>s v_ars_ <code>tick</code>s t<code>icks</code> <emu-nt>pipe</emu-nt>s p|ipes| <emu-const>tilde</emu-const>s t~ildes~
<emu-val>star</emu-val>s s*tars* <var>var</var>s v_ars_ <code>tick</code>s t<code>icks</code> <emu-nt>pipe</emu-nt>s p|ipes| <emu-const>tilde</emu-const>s t~ildes~ a.<var class="field">[[b]]</var> a.<var class="field">[[B]]</var> a.[b] a.[[&lt;<var>b</var>>]] a.[[%b%]]
2 changes: 1 addition & 1 deletion test/cases/iterator-close.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<li>ReturnIfAbrupt(<var>hasReturn</var>).<ol>
<li>If <var>hasReturn</var> is <emu-val>true</emu-val>, then<ol>
<li>Let <var>innerResult</var> be Invoke(<var>iterator</var>, <code>"return"</code>, ( )).</li>
<li>If <var>completion</var>.[[type]] is not <emu-const>throw</emu-const> and <var>innerResult</var>.[[type]] is <emu-const>throw</emu-const>, then<ol>
<li>If <var>completion</var>.<var class="field">[[type]]</var> is not <emu-const>throw</emu-const> and <var>innerResult</var>.<var class="field">[[type]]</var> is <emu-const>throw</emu-const>, then<ol>
<li>Return <var>innerResult</var>.</li>
</ol>
</li>
Expand Down