| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							|  |  |  |     const expressionTokens = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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]); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function finishWord() { | 
					
						
							|  |  |  |         if (currentWord === '') { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  |         expressionTokens.push(currentWord); | 
					
						
							| 
									
										
										
										
											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()) { | 
					
						
							|  |  |  |                     finishWord(); | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 finishWord(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             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 === ' ') { | 
					
						
							|  |  |  |                 finishWord(); | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  |             else if (['(', ')', '.'].includes(chr)) { | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |                 finishWord(); | 
					
						
							|  |  |  |                 currentWord += chr; | 
					
						
							|  |  |  |                 finishWord(); | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  |             else if (previousOperatorSymbol() !== isOperatorSymbol(chr)) { | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  |                 finishWord(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 currentWord += chr; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         currentWord += chr; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     finishWord(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-03 17:11:03 +02:00
										 |  |  |     return expressionTokens; | 
					
						
							| 
									
										
										
										
											2020-06-03 16:24:41 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  | function parser(tokens) { | 
					
						
							|  |  |  |     const attrs = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (let i = 0; i < tokens.length; i++) { | 
					
						
							|  |  |  |         const token = tokens[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (token.startsWith('#')) { | 
					
						
							|  |  |  |             const attr = { | 
					
						
							|  |  |  |                 type: 'label', | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  |                 name: token.substr(1), | 
					
						
							|  |  |  |                 isInheritable: false // FIXME
 | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (tokens[i + 1] === "=") { | 
					
						
							|  |  |  |                 if (i + 2 >= tokens.length) { | 
					
						
							|  |  |  |                     throw new Error(`Missing value for label "${token}"`); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 i += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 attr.value = tokens[i]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             attrs.push(attr); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else if (token.startsWith('~')) { | 
					
						
							|  |  |  |             if (i + 2 >= tokens.length || tokens[i + 1] !== '=') { | 
					
						
							|  |  |  |                 throw new Error(`Relation "${token}" should point to a note.`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             i += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  |             let notePath = tokens[i]; | 
					
						
							|  |  |  |             if (notePath.startsWith("#")) { | 
					
						
							|  |  |  |                 notePath = notePath.substr(1); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const noteId = notePath.split('/').pop(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const attr = { | 
					
						
							|  |  |  |                 type: 'relation', | 
					
						
							|  |  |  |                 name: token.substr(1), | 
					
						
							|  |  |  |                 isInheritable: false, // FIXME
 | 
					
						
							|  |  |  |                 value: noteId | 
					
						
							|  |  |  |             }; | 
					
						
							| 
									
										
										
										
											2020-06-04 00:04:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             attrs.push(attr); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             throw new Error(`Unrecognized attribute "${token}"`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return attrs; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-05 17:25:14 +02:00
										 |  |  | function lexAndParse(str) { | 
					
						
							|  |  |  |     const tokens = lexer(str); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return parser(tokens); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | } |