Skip to content
Snippets Groups Projects
parser.pegjs 6.89 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:(emojiCode / unicodeEmoji / 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
	= lines:quoteLine+
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	const children = applyParser(lines.join('\n'), 'fullParser');
	return createNode('quote', null, 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 sp:_ key: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: `${ q }${ sp }${ key }`
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
marihachi's avatar
marihachi committed
	= "[" ("検索" / "Search"i) "]" { return text(); }
marihachi's avatar
marihachi committed
	/ "検索"
	/ "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
marihachi's avatar
marihachi committed
	= BEGIN "```" lang:$(CHAR*) LF code:codeBlockContent 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
codeBlockContent
	= (!(LF "```" END) .)+
marihachi's avatar
marihachi committed
{ 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
marihachi's avatar
marihachi committed
	= (!"\\]" CHAR)+
// block: center

center
marihachi's avatar
marihachi committed
	= BEGIN "<center>" LF? content:(!(LF? "</center>" END) i:inline { return i; })+ LF? "</center>" END
	return createNode('center', null, mergeText(content));
marihachi's avatar
marihachi committed
//
// inline rules
//

inline
marihachi's avatar
marihachi committed
	= emojiCode
	/ unicodeEmoji
marihachi's avatar
marihachi committed
	/ 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
	/ mention
marihachi's avatar
marihachi committed
	/ hashtag
marihachi's avatar
marihachi committed
	/ url
marihachi's avatar
marihachi committed
	/ link
marihachi's avatar
marihachi committed
	/ fn
marihachi's avatar
marihachi committed
	/ text
marihachi's avatar
marihachi committed
// inline: emoji code
marihachi's avatar
marihachi committed

marihachi's avatar
marihachi committed
emojiCode
	= ":" name:emojiCodeName ":"
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	return createNode('emojiCode', { name: name });
marihachi's avatar
marihachi committed
}

marihachi's avatar
marihachi committed
emojiCodeName
marihachi's avatar
marihachi committed
	= [a-z0-9_+-]i+ { return text(); }

marihachi's avatar
marihachi committed
// inline: unicode emoji

marihachi's avatar
marihachi committed
// 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('unicodeEmoji', { emoji: text() });
marihachi's avatar
marihachi committed
}

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; })+ "**"
{
	return createNode('bold', null, mergeText(content));
marihachi's avatar
marihachi committed
}
marihachi's avatar
marihachi committed
	/ "__" content:$(!"__" c:([a-z0-9]i / _) { return c; })+ "__"
marihachi's avatar
marihachi committed
{
marihachi's avatar
marihachi committed
	const parsedContent = applyParser(content, 'inlineParser');
	return createNode('bold', null, parsedContent);
marihachi's avatar
marihachi committed
}
marihachi's avatar
marihachi committed

// inline: small

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

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

marihachi's avatar
marihachi committed
// inline: strike

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

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: mention

mention
	= "@" name:mentionName host:("@" host:mentionHost { return host; })?
{
	return createNode('mention', {
marihachi's avatar
marihachi committed
		username: name,
		host: host,
		acct: text()
marihachi's avatar
marihachi committed
	});
}

mentionName
	= !"-" mentionNamePart+ // first char is not "-".
{
	return text();
}

mentionNamePart
	= "-" &mentionNamePart // last char is not "-".
	/ [a-z0-9_]i

mentionHost
	= ![.-] mentionHostPart+ // first char is neither "." nor "-".
{
	return text();
}

mentionHostPart
	= [.-] &mentionHostPart // last char is neither "." nor "-".
	/ [a-z0-9_]i

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 ",".
marihachi's avatar
marihachi committed
	/ [a-z0-9_/:%#@$&?!~=+-]i
marihachi's avatar
marihachi committed

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

marihachi's avatar
marihachi committed
// inline: link

link
	= silent:"?"? "[" label:linkLabelPart+ "](" url:linkUrl ")"
marihachi's avatar
marihachi committed
{
	return createNode('link', {
		silent: (silent != null),
		url: url
	}, mergeText(label));
}

linkLabelPart
	= url { return text(); /* text node */ }
	/ link { return text(); /* text node */ }
	/ !"]" n:inline { return n; }
marihachi's avatar
marihachi committed

linkUrl
	= url { return text(); }

marihachi's avatar
marihachi committed
// inline: fn

fn
	= "[" name:$([a-z0-9_]i)+ args:fnArgs? _ content:(!"]" i:inline { return i; })+ "]"
{
	args = args || {};
	return createNode('fn', {
		name: name,
		args: args
	}, mergeText(content));
}

fnArgs
	= "." head:fnArg tails:("," arg:fnArg { return arg; })*
{
	const args = { };
	for (const pair of [head, ...tails]) {
		args[pair.k] = pair.v;
	}
	return args;
}

fnArg
	= k:$([a-z0-9_]i)+ "=" v:$([a-z0-9_]i)+
{
	return { k, v };
}
	/ k:$([a-z0-9_]i)+
{
	return { k: k, v: true };
}

marihachi's avatar
marihachi committed
// inline: text
marihachi's avatar
marihachi committed
text
	= . /* text node */
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
	= LF / &{ return location().start.column == 1; }
marihachi's avatar
marihachi committed
END "end of line"
marihachi's avatar
marihachi committed
	= LF / EOF
marihachi's avatar
marihachi committed

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\u00a0]