| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  | function preprocess(str) { | 
					
						
							|  |  |  |     if (str.startsWith('<p>')) { | 
					
						
							|  |  |  |         str = str.substr(3); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (str.endsWith('</p>')) { | 
					
						
							|  |  |  |         str = str.substr(0, str.length - 4); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     str = str.replace(" ", " "); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return str.replace(/<a[^>]+href="(#[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1"); | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | function lexer(str) { | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  |     str = preprocess(str); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |     const tokens = []; | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     let quotes = false; | 
					
						
							|  |  |  |     let currentWord = ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function isOperatorSymbol(chr) { | 
					
						
							|  |  |  |         return ['=', '*', '>', '<', '!'].includes(chr); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function previousOperatorSymbol() { | 
					
						
							|  |  |  |         if (currentWord.length === 0) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             return isOperatorSymbol(currentWord[currentWord.length - 1]); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @param endIndex - index of the last character of the token | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     function finishWord(endIndex) { | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |         if (currentWord === '') { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |         tokens.push({ | 
					
						
							|  |  |  |             text: currentWord, | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |             startIndex: endIndex - currentWord.length + 1, | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |             endIndex: endIndex | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         currentWord = ''; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (let i = 0; i < str.length; i++) { | 
					
						
							|  |  |  |         const chr = str[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (chr === '\\') { | 
					
						
							|  |  |  |             if ((i + 1) < str.length) { | 
					
						
							|  |  |  |                 i++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 currentWord += str[i]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else { | 
					
						
							|  |  |  |                 currentWord += chr; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else if (['"', "'", '`'].includes(chr)) { | 
					
						
							|  |  |  |             if (!quotes) { | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  |                 if (previousOperatorSymbol()) { | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                     finishWord(i - 1); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 quotes = chr; | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             else if (quotes === chr) { | 
					
						
							|  |  |  |                 quotes = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                 finishWord(i - 1); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             else { | 
					
						
							|  |  |  |                 // it's a quote but within other kind of quotes so it's valid as a literal character
 | 
					
						
							|  |  |  |                 currentWord += chr; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else if (!quotes) { | 
					
						
							|  |  |  |             if (currentWord.length === 0 && (chr === '#' || chr === '~')) { | 
					
						
							|  |  |  |                 currentWord = chr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if (chr === ' ') { | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                 finishWord(i - 1); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  |             else if (previousOperatorSymbol() !== isOperatorSymbol(chr)) { | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                 finishWord(i - 1); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 currentWord += chr; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         currentWord += chr; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |     finishWord(str.length - 1); | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |     return tokens; | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  | function parser(tokens, allowEmptyRelations = false) { | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |     const attrs = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (let i = 0; i < tokens.length; i++) { | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |         const {text, startIndex, endIndex} = tokens[i]; | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |         if (text.startsWith('#')) { | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |             const attr = { | 
					
						
							|  |  |  |                 type: 'label', | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                 name: text.substr(1), | 
					
						
							|  |  |  |                 isInheritable: false, // FIXME
 | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |                 nameStartIndex: startIndex, | 
					
						
							|  |  |  |                 nameEndIndex: endIndex | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |             if (i + 1 < tokens.length && tokens[i + 1].text === "=") { | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |                 if (i + 2 >= tokens.length) { | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                     throw new Error(`Missing value for label "${text}"`); | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 i += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |                 attr.value = tokens[i].text; | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |                 attr.valueStartIndex = tokens[i].startIndex; | 
					
						
							|  |  |  |                 attr.valueEndIndex = tokens[i].endIndex; | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             attrs.push(attr); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |         else if (text.startsWith('~')) { | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |             const attr = { | 
					
						
							|  |  |  |                 type: 'relation', | 
					
						
							|  |  |  |                 name: text.substr(1), | 
					
						
							|  |  |  |                 isInheritable: false, // FIXME
 | 
					
						
							|  |  |  |                 nameStartIndex: startIndex, | 
					
						
							|  |  |  |                 nameEndIndex: endIndex | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             attrs.push(attr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |             if (i + 2 >= tokens.length || tokens[i + 1].text !== '=') { | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |                 if (allowEmptyRelations) { | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else { | 
					
						
							|  |  |  |                     throw new Error(`Relation "${text}" should point to a note.`); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             i += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |             let notePath = tokens[i].text; | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  |             if (notePath.startsWith("#")) { | 
					
						
							|  |  |  |                 notePath = notePath.substr(1); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const noteId = notePath.split('/').pop(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |             attr.value = noteId; | 
					
						
							|  |  |  |             attr.valueStartIndex = tokens[i].startIndex; | 
					
						
							|  |  |  |             attr.valueEndIndex = tokens[i].endIndex; | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							| 
									
										
										
										
											2020-06-06 10:39:27 +02:00
										 |  |  |             throw new Error(`Unrecognized attribute "${text}"`); | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return attrs; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  | function lexAndParse(str, allowEmptyRelations = false) { | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  |     const tokens = lexer(str); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 12:56:24 +02:00
										 |  |  |     return parser(tokens, allowEmptyRelations); | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | export default { | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |     lexer, | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  |     parser, | 
					
						
							|  |  |  |     lexAndParse | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | } |