Skip to content
This repository was archived by the owner on Jun 5, 2020. It is now read-only.

Commit 25979d2

Browse files
committed
Stableize what we have
1 parent fe9ec4e commit 25979d2

8 files changed

Lines changed: 186 additions & 74 deletions

File tree

MetaScript.js

Lines changed: 125 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,29 @@
2525
/**
2626
* Constructs a new MetaScript instance.
2727
* @exports MetaScript
28-
* @param {string=} source Source to compile
28+
* @param {string} source Source to compile
29+
* @param {string=} filename Source file name if known, defaults to `"main"`.
2930
* @constructor
3031
*/
31-
var MetaScript = function(source) {
32+
var MetaScript = function(source, filename) {
3233

3334
/**
34-
* Meta program source.
35-
* @type {?string}
35+
* Original source.
36+
* @type {string}
3637
*/
37-
this.program = typeof source !== 'undefined' ? MetaScript.compile(source) : null;
38+
this.source = source;
39+
40+
/**
41+
* Original source file name.
42+
* @type {string}
43+
*/
44+
this.filename = filename || "main";
45+
46+
/**
47+
* The compiled meta program's source.
48+
* @type {string}
49+
*/
50+
this.program = MetaScript.compile(source);
3851
};
3952

4053
/**
@@ -56,11 +69,13 @@
5669
expr = /(\/\/\?|\/\*\?)(=?)/g, // Line/block expression
5770
exprLine = /\n|$/g, // Line terminator
5871
exprBlock = /\*\//g, // Block terminator
72+
exprEmpty = /(^|\n)([ \t]*)$/, // Empty line expression
5973
match, matchEnd, // Matches
6074
s, // Temporary string
6175
indent = '', // Indentation
6276
lastIndent = '', // Last indentation
63-
out = []; // Output stack
77+
out = [], // Output stack
78+
empty; // Line empty?
6479

6580
// Escapes a string to be used in a JavaScript string enclosed in single quotes
6681
function escapestr(s) {
@@ -99,13 +114,15 @@
99114

100115
// Get leading contents
101116
s = source.substring(index, match.index);
117+
118+
empty = exprEmpty.test(s);
102119

103120
// Look if it is a line or a block of meta
104-
if (match[1].indexOf('*') < 0) { // Line
105-
121+
if (match[1].indexOf('*') < 0) { // Line //? asd
122+
106123
// Trim whitespaces in front of the line and remember the indentation
107124
if (match[2] !== '=')
108-
s = s.replace(/(^|\n)([ \t]*)$/, function($0, $1, $2) { indent = $2; return $1; });
125+
s = s.replace(exprEmpty, function($0, $1, $2) { indent = $2; return $1; });
109126

110127
// Append leading contents
111128
append(s);
@@ -119,7 +136,7 @@
119136
out.push('__=\''+escapestr(lastIndent = indent)+'\';\n');
120137
}
121138
out.push(evaluate(source.substring(match.index+3, matchEnd.index).trim()));
122-
if (match[2] === '=')
139+
if (!empty || match[2] === '=')
123140
out.push('writeln();\n');
124141

125142
// Move on
@@ -169,23 +186,30 @@
169186
* Compiles the source to a meta program and transforms it using the specified scope. On node.js, this will wrap the
170187
* entire process in a new VM context.
171188
* @param {string} source Source
172-
* @param {Object} scope Scope
189+
* @param {string=} filename Source file name
190+
* @param {!Object} scope Scope
173191
* @param {string=} basedir Base directory for includes, defaults to `.` on node and `/` in the browser
174192
* @returns {string} Transformed source
175193
*/
176-
MetaScript.transform = function(source, scope, basedir) {
194+
MetaScript.transform = function(source, filename, scope, basedir) {
195+
if (typeof filename === 'object') {
196+
basedir = scope;
197+
scope = filename;
198+
filename = undefined;
199+
}
177200
if (MetaScript.IS_NODE) {
178201
var vm = require("vm"),
179202
sandbox;
180-
vm.runInNewContext('__result = new MetaScript(__source).transform(__scope, __basedir);', sandbox = {
203+
vm.runInNewContext('__result = new MetaScript(__source, __filename).transform(__scope, __basedir);', sandbox = {
181204
__source : source,
205+
__filename : filename,
182206
__scope : scope,
183207
__basedir : basedir,
184208
MetaScript : MetaScript
185209
});
186210
return sandbox.__result;
187211
} else {
188-
return new MetaScript(source).transform(scope, basedir); // Will probably pollute the global namespace
212+
return new MetaScript(source, filename).transform(scope, basedir); // Will probably pollute the global namespace
189213
}
190214
};
191215

@@ -215,6 +239,7 @@
215239
* @param {*} s Contents to write
216240
*/
217241
function write(s) {
242+
// Strip trailing white spaces on lines
218243
__out.push(s+"");
219244
}
220245

@@ -264,39 +289,28 @@
264289
/**
265290
* Includes another source file.
266291
* @function include
267-
* @param {string} __filename File to include
268-
* @param {boolean} __absolute Whether the path is absolute, defaults to `false` for a relative path
292+
* @param {string} filename File to include. May be a glob expression on node.js.
293+
* @param {boolean} absolute Whether the path is absolute, defaults to `false` for a relative path
269294
*/
270-
function include(__filename, __absolute) {
271-
__filename = __absolute ? __filename : (basedir === '/' ? basedir : basedir + '/') + __filename;
272-
var __source;
295+
function include(filename, absolute) {
296+
filename = absolute ? filename : (basedir === '/' ? basedir : basedir + '/') + filename;
297+
var ____ = __;
273298
if (MetaScript.IS_NODE) {
274-
var files = require("glob").sync(__filename);
275-
__source = "";
276-
files.forEach(function(file, i) {
277-
if (__source !== '') // Add line break between includes
278-
__source += __source.indexOf('\r\n') >= 0 ? '\r\n' : '\n';
279-
__source += require("fs").readFileSync(file)+"";
299+
var files = require("glob").sync(filename);
300+
files.sort(naturalCompare); // Sort these naturally (e.g. int8 < int16)
301+
files.forEach(function(file) {
302+
__eval(MetaScript.compile(indent(require("fs").readFileSync(file)+"", __)), file, filename);
303+
__ = ____;
280304
});
281305
} else { // Pull it synchronously, FIXME: Is this working?
282306
var request = XHR();
283307
request.open('GET', filename, false);
284308
request.send(null);
285309
if (typeof request.responseText === 'string') { // status is 0 on local filesystem
286-
__source = request.responseText;
310+
__eval(MetaScript.compile(indent(request.responseText, __)), request.responseText, filename);
311+
__ = ____;
287312
} else throw(new Error("Failed to fetch '"+filename+"': "+request.status));
288313
}
289-
var ____ = __;
290-
try {
291-
var __program = MetaScript.compile(indent(__source, __));
292-
eval(__program); // see: (*)
293-
} catch (err) {
294-
if (err.rethrow) throw(err);
295-
err = new Error(err.message+" in included meta program of '"+__filename+"':\n"+indent(__source, 4));
296-
err.rethrow = true;
297-
throw(err);
298-
}
299-
__ = ____;
300314
}
301315

302316
/**
@@ -315,24 +329,85 @@
315329

316330
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
317331

318-
try {
319-
(function() {
320-
// Using a wrapper function we enforce a unified behaviour of 'var's between the main and included
321-
// sources, making them always local. Of course it would be possible to make just the main source's vars
322-
// globally visible, but that'd be kinda hard to explain and maintain in a reliable way.
323-
eval(__program); // see: (*)
324-
})();
325-
return __out.join('');
326-
} catch (err) {
327-
if (err.rethrow) throw(err);
328-
err = new Error(err.message+" in main meta program:\n"+indent(vars.join('')+this.program, 4));
329-
err.rethrow = true;
330-
throw(err);
332+
/**
333+
* Evaluates a meta program.
334+
* @param {string} __program Meta program source
335+
* @param {string} __source Original source
336+
* @param {string} __filename Source file name
337+
* @inner
338+
* @private
339+
*/
340+
function __eval(__program, __source, __filename) {
341+
try {
342+
eval(__program);
343+
} catch (err) {
344+
if (err.rethrow) throw(err);
345+
err = new Error(err.message+" in meta program of '"+__filename+"':\n"+__err2code(__program, err));
346+
err.rethrow = true;
347+
throw(err);
348+
}
349+
}
350+
351+
/**
352+
* Generates a code view of eval'ed code from an Error.
353+
* @param {string} program Failed program
354+
* @param {!Error} err Error caught
355+
* @returns {string} Code view
356+
* @inner
357+
* @private
358+
*/
359+
function __err2code(program, err) {
360+
if (typeof err.stack !== 'string')
361+
return indent(program, 4);
362+
var match = /<anonymous>:(\d+):(\d+)\)/.exec(err.stack);
363+
if (!match) {
364+
return indent(program, 4);
365+
}
366+
var line = parseInt(match[1], 10)-1,
367+
start = line - 3,
368+
end = line + 4,
369+
lines = program.split("\n");
370+
if (start < 0) start = 0;
371+
if (end > lines.length) end = lines.length;
372+
var code = [];
373+
// start = 0; end = lines.length;
374+
while (start < end) {
375+
code.push(start === line ? "--> "+lines[start] : " "+lines[start]);
376+
start++;
377+
}
378+
return indent(code.join('\n'), 4);
331379
}
380+
381+
__eval(__program, this.source, this.filename);
382+
return __out.join('').replace(/[ \t]+(\r?\n)/g, function($0, $1) { return $1; });
332383
};
333384

334385
// (*) The use of eval() is - of course - potentially evil, but there is no way around it without making the library
335386
// harder to use. To limit the impact we always use a fresh VM context under node.js in MetaScript.transform.
387+
388+
/**
389+
* Compares two strings naturally, like in `"file9" < "file10"`.
390+
* @param {string} a
391+
* @param {string} b
392+
* @returns {number}
393+
* @version 0.4.4
394+
* @author Lauri Rooden - https://github.com/litejs/natural-compare-lite
395+
* @license MIT License - http://lauri.rooden.ee/mit-license.txt
396+
*/
397+
function naturalCompare(a, b) {
398+
if (a != b) for (var i, ca, cb = 1, ia = 0, ib = 0; cb;) {
399+
ca = a.charCodeAt(ia++) || 0;
400+
cb = b.charCodeAt(ib++) || 0;
401+
if (ca < 58 && ca > 47 && cb < 58 && cb > 47) {
402+
for (i = ia; ca = a.charCodeAt(ia), ca < 58 && ca > 47; ia++);
403+
ca = (a.slice(i - 1, ia) | 0) + 1;
404+
for (i = ib; cb = b.charCodeAt(ib), cb < 58 && cb > 47; ib++);
405+
cb = (b.slice(i - 1, ib) | 0) + 1;
406+
}
407+
if (ca != cb) return (ca < cb) ? -1 : 1;
408+
}
409+
return 0;
410+
}
336411

337412
/**
338413
* Constructs a XMLHttpRequest object.

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
**Metaprogramming** is the writing of computer programs that write or manipulate other programs (or themselves) as their
55
data, or that do part of the work at compile time that would otherwise be done at runtime.
66

7-
**MetaScript** is a tool for compile time meta programming using JavaScript as the meta language.
7+
**MetaScript** is a tool for build time meta programming using JavaScript as the meta language.
88

99
How does it work?
1010
-----------------
@@ -155,15 +155,17 @@ There are a few quite useful utility functions available to every meta program:
155155
* **indent(str:string, indent:string|number):string** indents a block of text using the specified indentation given
156156
either as a whitespace string or number of whitespaces to use.
157157
* **escapestr(str:string):string**
158-
Ecapes a string to be used inside of a single or double quote enclosed JavaScript string.
158+
Escapes a string to be used inside of a single or double quote enclosed JavaScript string.
159159

160-
Additionally, there is one internal variable named `__` (2x underscore) that remembers the current indentation level.
161-
This is used for example to indent included sources exactly like the meta block that contains the include call.
160+
Additionally, there are [a few internal variables](https://github.com/dcodeIO/MetaScript/wiki#other-__-prefixed-variables).
161+
Most notably there is the variable [__](https://github.com/dcodeIO/MetaScript/wiki#the-__-variable) (2x underscore) that
162+
remembers the current indentation level. This is used for example to indent included sources exactly like the meta block
163+
that contains the include call.
162164

163165
Documentation
164166
-------------
165-
* [Get additional insights from the wiki](https://github.com/dcodeIO/MetaScript/wiki)
166-
* [Tiny but fully commented source](https://github.com/dcodeIO/MetaScript/blob/master/MetaScript.js)
167+
* [Get additional insights at the wiki](https://github.com/dcodeIO/MetaScript/wiki)
167168
* [View the API documentation](http://htmlpreview.github.com/?http://github.com/dcodeIO/MetaScript/master/docs/MetaScript.html)
169+
* [View the tiny but fully commented source](https://github.com/dcodeIO/MetaScript/blob/master/MetaScript.js)
168170

169171
**License:** Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html

bin/metascript

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ if (app.argv.length >= 2) {
2626
console.error(("Processing '"+filename+"' in '"+basedir+"' with scope:\n").white.bold+JSON.stringify(scope, null, 2).grey.bold);
2727

2828
try {
29-
process.stdout.write(MetaScript.transform(fs.readFileSync(filename), scope, basedir)); // Runs in new vm context
29+
process.stdout.write(MetaScript.transform(fs.readFileSync(filename), filename, scope, basedir)); // Runs in new vm context
3030
app.ok();
3131
} catch (err) {
3232
app.fail(err.message);

docs/MetaScript.html

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ <h2>
3939

4040

4141
<dt>
42-
<h4 class="name" id="MetaScript"><span class="type-signature"></span>new MetaScript<span class="signature">(<span class="optional">source</span>)</span><span class="type-signature"></span></h4>
42+
<h4 class="name" id="MetaScript"><span class="type-signature"></span>new MetaScript<span class="signature">(source)</span><span class="type-signature"></span></h4>
4343

4444

4545
</dt>
@@ -69,8 +69,6 @@ <h5>Parameters:</h5>
6969
<th>Type</th>
7070

7171

72-
<th>Argument</th>
73-
7472

7573

7674

@@ -96,14 +94,6 @@ <h5>Parameters:</h5>
9694
</td>
9795

9896

99-
<td class="attributes">
100-
101-
&lt;optional><br>
102-
103-
104-
105-
</td>
106-
10797

10898

10999

@@ -223,7 +213,52 @@ <h4 class="name" id="program"><span class="type-signature"></span>program<span c
223213
<dd>
224214

225215
<div class="description">
226-
<p>Meta program source.</p>
216+
<p>The compiled meta program&#39;s source.</p>
217+
</div>
218+
219+
220+
221+
<dl class="details">
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+
232+
233+
234+
235+
236+
237+
238+
239+
240+
241+
242+
243+
244+
245+
</dl>
246+
247+
248+
249+
</dd>
250+
251+
252+
253+
<dt>
254+
<h4 class="name" id="source"><span class="type-signature"></span>source<span class="type-signature"> :string</span></h4>
255+
256+
257+
</dt>
258+
<dd>
259+
260+
<div class="description">
261+
<p>The original source.</p>
227262
</div>
228263

229264

0 commit comments

Comments
 (0)