Skip to content
Snippets Groups Projects
parser.pegjs 5.73 KiB
Newer Older
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	const {
marihachi's avatar
marihachi committed
		createNode,
marihachi's avatar
marihachi committed
		mergeText,
		setConsumeCount,
		consumeDynamically
	} = require('./util');
marihachi's avatar
marihachi committed
	function applyParser(input, startRule) {
marihachi's avatar
marihachi committed
		let parseFunc = peg$parse;
marihachi's avatar
marihachi committed
		return parseFunc(input, startRule ? { startRule } : { });
marihachi's avatar
marihachi committed
	}
marihachi's avatar
marihachi committed

	// emoji

marihachi's avatar
marihachi committed
	const emojiRegex = require('twemoji-parser/dist/lib/regex').default;
marihachi's avatar
marihachi committed
	const anchoredEmojiRegex = RegExp(`^(?:${emojiRegex.source})`);

	/**
	 * check if the input matches the emoji regexp.
	 * if they match, set the byte length of the emoji.
	*/
	function matchUnicodeEmoji() {
		const offset = location().start.offset;
		const src = input.substr(offset);

		const result = anchoredEmojiRegex.exec(src);
		if (result != null) {
marihachi's avatar
marihachi committed
			setConsumeCount(result[0].length); // length(utf-16 byte length) of emoji sequence.
marihachi's avatar
marihachi committed
			return true;
		}

		return false;
	}
marihachi's avatar
marihachi committed
}
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
//
// parsers
//

fullParser
	= nodes:(&. n:(block / inline) { return n; })* { return mergeText(nodes); }
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
plainParser
marihachi's avatar
marihachi committed
	= nodes:(&. n:(emoji / text) { return n; })* { return mergeText(nodes); }
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
inlineParser
marihachi's avatar
marihachi committed
	= nodes:(&. n:inline { return n; })* { return mergeText(nodes); }

//
// block rules
//
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
block
marihachi's avatar
marihachi committed
	= quote
marihachi's avatar
marihachi committed
	/ search
marihachi's avatar
marihachi committed
	/ codeBlock
marihachi's avatar
marihachi committed
	/ mathBlock
	/ center
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
// block: quote

quote
marihachi's avatar
marihachi committed
	= head:quoteLine tails:(LF line:quoteLine { return line; })*
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	const lines = [head, ...tails];
	const children = applyParser(lines.join('\n'), 'fullParser');
	return createNode('quote', { }, children);
marihachi's avatar
marihachi committed
}

marihachi's avatar
marihachi committed
quoteLine
	= BEGIN ">" _? text:$(CHAR+) END { return text; }
marihachi's avatar
marihachi committed

// block: search

search
marihachi's avatar
marihachi committed
	= BEGIN q:searchQuery _ searchKey END
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	return createNode('search', {
marihachi's avatar
marihachi committed
		query: q,
marihachi's avatar
marihachi committed
		content: text()
marihachi's avatar
marihachi committed
	});
}

marihachi's avatar
marihachi committed
searchQuery
	= (!(_ searchKey END) CHAR)+ { return text(); }
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
searchKey
	= "[" ("検索" / "Search"i) "]"
	/ "検索"
	/ "Search"i
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
// block: codeBlock
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
codeBlock
	= BEGIN "```" lang:$(CHAR*) LF code:codeBlockLines LF "```" END
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	lang = lang.trim();
	return createNode('blockCode', {
		code: code,
marihachi's avatar
marihachi committed
		lang: lang.length > 0 ? lang : null,
	});
}
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
codeBlockLines
	= head:codeBlockLine tails:(LF line:codeBlockLine { return line; })*
{ return text(); }
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
codeBlockLine
	= BEGIN (!(BEGIN "```" END) CHAR)* END { return text(); }
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
// block: mathBlock

mathBlock
marihachi's avatar
marihachi committed
	= BEGIN "\\[" LF? formula:mathBlockLines LF? "\\]" END
