Skip to content
This repository has been archived by the owner on Oct 25, 2022. It is now read-only.

PR: merge dev branch #9

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions lib/parser.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// Copyright (c) 2018-2019 urain39 <urain39[AT]qq[DOT]com>

import { Token, TokenMember, TokenType, tokenize } from './tokenizer';
import {
Token,
TokenType,
TokenMember,
tokenize
} from './tokenizer';
import { Renderer } from './renderer';

function buildTree(tokens: Token[]): Token[] {
let type_: TokenType,
value: string,
section: Token | undefined,
let section: Token | undefined,
sections: Token[] = [],
treeRoot: Token[] = [],
collector: Token[] = treeRoot;

for (let token of tokens) {
type_ = token[TokenMember.TYPE];
value = token[TokenMember.VALUE];

switch (type_) {
for (const token of tokens) {
switch (token[TokenMember.TYPE]) {
// Enter a section
case TokenType.IF:
case TokenType.NOT:
Expand All @@ -27,22 +27,24 @@ function buildTree(tokens: Token[]): Token[] {
// Initialize section block
collector = section[TokenMember.BLOCK] = [];
break;
// Switch section block
case TokenType.ELSE:
section = sections[sections.length - 1];

// Check current(top) section is valid?
if (!section || section[TokenMember.TYPE] !== TokenType.IF || value !== section[TokenMember.VALUE])
throw new SyntaxError(`Unexpected token '<type=${type_}, value=${value}>'`);
if (!section || section[TokenMember.TYPE] !== TokenType.IF || token[TokenMember.VALUE] !== section[TokenMember.VALUE])
throw new SyntaxError(`Unexpected token '<type=${token[TokenMember.TYPE]}, value=${token[TokenMember.VALUE]}>'`);

// Switch the block to else block
collector = section[TokenMember.ELSE_BLOCK] = [];
break;
// Leave a section
case TokenType.END:
section = sections.pop();

// Check if section is not match
if (!section || value !== section[TokenMember.VALUE])
throw new SyntaxError(`Unexpected token '<type=${type_}, value=${value}>'`);
if (!section || token[TokenMember.VALUE] !== section[TokenMember.VALUE])
throw new SyntaxError(`Unexpected token '<type=${token[TokenMember.TYPE]}, value=${token[TokenMember.VALUE]}>'`);

// Change type of which section contains else block
if ((section as Token)[TokenMember.ELSE_BLOCK] instanceof Array && (section[TokenMember.ELSE_BLOCK] as Token[]).length > 0)
Expand All @@ -55,17 +57,16 @@ function buildTree(tokens: Token[]): Token[] {
else
collector = treeRoot;
break;
// Text or Formatter
default:
collector.push(token);
}
}

if (sections.length > 0) {
section = (sections.pop() as Token);
type_ = section[TokenMember.TYPE];
value = section[TokenMember.VALUE];
section = sections.pop() as Token;

throw new SyntaxError(`No match section '<type=${type_}, value=${value}>'`);
throw new SyntaxError(`No match section '<type=${section[TokenMember.TYPE]}, value=${section[TokenMember.VALUE]}>'`);
}

return treeRoot;
Expand Down
111 changes: 93 additions & 18 deletions lib/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,26 @@ export class Context {

// No cached record found
if (!found) {
let key: string,
keys: string[] = name.split('.');
let name_: string,
properties: string[] = name.split('.');

key = keys[0];
keys = keys.slice(1);
name_ = properties[0];
properties = properties.slice(1);

// Try to look up the name in data
for (let context: Context | undefined = this; context; context = context.parent) {
// Find out which context contains key
if (context.data.hasOwnProperty(key)) {
value = context.data[key];
// Find out which context contains name
if (context.data instanceof Object && context.data.hasOwnProperty(name_)) {
value = (context.data as Map)[name_];
break;
}
}

// Resolve properties
urain39 marked this conversation as resolved.
Show resolved Hide resolved
for (key of keys) {
if (value instanceof Object && value.hasOwnProperty(key)) {
value = value[key];
// XXX: Should we check value valid at first?
for (const property of properties) {
if (value instanceof Object && value.hasOwnProperty(property)) {
value = value[property];
} else {
value = null // Reset value
break;
Expand All @@ -66,10 +67,6 @@ export class Context {

return value;
}

public push(data: Map): Context {
return new Context(data, this);
}
}

export class Renderer {
Expand All @@ -79,10 +76,88 @@ export class Renderer {
this.treeRoot = treeRoot;
}

public render(data: Map): any {
let value: any = null,
context = new Context(data);
public renderTree(treeRoot: Token[], context: Context): string[] {
let value: any,
buffer: string[] = [];

for (const token of treeRoot) {
switch (token[TokenMember.TYPE]) {
case TokenType.IF:
value = context.resolve(token[TokenMember.VALUE]);

if (!value)
continue;

if (value instanceof Array)
for (const value_ of value)
buffer.push(this.renderTree(
token[TokenMember.BLOCK] as Token[],
new Context(value_, context)
).join(''));
else
buffer.push(this.renderTree(
token[TokenMember.BLOCK] as Token[],
context
).join(''));
break;
case TokenType.NOT:
value = context.resolve(token[TokenMember.VALUE]);

if (value)
continue;

buffer.push(this.renderTree(
token[TokenMember.BLOCK] as Token[],
context
).join(''));
break;
case TokenType.ELSE:
urain39 marked this conversation as resolved.
Show resolved Hide resolved
value = context.resolve(token[TokenMember.VALUE]);

if (value) {
if (value instanceof Array)
urain39 marked this conversation as resolved.
Show resolved Hide resolved
for (const value_ of value)
buffer.push(this.renderTree(
token[TokenMember.BLOCK] as Token[],
new Context(value_, context)
).join(''));
else
buffer.push(this.renderTree(
token[TokenMember.BLOCK] as Token[],
context
).join(''));
} else {
buffer.push(this.renderTree(
token[TokenMember.ELSE_BLOCK] as Token[],
context
).join(''));
}
break;
case TokenType.TEXT:
buffer.push(
token[TokenMember.VALUE]
);
break;
case TokenType.FORMAT:
buffer.push(context.resolve(
token[TokenMember.VALUE]
));
break;
// TODO: escapeHTML
case TokenType.FORMAT_ESCAPE:
buffer.push(context.resolve(
token[TokenMember.VALUE]
));
break;
}
}

return buffer;
}

value = context.resolve('');
public render(data: Map): string {
return this.renderTree(
urain39 marked this conversation as resolved.
Show resolved Hide resolved
this.treeRoot, new Context(data)
).join('');
}
}
10 changes: 5 additions & 5 deletions lib/tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export const enum TokenType {
FORMAT_ESCAPE
}

// See https://github.com/microsoft/TypeScript/pull/33050
// https://stackoverflow.com/questions/47842266/recursive-types-in-typescript
type TokenTuple<T> = [TokenType, string, T[] | undefined, T[] | undefined];
export interface Token extends TokenTuple<Token> {}

let TokenTypeMap = {
'?': TokenType.IF,
'!': TokenType.NOT,
Expand All @@ -25,11 +30,6 @@ let TokenTypeMap = {
'#': TokenType.FORMAT
};

// See https://github.com/microsoft/TypeScript/pull/33050
// https://stackoverflow.com/questions/47842266/recursive-types-in-typescript
type TokenTuple<T> = [TokenType, string, T[] | undefined, T[] | undefined];
export interface Token extends TokenTuple<Token> {}

export function tokenize(source: string, prefix: string, suffix: string): Token[] {
let type_: string,
value: string,
Expand Down
Empty file added tests/.gitkeep
Empty file.