Initial commit
This commit is contained in:
		
						commit
						44c21e670f
					
				
							
								
								
									
										24
									
								
								cli.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								cli.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { TemplateEngine } from './index.js';
 | 
				
			||||||
 | 
					import { basename } from 'node:path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Check arguments
 | 
				
			||||||
 | 
					if (process.argv.length !== 3) {
 | 
				
			||||||
 | 
					  console.error(`Usage: node ${basename(process.argv[1])} filename`);
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const fileName = process.argv[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tp = new TemplateEngine();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try {
 | 
				
			||||||
 | 
					  result = await tp.render(fileName);
 | 
				
			||||||
 | 
					} catch (e) {
 | 
				
			||||||
 | 
					  console.error(e.message);
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (result) {
 | 
				
			||||||
 | 
					  process.stdout.write(result);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								example_templates/everything.n0m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								example_templates/everything.n0m
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					$ #!/bin/sh
 | 
				
			||||||
 | 
					$ title='My Website';
 | 
				
			||||||
 | 
					$ dangerous='<b>Unescaped</b>';
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  # This is a comment
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <title><% $title %></title>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <h1>Welcome: <% $2 %></h1>
 | 
				
			||||||
 | 
					    <p><!% $dangerous %></p>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										5
									
								
								example_templates/inline_tags.n0m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								example_templates/inline_tags.n0m
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					$ VAR1="So <b>many</b> cats!";
 | 
				
			||||||
 | 
					<h1><% $VAR1 %></h1>
 | 
				
			||||||
 | 
					<h1><!% $VAR1 %></h1>
 | 
				
			||||||
 | 
					<h1><$% echo $VAR1 | cut -f 2 -d ' ' %></h1>
 | 
				
			||||||
 | 
					<h1><$!% echo $VAR1 | cut -f 2 -d ' ' %></h1>
 | 
				
			||||||
							
								
								
									
										12
									
								
								example_templates/restaurants.n0m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								example_templates/restaurants.n0m
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					$ #!/bin/sh
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head><title>Restaurants</title></head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <ul>
 | 
				
			||||||
 | 
					      $ while read restaurant; do
 | 
				
			||||||
 | 
					        <li><% $restaurant %></li>
 | 
				
			||||||
 | 
					      $ done <restaurants.txt
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										3
									
								
								example_templates/restaurants.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								example_templates/restaurants.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					Luigi's
 | 
				
			||||||
 | 
					Papa Johns
 | 
				
			||||||
 | 
					SushiQ
 | 
				
			||||||
							
								
								
									
										1
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export { TemplateEngine } from './src/template_engine.js';
 | 
				
			||||||
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "n0m-template-engine",
 | 
				
			||||||
 | 
					  "version": "1.0.0",
 | 
				
			||||||
 | 
					  "description": "",
 | 
				
			||||||
 | 
					  "main": "index.js",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "start": "node cli.js",
 | 
				
			||||||
 | 
					    "test": "echo \"Error: no test specified\" && exit 1"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "author": "",
 | 
				
			||||||
 | 
					  "license": "ISC",
 | 
				
			||||||
 | 
					  "type": "module"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										85
									
								
								src/character_stream.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/character_stream.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import { readFile } from 'node:fs/promises';
 | 
				
			||||||
 | 
					import { basename } from 'node:path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class CharacterStream {
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.reset();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  reset() {
 | 
				
			||||||
 | 
					    this._data = '';
 | 
				
			||||||
 | 
					    this.fileName = '';
 | 
				
			||||||
 | 
					    this._pos = 0;
 | 
				
			||||||
 | 
					    this.line = 0;
 | 
				
			||||||
 | 
					    this.column = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async loadFile(fileName) {
 | 
				
			||||||
 | 
					    this.reset();
 | 
				
			||||||
 | 
					    this.fileName = basename(fileName);
 | 
				
			||||||
 | 
					    this._data = await readFile(fileName, 'utf-8');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  peek(length = 1) {
 | 
				
			||||||
 | 
					    return this._data.substring(this._pos, this._pos + length);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  peekWhile(predicate) {
 | 
				
			||||||
 | 
					    let buffer = '';
 | 
				
			||||||
 | 
					    for (
 | 
				
			||||||
 | 
					      let offset = 0, c = this._data[this._pos + offset];
 | 
				
			||||||
 | 
					      c !== undefined && predicate(c);
 | 
				
			||||||
 | 
					      offset++, c = this._data[this._pos + offset]
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      buffer += c;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return buffer;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Peek first character after any characters that match predicate
 | 
				
			||||||
 | 
					  peekAfter(predicate) {
 | 
				
			||||||
 | 
					    const skip = this.peekWhile(predicate);
 | 
				
			||||||
 | 
					    return this.peek(skip.length + 1).substring(skip.length);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isNext(str) {
 | 
				
			||||||
 | 
					    return this.peek(str.length) === str;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  next(length = 1) {
 | 
				
			||||||
 | 
					    const chunk = this.peek(length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this._pos += chunk.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.line += this._countOccurrences(chunk, '\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const lastNewLine = chunk.lastIndexOf('\n');
 | 
				
			||||||
 | 
					    if (lastNewLine === -1) {
 | 
				
			||||||
 | 
					      this.column += chunk.length;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.column = chunk.length - lastNewLine - 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return chunk;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nextWhile(predicate) {
 | 
				
			||||||
 | 
					    return this.next(this.peekWhile(predicate).length);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _countOccurrences = (str, substr) => {
 | 
				
			||||||
 | 
					    let count = 0;
 | 
				
			||||||
 | 
					    let i = 0;
 | 
				
			||||||
 | 
					    while(true) {
 | 
				
			||||||
 | 
					      i = str.indexOf(substr, i);
 | 
				
			||||||
 | 
					      if (i === -1) break;
 | 
				
			||||||
 | 
					      i += substr.length;
 | 
				
			||||||
 | 
					      count++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return count;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  eof() {
 | 
				
			||||||
 | 
					    return this.peek() === '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/errors/template_syntax_error.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/errors/template_syntax_error.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					export class TemplateSyntaxError extends Error {
 | 
				
			||||||
 | 
					  constructor(cs, message) {
 | 
				
			||||||
 | 
					    super(
 | 
				
			||||||
 | 
					      `${cs.fileName}:${cs.line + 1}:${cs.column + 1}: ${message}`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/template_engine.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/template_engine.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import { TokenStream } from './token_stream.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class TemplateEngine {
 | 
				
			||||||
 | 
					  async render(fileName) {
 | 
				
			||||||
 | 
					    const ts = new TokenStream();
 | 
				
			||||||
 | 
					    await ts.loadFile(fileName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let buffer = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (!ts.eof()) {
 | 
				
			||||||
 | 
					      const token = ts.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      switch (token.type) {
 | 
				
			||||||
 | 
					        case 'raw':
 | 
				
			||||||
 | 
					          buffer += this._renderRaw(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'inline':
 | 
				
			||||||
 | 
					          buffer += this._renderInline(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'inline_unescaped':
 | 
				
			||||||
 | 
					          buffer += this._renderInlineUnescaped(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'inline_statement':
 | 
				
			||||||
 | 
					          buffer += this._renderInlineStatement(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'inline_statement_unescaped':
 | 
				
			||||||
 | 
					          buffer += this._renderInlineStatementUnescaped(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'statement':
 | 
				
			||||||
 | 
					          buffer += this._renderStatement(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'comment':
 | 
				
			||||||
 | 
					          buffer += this._renderComment(token.value);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          throw new Error(`Unrecognized token: ${token.type}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return buffer;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderComment(value) {
 | 
				
			||||||
 | 
					    return `# ${value}\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderStatement(value) {
 | 
				
			||||||
 | 
					    return `${value}\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderInline(value) {
 | 
				
			||||||
 | 
					    return `printf '%s' "${value}" | jq -Rrj @html;\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderInlineUnescaped(value) {
 | 
				
			||||||
 | 
					    return `printf '%s' "${value}";\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderInlineStatement(value) {
 | 
				
			||||||
 | 
					    return `printf '%s' "$(${value})" | jq -Rrj @html;\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderInlineStatementUnescaped(value) {
 | 
				
			||||||
 | 
					    return `printf '%s' "$(${value})";\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _renderRaw(value) {
 | 
				
			||||||
 | 
					    const lines = value.split('\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let buffer = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < lines.length; i++) {
 | 
				
			||||||
 | 
					      const line = lines[i];
 | 
				
			||||||
 | 
					      const isLastLine = i === lines.length - 1;
 | 
				
			||||||
 | 
					      const formatString = isLastLine ? '%s' : '%s\\n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      buffer += `printf '${formatString}' '${line.replace("'", "'\\''")}';\n`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return buffer;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										207
									
								
								src/token_stream.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/token_stream.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,207 @@
 | 
				
			|||||||
 | 
					import { CharacterStream } from './character_stream.js';
 | 
				
			||||||
 | 
					import { TemplateSyntaxError } from './errors/template_syntax_error.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class TokenStream {
 | 
				
			||||||
 | 
					  static OPEN_INLINE = '<%';
 | 
				
			||||||
 | 
					  static CLOSE_INLINE = '%>';
 | 
				
			||||||
 | 
					  static OPEN_INLINE_UNESCAPED = '<!%';
 | 
				
			||||||
 | 
					  static CLOSE_INLINE_UNESCAPED = '%>';
 | 
				
			||||||
 | 
					  static OPEN_INLINE_STATEMENT = '<$%';
 | 
				
			||||||
 | 
					  static CLOSE_INLINE_STATEMENT = '%>';
 | 
				
			||||||
 | 
					  static OPEN_INLINE_STATEMENT_UNESCAPED = '<$!%';
 | 
				
			||||||
 | 
					  static CLOSE_INLINE_STATEMENT_UNESCAPED = '%>';
 | 
				
			||||||
 | 
					  static STATEMENT = '$';
 | 
				
			||||||
 | 
					  static COMMENT = '#';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.reset();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  reset() {
 | 
				
			||||||
 | 
					    this._cs = new CharacterStream();
 | 
				
			||||||
 | 
					    this._peekedToken = null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loadFile(fileName) {
 | 
				
			||||||
 | 
					    this.reset();
 | 
				
			||||||
 | 
					    return this._cs.loadFile(fileName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  peek() {
 | 
				
			||||||
 | 
					    if (!this._peekedToken) this._peekedToken = this._readNext();
 | 
				
			||||||
 | 
					    return this._peekedToken;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  next() {
 | 
				
			||||||
 | 
					    const token = this.peek();
 | 
				
			||||||
 | 
					    this._peekedToken = null;
 | 
				
			||||||
 | 
					    return token;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  eof() {
 | 
				
			||||||
 | 
					    return this.peek() === null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readNext() {
 | 
				
			||||||
 | 
					    let raw = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const flushRaw = () => {
 | 
				
			||||||
 | 
					      if (raw.length) {
 | 
				
			||||||
 | 
					        const value = raw;
 | 
				
			||||||
 | 
					        raw = '';
 | 
				
			||||||
 | 
					        return {type: 'raw', value};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (!this._cs.eof()) {
 | 
				
			||||||
 | 
					      if (this._cs.isNext(TokenStream.OPEN_INLINE)) {
 | 
				
			||||||
 | 
					        return flushRaw() || this._readInline();
 | 
				
			||||||
 | 
					      } if (this._cs.isNext(TokenStream.OPEN_INLINE_UNESCAPED)) {
 | 
				
			||||||
 | 
					        return flushRaw() || this._readInlineUnescaped();
 | 
				
			||||||
 | 
					      } if (this._cs.isNext(TokenStream.OPEN_INLINE_STATEMENT)) {
 | 
				
			||||||
 | 
					        return flushRaw() || this._readInlineStatement();
 | 
				
			||||||
 | 
					      } if (this._cs.isNext(TokenStream.OPEN_INLINE_STATEMENT_UNESCAPED)) {
 | 
				
			||||||
 | 
					        return flushRaw() || this._readInlineStatementUnescaped();
 | 
				
			||||||
 | 
					      } else if (
 | 
				
			||||||
 | 
					        this._cs.column === 0 &&
 | 
				
			||||||
 | 
					        this._cs.peekAfter(this._isWhitespace) === TokenStream.STATEMENT
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        return flushRaw() || this._readStatement();
 | 
				
			||||||
 | 
					      } else if (
 | 
				
			||||||
 | 
					        this._cs.column === 0 &&
 | 
				
			||||||
 | 
					        this._cs.peekAfter(this._isWhitespace) === TokenStream.COMMENT
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        return flushRaw() || this._readComment();
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        raw += this._cs.next();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return flushRaw() || null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _missingInlineSpaceError(tag) {
 | 
				
			||||||
 | 
					    throw new TemplateSyntaxError(
 | 
				
			||||||
 | 
					      this._cs,
 | 
				
			||||||
 | 
					      `Inline tag: "${tag}" must be followed by a whitespace`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readInlineGeneric(tokenType, openTag, closeTag) {
 | 
				
			||||||
 | 
					    this._cs.next(openTag.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check for mandatory space
 | 
				
			||||||
 | 
					    if (this._cs.peek() !== ' ') {
 | 
				
			||||||
 | 
					      this._missingInlineSpaceError(openTag);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let value = '';
 | 
				
			||||||
 | 
					    while (!this._cs.eof() && !this._cs.isNext(closeTag)) {
 | 
				
			||||||
 | 
					      const char = this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this._cs.isNext(closeTag)) {
 | 
				
			||||||
 | 
					        if (char !== ' ') {
 | 
				
			||||||
 | 
					          this._missingInlineSpaceError(closeTag);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        value += char;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this._cs.next(closeTag.length);
 | 
				
			||||||
 | 
					    return {type: tokenType, value};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readInline() {
 | 
				
			||||||
 | 
					    return this._readInlineGeneric(
 | 
				
			||||||
 | 
					      'inline',
 | 
				
			||||||
 | 
					      TokenStream.OPEN_INLINE,
 | 
				
			||||||
 | 
					      TokenStream.CLOSE_INLINE
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readInlineUnescaped() {
 | 
				
			||||||
 | 
					    return this._readInlineGeneric(
 | 
				
			||||||
 | 
					      'inline_unescaped',
 | 
				
			||||||
 | 
					      TokenStream.OPEN_INLINE_UNESCAPED,
 | 
				
			||||||
 | 
					      TokenStream.CLOSE_INLINE_UNESCAPED
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readInlineStatement() {
 | 
				
			||||||
 | 
					    return this._readInlineGeneric(
 | 
				
			||||||
 | 
					      'inline_statement',
 | 
				
			||||||
 | 
					      TokenStream.OPEN_INLINE_STATEMENT,
 | 
				
			||||||
 | 
					      TokenStream.CLOSE_INLINE_STATEMENT
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readInlineStatementUnescaped() {
 | 
				
			||||||
 | 
					    return this._readInlineGeneric(
 | 
				
			||||||
 | 
					      'inline_statement_unescaped',
 | 
				
			||||||
 | 
					      TokenStream.OPEN_INLINE_STATEMENT_UNESCAPED,
 | 
				
			||||||
 | 
					      TokenStream.CLOSE_INLINE_STATEMENT_UNESCAPED
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readStatement() {
 | 
				
			||||||
 | 
					    // Skip whitespace
 | 
				
			||||||
 | 
					    this._cs.nextWhile(this._isWhitespace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Skip STATEMENT
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check for mandatory space
 | 
				
			||||||
 | 
					    if (this._cs.peek() !== ' ') {
 | 
				
			||||||
 | 
					      throw new TemplateSyntaxError(
 | 
				
			||||||
 | 
					        this._cs,
 | 
				
			||||||
 | 
					        `Statement character: "${TokenStream.STATEMENT}" must be ` +
 | 
				
			||||||
 | 
					        `followed by a whitespace`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Consume space
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remainder of line is statement
 | 
				
			||||||
 | 
					    const value = this._cs.nextWhile((c) => c !== '\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Swallow new line
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {type: 'statement', value};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _readComment() {
 | 
				
			||||||
 | 
					    // Skip whitespace
 | 
				
			||||||
 | 
					    this._cs.nextWhile(this._isWhitespace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Skip COMMENT
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check for mandatory space
 | 
				
			||||||
 | 
					    if (this._cs.peek() !== ' ') {
 | 
				
			||||||
 | 
					      throw new TemplateSyntaxError(
 | 
				
			||||||
 | 
					        this._cs,
 | 
				
			||||||
 | 
					        `Comment character: "${TokenStream.COMMENT}" must be followed by a ` +
 | 
				
			||||||
 | 
					        `whitespace`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Consume space
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remainder of line is comment
 | 
				
			||||||
 | 
					    const value = this._cs.nextWhile((c) => c !== '\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Swallow new line
 | 
				
			||||||
 | 
					    this._cs.next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {type: 'comment', value};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Utility methods
 | 
				
			||||||
 | 
					  _isWhitespace(c) {
 | 
				
			||||||
 | 
					    return /\s/.test(c);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user