More builder tests and improved compiler API docs

This commit is contained in:
Patrick Steele-Idem 2015-12-15 11:19:01 -07:00
parent 149c64395e
commit 1533325034
28 changed files with 411 additions and 60 deletions

View File

@ -25,6 +25,7 @@ var SelfInvokingFunction = require('./ast/SelfInvokingFunction');
var ForStatement = require('./ast/ForStatement');
var BinaryExpression = require('./ast/BinaryExpression');
var UpdateExpression = require('./ast/UpdateExpression');
var Expression = require('./ast/Expression');
class Builder {
assignment(left, right) {
@ -45,6 +46,10 @@ class Builder {
});
}
expression(value) {
return new Expression({value});
}
forEach(varName, target, body) {
if (typeof varName === 'object') {
var options = varName;
@ -88,7 +93,7 @@ class Builder {
}
htmlElement(tagName, attributes, body, argument) {
if (typeof tagName === 'object') {
if (typeof tagName === 'object' && !(tagName instanceof Node)) {
let elInfo = tagName;
tagName = elInfo.tagName;
attributes = elInfo.attributes;

View File

@ -169,6 +169,8 @@ class Generator {
}
getCode() {
this.flushBufferedWrites();
while(this._slots.length) {
let slots = this._slots;
this._slots = [];
@ -358,15 +360,26 @@ class Generator {
return this;
}
incIndent(code) {
this._currentIndent += this._indentStr;
incIndent(count) {
if (count != null) {
for (let i=0; i<count; i++) {
this._currentIndent += ' ';
}
} else {
this._currentIndent += this._indentStr;
}
return this;
}
decIndent(code) {
decIndent(count) {
if (count == null) {
count = this._indentSize;
}
this._currentIndent = this._currentIndent.substring(
0,
this._currentIndent.length - this._indentSize);
this._currentIndent.length - count);
return this;
}
@ -406,6 +419,9 @@ class Generator {
for (let i=0; i<value.length; i++) {
let v = value[i];
this.writeLineIndent();
if (v instanceof Node) {
this.generateCode(v);
} else {

View File

@ -0,0 +1,16 @@
'use strict';
var Node = require('./Node');
class Expression extends Node {
constructor(def) {
super('Html');
this.value = def.value;
}
generateCode(generator) {
generator.generateCode(this.value);
}
}
module.exports = Expression;

View File

@ -15,6 +15,7 @@ class Html extends Node {
generateHtmlCode(generator) {
let argument = this.argument;
console.log(module.id, 'Html', argument);
generator.addWrite(argument);
}
}

View File

@ -10,9 +10,11 @@ class HtmlComment extends Node {
generateHtmlCode(generator) {
var comment = this.comment;
generator.addWrite('<--');
var literal = generator.builder.literal;
generator.addWrite(literal('<--'));
generator.addWrite(comment);
generator.addWrite('-->');
generator.addWrite(literal('-->'));
}
}

View File

@ -1,13 +1,29 @@
'use strict';
var Node = require('./Node');
var Literal = require('./Literal');
var escapeXmlAttr = require('raptor-util/escapeXml').attr;
var HtmlAttributeCollection = require('./HtmlAttributeCollection');
class HtmlElement extends Node {
constructor(def) {
super('HtmlElement');
this.tagName = def.tagName;
var tagName = def.tagName;
this.tagName = null;
this.dynamicTagName = null;
if (tagName instanceof Node) {
if (tagName instanceof Literal) {
this.tagName = tagName.value;
} else {
this.dynamicTagName = tagName;
}
} else if (typeof tagName === 'string'){
this.tagName = tagName;
}
this._attributes = def.attributes;
if (!(this._attributes instanceof HtmlAttributeCollection)) {
@ -22,6 +38,14 @@ class HtmlElement extends Node {
generateHtmlCode(generator) {
var tagName = this.tagName;
// Convert the tag name into a Node so that we generate the code correctly
if (tagName) {
tagName = generator.builder.literal(tagName);
} else {
tagName = this.dynamicTagName;
}
var body = this.body;
var startTagOnly = this.startTagOnly;
var allowSelfClosing = this.allowSelfClosing;
@ -29,7 +53,9 @@ class HtmlElement extends Node {
var builder = generator.builder;
// Starting tag
generator.addWriteLiteral('<' + tagName);
generator.addWriteLiteral('<');
generator.addWrite(tagName);
var attributes = this._attributes && this._attributes.all;
@ -72,33 +98,22 @@ class HtmlElement extends Node {
});
}
// Body
if (hasBody) {
generator.addWriteLiteral('>');
generator.generateStatements(body);
generator.addWriteLiteral('</');
generator.addWrite(tagName);
generator.addWriteLiteral('>');
} else {
if (startTagOnly) {
generator.addWriteLiteral('>');
} else if (allowSelfClosing) {
generator.addWriteLiteral('/>');
}
}
// Body
if (hasBody) {
generator.generateStatements(body);
}
// Ending tag
if (tagName instanceof Node) {
generator.addWriteLiteral('</');
generator.addWrite(tagName);
generator.addWriteLiteral('>');
} else {
if (hasBody) {
generator.addWriteLiteral('</' + tagName + '>');
} else {
if (!startTagOnly && !allowSelfClosing) {
generator.addWriteLiteral('></' + tagName + '>');
}
generator.addWriteLiteral('></');
generator.addWrite(tagName);
generator.addWriteLiteral('>');
}
}
}
@ -152,6 +167,16 @@ class HtmlElement extends Node {
}
}
setTagName(newTagName) {
this.tagName = newTagName;
this.dynamicTagName = null;
}
getDynamicTagName(dynamicTagName) {
this.tagName = null;
this.dynamicTagName = dynamicTagName;
}
toString() {
var tagName = this.tagName;
return '<' + tagName + '>';

View File

@ -14,6 +14,14 @@ class Vars extends Node {
var kind = this.kind;
var isStatement = this.statement;
if (declarations && !Array.isArray(declarations) && typeof declarations === 'object') {
// Convert the object into an array of variables
declarations = Object.keys(declarations).map((id) => {
let init = declarations[id];
return { id, init };
});
}
if (!declarations || !declarations.length) {
return;
}
@ -21,11 +29,12 @@ class Vars extends Node {
for (let i=0; i<declarations.length; i++) {
var declaration = declarations[i];
if (i === 0) {
generator.write(kind + ' ');
} else {
generator.incIndent(4);
generator.writeLineIndent();
generator.write(' ');
}
var varName = declaration.id || declaration.name;
@ -49,6 +58,10 @@ class Vars extends Node {
generator.generateCode(initValue);
}
if (i !== 0) {
generator.decIndent(4);
}
if (i < declarations.length - 1) {
generator.write(',\n');
} else {

View File

@ -20,7 +20,7 @@ wrapperNode.appendChild(this);
#### makeContainer(array)
Converts the provided `array` into a `ArrayContainer`. If the provided `array` is already an instance of a `Container` then it is simply returned.
Converts the provided `Array` into an `ArrayContainer`. If the provided `Array` is already an instance of a `Container` then it is simply returned.
#### appendChild(node)
@ -248,16 +248,129 @@ for (var i = 0; i < 0; i++) {
### functionCall(callee, args)
Returns a node that generates the following code:
```javascript
<callee>(<arg1, arg2, ..., argN>)
```
```javascript
builder.functionCall(
'console.log',
[
builder.literal('Hello'),
builder.identifier('name')
]);
// Output:
console.log('Hello', name);
```
### functionDeclaration(name, params, body)
Returns a node that generates the following code:
```javascript
function [name](<param1, param2, ..., paramN>) {
<body>
}
```
_Named function declaration:_
```javascript
builder.functionDeclaration(
'foo',
[
'num1',
'num2'
],
[
builder.returnStatement(builder.binaryExpression('num1', '+', 'num2'))
]);
// Output:
function add(num1, num2) {
return num1 + num2;
}
```
_Anonymous function declaration:_
```javascript
builder.functionDeclaration(
null,
[
'num1',
'num2'
],
[
builder.returnStatement(builder.binaryExpression('num1', '+', 'num2'))
]);
// Output:
function(num1, num2) {
return num1 + num2;
}
```
### html(argument)
Returns a node that renders a fragment of HTML (special HTML characters will not be escaped):
```javascript
builder.html(
builder.literal('<div>Hello World</div>')
);
// Output:
out.w("<div>Hello World</div>");
```
### htmlComment(comment)
```javascript
builder.htmlComment(
builder.literal('This is an HTML comment'))
// Output:
out.w("<--This is an HTML comment-->");
```
### htmlElement(tagName, attributes, body, argument)
```javascript
builder.htmlElement(
'div',
[
{
name: 'class',
value: builder.literal('greeting')
}
],
[
builder.text(builder.literal('Hello World'))
])
// Output:
out.w("<div class=\"greeting\">Hello World</div>");
```
### identifier(name)
Returns a node that generates the code for a JavaScript identifier code (e.g., a variable name, parameter name, property name, etc.)
For example:
```javascript
builder.assignment(
builder.identifier('foo'),
builder.literal('abc'))
// Output code:
foo = "abc"
```
### ifStatement(test, body, elseStatement)
Returns a node that generates the following code:
@ -287,6 +400,49 @@ if (true) {
### literal(value)
Returns code to generate a JavaScript code for literal strings, numbers, booleans, objects and arrays.
For example:
```javascript
builder.literal('abc');
// Output code:
"abc"
```
Or, for a more complex example:
```javascript
builder.vars({
'aString': builder.literal('abc'),
'aNumber': builder.literal(123),
'aBoolean': builder.literal(false),
'anObject': builder.literal({
foo: 'bar',
dynamic: builder.expression('data.name'),
}),
'anArray': builder.literal([
'foo',
builder.expression('data.name')
])
})
// Output code:
var aString = "abc",
aNumber = 123,
aBoolean = false,
anObject = {
"foo": "bar",
"dynamic": data.name
},
anArray = [
"foo",
data.name
]
```
### node(type)
### program(body)

View File

@ -1,14 +1,3 @@
function create(__helpers) {
var str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) {
for (var i = 0; i < 0; i++) {
console.log(i);
}
};
for (var i = 0; i < 0; i++) {
console.log(i);
}
(module.exports = require("marko").c(__filename)).c(create);

View File

@ -2,21 +2,19 @@
module.exports = function(builder) {
return builder.templateRoot([
builder.forStatement(
builder.vars([
{
id: 'i',
init: builder.literal(0)
}
]),
builder.binaryExpression('i', '<', builder.literal(0)),
builder.updateExpression('i', '++'),
[
builder.functionCall('console.log', [
builder.identifier('i')
])
]
)
]);
return builder.forStatement(
builder.vars([
{
id: 'i',
init: builder.literal(0)
}
]),
builder.binaryExpression('i', '<', builder.literal(0)),
builder.updateExpression('i', '++'),
[
builder.functionCall('console.log', [
builder.identifier('i')
])
]
);
};

View File

@ -0,0 +1 @@
console.log("Hello", name)

View File

@ -0,0 +1,10 @@
'use strict';
module.exports = function(builder) {
return builder.functionCall(
'console.log',
[
builder.literal('Hello'),
builder.identifier('name')
]);
};

View File

@ -0,0 +1,3 @@
function(num1, num2) {
return num1 + num2;
}

View File

@ -0,0 +1,13 @@
'use strict';
module.exports = function(builder) {
return builder.functionDeclaration(
null,
[
'num1',
'num2'
],
[
builder.returnStatement(builder.binaryExpression('num1', '+', 'num2'))
]);
};

View File

@ -0,0 +1,3 @@
function add(num1, num2) {
return num1 + num2;
}

View File

@ -0,0 +1,13 @@
'use strict';
module.exports = function(builder) {
return builder.functionDeclaration(
'add',
[
'num1',
'num2'
],
[
builder.returnStatement(builder.binaryExpression('num1', '+', 'num2'))
]);
};

View File

@ -0,0 +1 @@
out.w("<div>Hello World</div>");

View File

@ -0,0 +1,7 @@
'use strict';
module.exports = function(builder) {
return builder.html(
builder.literal('<div>Hello World</div>')
);
};

View File

@ -0,0 +1 @@
out.w("<--This is an HTML comment-->");

View File

@ -0,0 +1,6 @@
'use strict';
module.exports = function(builder) {
return builder.htmlComment(
builder.literal('This is an HTML comment'));
};

View File

@ -0,0 +1,5 @@
out.w("<" +
data.tagName +
" class=\"greeting\">Hello World</" +
data.tagName +
">");

View File

@ -0,0 +1,15 @@
'use strict';
module.exports = function(builder) {
return builder.htmlElement(
builder.expression('data.tagName'),
[
{
name: 'class',
value: builder.literal('greeting')
}
],
[
builder.text(builder.literal('Hello World'))
]);
};

View File

@ -0,0 +1 @@
out.w("<div class=\"greeting\">Hello World</div>");

View File

@ -0,0 +1,15 @@
'use strict';
module.exports = function(builder) {
return builder.htmlElement(
'div',
[
{
name: 'class',
value: builder.literal('greeting')
}
],
[
builder.text(builder.literal('Hello World'))
]);
};

View File

@ -0,0 +1 @@
foo = "abc"

View File

@ -0,0 +1,7 @@
'use strict';
module.exports = function(builder) {
return builder.assignment(
builder.identifier('foo'),
builder.literal('abc'));
};

View File

@ -0,0 +1,11 @@
var aString = "abc",
aNumber = 123,
aBoolean = false,
anObject = {
"foo": "bar",
"dynamic": data.name
},
anArray = [
"foo",
data.name
]

View File

@ -0,0 +1,17 @@
'use strict';
module.exports = function(builder) {
return builder.vars({
'aString': builder.literal('abc'),
'aNumber': builder.literal(123),
'aBoolean': builder.literal(false),
'anObject': builder.literal({
foo: 'bar',
dynamic: builder.expression('data.name'),
}),
'anArray': builder.literal([
'foo',
builder.expression('data.name')
])
});
};