marihachi's avatar
marihachi committed
	return createNode('mathBlock', {
		formula: formula.trim()
marihachi's avatar
marihachi committed
mathBlockLines
	= mathBlockLine (LF mathBlockLine)*
{ return text(); }

mathBlockLine
	= (!("\\]" END) CHAR)+
// block: center

center
marihachi's avatar
marihachi committed
	= BEGIN "<center>" LF? content:centerLines LF? "</center>" END
marihachi's avatar
marihachi committed
	const children = applyParser(content, 'inlineParser');
	return createNode('center', { }, children);
marihachi's avatar
marihachi committed
centerLines
	= centerLine (LF centerLine)*
{ return text(); }

centerLine
	= (!("</center>" END) CHAR)+

//
// inline rules
//

inline
marihachi's avatar
marihachi committed
	= emoji
	/ big
marihachi's avatar
marihachi committed
	/ bold
	/ small
marihachi's avatar
marihachi committed
	/ italic
marihachi's avatar
marihachi committed
	/ strike
	/ inlineCode
	/ mathInline
marihachi's avatar
marihachi committed
	/ hashtag
marihachi's avatar
marihachi committed
	/ url
marihachi's avatar
marihachi committed
	/ text
marihachi's avatar
marihachi committed
// inline: emoji

emoji
	= customEmoji / unicodeEmoji

customEmoji
	= ":" name:emojiName ":"
{
	return createNode('emoji', { name: name });
}

emojiName
	= [a-z0-9_+-]i+ { return text(); }

// NOTE: if the text matches one of the emojis, it will count the length of the emoji sequence and consume it.
unicodeEmoji
marihachi's avatar
marihachi committed
	= &{ return matchUnicodeEmoji(); } (&{ return consumeDynamically(); } .)+
marihachi's avatar
marihachi committed
{
	return createNode('emoji', { emoji: text() });
}

marihachi's avatar
marihachi committed
// inline: big
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
big
marihachi's avatar
marihachi committed
	= "***" content:(!"***" i:inline { return i; })+ "***"
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	return createNode('fn', {
		name: 'tada',
		args: { }
marihachi's avatar
marihachi committed
	}, mergeText(content));
marihachi's avatar
marihachi committed
}
Marihachi's avatar
Marihachi committed

marihachi's avatar
marihachi committed
// inline: bold

marihachi's avatar
marihachi committed
	= "**" content:(!"**" i:inline { return i; })+ "**"
{
marihachi's avatar
marihachi committed
	return createNode('bold', { }, mergeText(content));
marihachi's avatar
marihachi committed
}
marihachi's avatar
marihachi committed
	/ "__" content:$(!"__" c:[a-z0-9 \t]i { return c; })+ "__"
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	const parsedContent = applyParser(content, 'inlineParser');
marihachi's avatar
marihachi committed
	return createNode('bold', { }, parsedContent);
marihachi's avatar
marihachi committed
}
marihachi's avatar
marihachi committed

// inline: small

small
	= "<small>" content:(!"</small>" i:inline { return i; })+ "</small>"
{
marihachi's avatar
marihachi committed
	return createNode('small', { }, mergeText(content));
marihachi's avatar
marihachi committed
// inline: italic

italic
	= "<i>" content:(!"</i>" i:inline { return i; })+ "</i>"
{
	return createNode('italic', { }, mergeText(content));
}
	/ "*" content:$(!"*" [a-z0-9 \t]i)+ "*"
{
	const parsedContent = applyParser(content, 'inlineParser');
	return createNode('italic', { }, parsedContent);
}
	/ "_" content:$(!"_" [a-z0-9 \t]i)+ "_"
{
	const parsedContent = applyParser(content, 'inlineParser');
	return createNode('italic', { }, parsedContent);
}

marihachi's avatar
marihachi committed
// inline: strike

strike
	= "~~" content:(!("~" / LF) i:inline { return i; })+ "~~"
{
	return createNode('strike', { }, mergeText(content));
}

marihachi's avatar
marihachi committed
// inline: inlineCode

inlineCode
	= "`" content:$(!"`" c:CHAR { return c; })+ "`"
{
marihachi's avatar
marihachi committed
	return createNode('inlineCode', {
marihachi's avatar
marihachi committed
		code: content
	});
}

marihachi's avatar
marihachi committed
// inline: mathInline

mathInline
	= "\\(" content:$(!"\\)" c:CHAR { return c; })+ "\\)"
{
marihachi's avatar
marihachi committed
	return createNode('mathInline', {
marihachi's avatar
marihachi committed
		formula: content
	});
}

marihachi's avatar
marihachi committed
// inline: hashtag

hashtag
	= "#" content:hashtagContent
{
	return createNode('hashtag', { hashtag: content });
}

hashtagContent
	= (hashtagBracketPair / hashtagChar)+ { return text(); }

hashtagBracketPair
	= "(" hashtagContent* ")"
	/ "[" hashtagContent* "]"
	/ "「" hashtagContent* "」"

hashtagChar
	= ![  \t.,!?'"#:\/\[\]【】()「」] CHAR

marihachi's avatar
marihachi committed
// inline: URL

url
	= "<" url:urlFormat ">"
{
	return createNode('url', { url: url });
}
	/ url:urlFormat
{
	return createNode('url', { url: url });
}

urlFormat
	= "http" "s"? "://" urlContent
{
	return text();
}

urlContent
	= urlContentPart+

urlContentPart
	= urlBracketPair
	/ [.,] &urlContentPart // last char is neither "." nor ",".
	/ [a-z0-9/:%#@$&?!~=+-]i

urlBracketPair
	= "(" urlContentPart* ")"
	/ "[" urlContentPart* "]"

marihachi's avatar
marihachi committed
// inline: text
marihachi's avatar
marihachi committed
text
	= . { return createNode('text', { text: text() }); }
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
//
// General
//
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
BEGIN "beginning of line"
marihachi's avatar
marihachi committed
	= &{ return location().start.column == 1; }

marihachi's avatar
marihachi committed
END "end of line"
	= &LF / EOF

marihachi's avatar
marihachi committed
EOF
	= !.

marihachi's avatar
marihachi committed
CHAR
	= !LF . { return text(); }

LF
	= "\r\n" / [\r\n]

marihachi's avatar
marihachi committed
_ "whitespace"
marihachi's avatar
marihachi committed
	= [  \t]