|
|
|
@ -3,6 +3,8 @@ import { TemplateSyntaxError } from './errors/template_syntax_error.mjs';
|
|
|
|
|
|
|
|
|
|
export class TokenStream { |
|
|
|
|
static ESCAPE = '\\'; |
|
|
|
|
static OPEN_STATEMENT = '${'; |
|
|
|
|
static CLOSE_STATEMENT = '}$'; |
|
|
|
|
static OPEN_INLINE = '<%'; |
|
|
|
|
static CLOSE_INLINE = '%>'; |
|
|
|
|
static OPEN_INLINE_UNESCAPED = '<!%'; |
|
|
|
@ -64,10 +66,12 @@ export class TokenStream {
|
|
|
|
|
// interpreter that first builds the template and then executes)
|
|
|
|
|
this._skipShebang(); |
|
|
|
|
} else if (this._cs.isNext(TokenStream.ESCAPE)) { |
|
|
|
|
// Skip space
|
|
|
|
|
this._cs.next(); |
|
|
|
|
// Skip escape
|
|
|
|
|
this._cs.next(TokenStream.ESCAPE.length); |
|
|
|
|
// Treat everything as raw until next space
|
|
|
|
|
raw += this._cs.nextWhile((c) => /\S/.test(c)); |
|
|
|
|
} else if (this._cs.isNext(TokenStream.OPEN_STATEMENT)) { |
|
|
|
|
return flushRaw() || this._readBlockStatement(); |
|
|
|
|
} else if (this._cs.isNext(TokenStream.OPEN_INLINE)) { |
|
|
|
|
return flushRaw() || this._readInline(); |
|
|
|
|
} else if (this._cs.isNext(TokenStream.OPEN_INLINE_UNESCAPED)) { |
|
|
|
@ -102,40 +106,64 @@ export class TokenStream {
|
|
|
|
|
this._cs.next(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_missingInlineSpaceError(tag) { |
|
|
|
|
_missingOpenSpaceError(tag) { |
|
|
|
|
throw new TemplateSyntaxError( |
|
|
|
|
this._cs, |
|
|
|
|
`Inline tag: "${tag}" must be followed by a whitespace` |
|
|
|
|
`Tag: "${tag}" must be followed by a whitespace or new line` |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readInlineGeneric(tokenType, openTag, closeTag) { |
|
|
|
|
_missingCloseSpaceError(tag) { |
|
|
|
|
throw new TemplateSyntaxError( |
|
|
|
|
this._cs, |
|
|
|
|
`Tag: "${tag}" must be preceded by a whitespace or new line` |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readBlockGeneric(tokenType, openTag, closeTag) { |
|
|
|
|
this._cs.next(openTag.length); |
|
|
|
|
|
|
|
|
|
// Check for mandatory space
|
|
|
|
|
if (this._cs.peek() !== ' ') { |
|
|
|
|
this._missingInlineSpaceError(openTag); |
|
|
|
|
if (/\S/.test(this._cs.peek())) { |
|
|
|
|
this._missingOpenSpaceError(openTag); |
|
|
|
|
} |
|
|
|
|
this._cs.next(); |
|
|
|
|
|
|
|
|
|
let value = ''; |
|
|
|
|
while (!this._cs.eof() && !this._cs.isNext(closeTag)) { |
|
|
|
|
const char = this._cs.next(); |
|
|
|
|
// Handle any escaped closing tag
|
|
|
|
|
if (this._cs.isNext(TokenStream.ESCAPE)) { |
|
|
|
|
// Skip escape
|
|
|
|
|
this._cs.next(TokenStream.ESCAPE.length); |
|
|
|
|
|
|
|
|
|
if (this._cs.isNext(closeTag)) { |
|
|
|
|
if (char !== ' ') { |
|
|
|
|
this._missingInlineSpaceError(closeTag); |
|
|
|
|
} |
|
|
|
|
// Treat everything as raw until next space or eof
|
|
|
|
|
value += this._cs.nextWhile((c) => /\S/.test(c)); |
|
|
|
|
} else { |
|
|
|
|
value += char; |
|
|
|
|
const char = this._cs.next(); |
|
|
|
|
|
|
|
|
|
if (this._cs.isNext(closeTag)) { |
|
|
|
|
if (/\S/.test(char)) { |
|
|
|
|
this._missingCloseSpaceError(closeTag); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
value += char; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
this._cs.next(closeTag.length); |
|
|
|
|
return {type: tokenType, value}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readBlockStatement() { |
|
|
|
|
return this._readBlockGeneric( |
|
|
|
|
'block_statement', |
|
|
|
|
TokenStream.OPEN_STATEMENT, |
|
|
|
|
TokenStream.CLOSE_STATEMENT |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readInline() { |
|
|
|
|
return this._readInlineGeneric( |
|
|
|
|
return this._readBlockGeneric( |
|
|
|
|
'inline', |
|
|
|
|
TokenStream.OPEN_INLINE, |
|
|
|
|
TokenStream.CLOSE_INLINE |
|
|
|
@ -143,7 +171,7 @@ export class TokenStream {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readInlineUnescaped() { |
|
|
|
|
return this._readInlineGeneric( |
|
|
|
|
return this._readBlockGeneric( |
|
|
|
|
'inline_unescaped', |
|
|
|
|
TokenStream.OPEN_INLINE_UNESCAPED, |
|
|
|
|
TokenStream.CLOSE_INLINE_UNESCAPED |
|
|
|
@ -151,7 +179,7 @@ export class TokenStream {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readInlineStatement() { |
|
|
|
|
return this._readInlineGeneric( |
|
|
|
|
return this._readBlockGeneric( |
|
|
|
|
'inline_statement', |
|
|
|
|
TokenStream.OPEN_INLINE_STATEMENT, |
|
|
|
|
TokenStream.CLOSE_INLINE_STATEMENT |
|
|
|
@ -159,7 +187,7 @@ export class TokenStream {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_readInlineStatementUnescaped() { |
|
|
|
|
return this._readInlineGeneric( |
|
|
|
|
return this._readBlockGeneric( |
|
|
|
|
'inline_statement_unescaped', |
|
|
|
|
TokenStream.OPEN_INLINE_STATEMENT_UNESCAPED, |
|
|
|
|
TokenStream.CLOSE_INLINE_STATEMENT_UNESCAPED |
|
|
|
|