-
-
Save ympbyc/3658681 to your computer and use it in GitHub Desktop.
/* | |
* BlockParser | |
* convert smalltalk style block to javascript's closure | |
*/ | |
(function () { | |
'use strict'; | |
var Packrat, BlockParser, | |
__each = function (obj, fn) { | |
for (var key in obj) | |
if (obj.hasOwnProperty(key)) | |
fn(obj[key], key); | |
return; | |
}, | |
__template = function (template, hashmap) { | |
var dest_str = template; | |
__each(hashmap, function (it, key) { | |
dest_str = dest_str.replace(new RegExp('%'+key+'%', 'g'), it || ""); | |
}); | |
return dest_str; | |
}; | |
try { | |
Packrat = require('packrat'); | |
} catch (err) { | |
Packrat = window.Packrat || throw "packrat.js is required"; | |
} | |
function BlockParser (input) { | |
this.cache = {}; | |
this.input = input; | |
this.destinationTemplate = "function (%parameters%) { %body% }"; | |
}; | |
BlockParser.prototype = new Packrat(""); | |
BlockParser.prototype.expr = function () { | |
var _this = this; | |
return this.cacheDo("expr", function () { | |
var parameters, body; | |
_this.blockStart(); | |
parameters = _this.blockHead(); | |
body = _this.body(); | |
_this.blockEnd(); | |
return __template(this.destinationTemplate, {parameters:parameters, body:body}); | |
}); | |
}; | |
BlockParser.prototype.space = function () { | |
var _this = this; | |
return this.cacheDo("space", function () { return _this.regex(/[\s\n\t]+/); }); | |
}; | |
BlockParser.prototype.blockStart = function () { | |
var _this = this; | |
return this.cacheDo("blockStart", function () {return _this.chr("[");}); | |
}; | |
BlockParser.prototype.blockEnd = function () { | |
var _this = this; | |
return this.cacheDo("blockEnd", function () {return _this.chr("]");}); | |
}; | |
BlockParser.prototype.variable = function () { | |
var _this = this; | |
return this.cacheDo("variable", function () {return _this.regex(/[a-zA-Z]+/);}); | |
}; | |
BlockParser.prototype.colon = function () { | |
var _this = this; | |
return this.cacheDo("colon", function () {return _this.chr(":");}); | |
}; | |
BlockParser.prototype.variables = function () { | |
var _this = this; | |
return this.cacheDo("variables", function () { | |
vars = ""; | |
_this.many(function () { | |
_this.colon(); | |
vars += _this.variable() + ", "; | |
_this.optional(_this.space); | |
}); | |
return vars.slice(0, -2); | |
}); | |
}; | |
BlockParser.prototype.colonVariable = function () { | |
var _this = this; | |
return this.cacheDo("colonVariable", function () { | |
return _this.sequence(_this.colon, _this.variable); | |
}); | |
}; | |
BlockParser.prototype.verticalBar = function () { | |
var _this = this; | |
return this.cacheDo("verticalBar", function () {return _this.chr("|");}); | |
}; | |
BlockParser.prototype.blockHead = function () { | |
var _this = this; | |
return this.cacheDo("blockHead", function () { | |
return _this.optional(function () { | |
var params; | |
params = _this.variables(); | |
_this.verticalBar(); | |
return params; | |
}); | |
}); | |
}; | |
BlockParser.prototype.body = function () { | |
var _this = this; | |
return this.cacheDo("body", function () { | |
return _this.many(function () { | |
return _this.notFollowedBy(_this.blockEnd) === null ? _this.anyChar() : null; | |
}) | |
}); | |
}; | |
if ( ! exports) var exports = window; | |
exports.BlockParser = BlockParser; | |
(function () { | |
" examples " | |
new BlockParser("[:foo :bar | return bar]").expr(); // function (foo bar ) { return bar } | |
new BlockParser("[block without parameters]").expr(); // function () { block without parameters } | |
})(); | |
}).call(this); |
jsを楽に書きたい。
Little Smalltalk楽しい。
というわけでLittle Smalltalk -> Javascriptコンパイラ作る。
Little Smalltalkの処理系が手に入らなかったので本を見ながら作る。
- 最初は基本型はjsの基本型のみをサポートする。(ListやらBagやらは作らない)
- 出発点がjsの作成支援なので、最初のバージョンは最適化等は考えず、単純な翻訳機に徹する。
- function () {} が [] になるだけでも相当恩恵はある。
- メッセージ式はそのままjsのメソッド呼び出しに変換する。
- 参照: https://gist.github.com/3654115
- オブジェクトobjへの単項メッセージobj unaryはobj.unary()、キーワード引数obj keyword: 1 keyword2: 2はobj.keyword1_keyword2(1, 2)、二項メッセージは後回し
- このときunaryやkeyword1_keyword2はjsでobjのコンストラクタのprototypeに定義することとする。
- 予約語やメソッド名に使えない記号のときは他の名前に変換できる仕組みを用意する。 (辞書?)
最終目標として、from.stをto.jsにコンパイルできるようにする。
to.jsはCoffeeScriptが吐き出すコードを真似たい。
Class Klass :superClass | |
| someArray index | | |
[ | |
new | |
someArray <- Array new. | |
index <- 0 | |
| | |
current | |
^ someArray at: index | |
| | |
current: replacement | |
someArray at: index put: replacement. | |
^ self | |
| | |
incrementPointer | origin | | |
origin <- index deepCopy. | |
index <- index + 1. | |
(origin, ' to ', index) print. | |
^ index | |
] | |
(Klass new ; incrementPointer ; current: 3) current print. | |
#(1 2 3 4) inject: 0 into: [:a :lastres | lastres + a]. |
http://www.konokida.com/orengo/01.ParsingExpressionGrammar/index.html
http://d.hatena.ne.jp/kmizushima/20100203/1265183754
http://d.hatena.ne.jp/ku-ma-me/20070906/p1
http://code.google.com/p/dupsrem/source/browse/trunk/dupes/chrome/content/removedupes/kouprey.js?r=15
http://practical-scheme.net/wiliki/wiliki.cgi?Rui%3AParsingExpressionGrammar
入力に対してパーサを次々に試していき、成功したらマッチした部分を消費し、失敗したらバックトラックを行う(失敗したパーサ試行前まで戻る)。
連接 a b
全て読み込むかまったく読み込まず死。
選択 a / b
aに失敗したらbを検査。どちらかが読み込めればOK
繰り返し a*
aの0回以上の繰り返し. 配列が返る。
1回以上の繰り返し a+
aa*のシンタックスシュガー
先読みAnd &a
入力がaにマッチするか検査して、マッチしたら成功。ただし入力を消費しない。
失敗したら死
先読みNot !a
Andの否定
/* packratparser.js - javascript clone of http://d.hatena.ne.jp/ku-ma-me/20070906/p1 | |
* | |
* packratparser.js is part of Little Smallscript. | |
*/ | |
(function () { | |
'use strict'; | |
var Packrat, | |
__toArray = function (a) { return [].slice.call(a); }, | |
__valid = function (vari) { return /*vari !== null &&*/ vari !== undefined; }; | |
/* | |
* Packrat Parser is an implementation of PEG | |
* new this constructor function with input you want to parse | |
* to generare parsers. | |
*/ | |
Packrat = (function () { | |
var Packrat, NoParse; | |
/* constructor */ | |
function Packrat (input) { | |
this.input = input; | |
this.index = 0; | |
this.cache = {}; | |
} | |
/* | |
* Cache parsers instead of evaluating them every time. | |
* s = parser name, fn = the parser returned by the parser | |
*/ | |
Packrat.prototype.cacheDo = function (s, fn) { | |
fn = fn || function () {}; //block | |
var c = {}; // c={fn:,idx:} | |
if ((this.cache[s] || (this.cache[s] = {}))[this.index]) { | |
c = this.cache[s][this.index]; | |
if (c.idx) { | |
this.index = c.idx; | |
return c.fn; | |
} | |
return this.noParse(); | |
} | |
try { | |
c.idx = this.index; | |
c.fn = fn.call(this); | |
this.cache[s][c.idx] = {fn:c.fn, idx:this.index}; | |
return c.fn; | |
} catch (err) { | |
this.cache[s][c.idx] = null; | |
throw err; | |
} | |
}; | |
/* | |
* constructor for Error objects that is thrown on failure | |
*/ | |
NoParse = (function () { | |
function NoParse () { | |
}; | |
NoParse.prototype = new Error; | |
return NoParse; | |
})(); | |
/* | |
* throw NoParse | |
*/ | |
Packrat.prototype.noParse = function () { | |
throw new NoParse; | |
}; | |
/*--- Definition of basic combinators ---*/ | |
/* available pegs | |
* a b, a / b, a?, a*, a+, &a, !a, (a) | |
*/ | |
/* | |
* ordered or | |
* a / b / ... | |
*/ | |
Packrat.prototype.try_ = function (/* &rest arguments */) { | |
var i, ret, _this = this; | |
i = this.index; | |
__toArray(arguments).forEach(function (a) { | |
if (ret) return; | |
try { | |
ret = a.call(_this); | |
} catch (err) { | |
if ( ! err instanceof NoParse) throw err; | |
_this.index = i; | |
} | |
}); | |
return __valid(ret) ? ret : this.noParse(); | |
}; | |
/* | |
* match all or none | |
* a b ... | |
*/ | |
Packrat.prototype.sequence = function (/* &rest arguments */) { | |
var i, ret, fail, _this = this; | |
i = this.index; | |
ret = ""; | |
fail = false; | |
__toArray(arguments).forEach(function (a) { | |
if (fail) return; | |
try { | |
ret += a.call(_this); | |
} catch (err) { | |
if ( ! err instanceof NoParse) throw err; | |
_this.index = i; | |
fail = true; | |
_this.noParse(); | |
} | |
}); | |
return __valid(ret) ? ret : this.noParse(); | |
}; | |
/* | |
* succeeds even when the parser doesn't match | |
* a? | |
*/ | |
Packrat.prototype.optional = function (fn) { | |
var i; | |
i = this.index; | |
try { | |
return fn.call(this); | |
} catch (err) { | |
if ( ! err instanceof NoParse) throw err; | |
this.index = i; //backtraq | |
return null; | |
} | |
}; | |
/* | |
* succeeds if the given parser matches. | |
* this parser doesn't consume the input | |
* &a | |
*/ | |
Packrat.prototype.followedBy = function (fn) { | |
var f = true, | |
i = this.index; | |
try { | |
fn.call(this); | |
f = false; | |
} catch (err) { | |
if ( ! err instanceof NoParse) throw err; | |
} | |
this.index = i; //backtraq | |
return f ? this.noParse() : null; | |
}; | |
/* | |
* opposite of followedBy | |
* !a | |
*/ | |
Packrat.prototype.notFollowedBy = function (fn) { | |
var f = false, | |
i = this.index; | |
try { | |
fn.call(this); | |
f = true; | |
} catch (err) { | |
if ( ! err instanceof NoParse) throw err; | |
} | |
this.index = i; | |
return f ? this.noParse() : null; | |
}; | |
/* | |
* 0 or more ocuurance. | |
* a* | |
*/ | |
Packrat.prototype.many = function (fn) { | |
var _this = this; | |
return this.try_( | |
function () { return _this.many1( function () { return fn.call(_this); } ); }, | |
function () { return ""; } | |
); | |
}; | |
/* | |
* 1 or more occurance. | |
* a+ | |
*/ | |
Packrat.prototype.many1 = function (fn) { | |
var v, vs, _this = this; | |
v = fn.call(this); | |
vs = this.many(function () { return fn.call(_this); }); | |
return v += vs; | |
}; | |
/* | |
* Matchs and consumes any one character. | |
*/ | |
Packrat.prototype.anyChar = function () { | |
var c; | |
c = this.input[this.index]; | |
this.index += 1; | |
return __valid(c) ? c : this.noParse(); | |
}; | |
/* | |
* Takes predicate block and consumes a character if satisfied. | |
*/ | |
Packrat.prototype.satisfyChar = function (fn) { | |
var c; | |
c = this.anyChar(); | |
return fn.call(this, c) ? c : this.noParse(); | |
}; | |
/* | |
* Matches the given character. | |
*/ | |
Packrat.prototype.chr = function (ch) { | |
var c; | |
c = this.anyChar(); | |
return c == ch ? c : this.noParse() | |
}; | |
/* | |
* Matches the given string. | |
*/ | |
Packrat.prototype.string = function (str) { | |
var _this = this; | |
str.split('').forEach(function (ch) { | |
var c; | |
c = _this.anyChar(); | |
if (c !== ch) _this.noParse(); | |
}); | |
return str; | |
} | |
/* | |
* Reular expression | |
*/ | |
Packrat.prototype.regex = function (regex) { | |
var rc, match; | |
rc = regex.exec(this.input.substring(this.index)); | |
if (rc) { | |
match = rc[0]+""; | |
this.index += match.length; | |
return match; | |
} | |
this.noParse(); | |
} | |
return Packrat; | |
})(); | |
if ( ! exports) var exports = window; | |
exports.Packrat = Packrat; | |
}).call(this); |
(function () { | |
var Klass, | |
__hasProp = {}.hasOwnProperty, | |
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; | |
Klass = (function (_super) { | |
__extends(Klass, _super); | |
function Klass () { | |
this.someArray = null; | |
this.index = null; | |
this.init(); | |
} | |
Klass.prototype.init = function () { | |
this.someArray = Array.new_(); | |
this.index = 0; | |
}; | |
Klass.prototype.current = function () { | |
return this.someArray.at_(this.index); | |
}; | |
Klass.prototype.current_ = function (replacement) { | |
this.someArray.at_put_(this.index, replacement); | |
return this; | |
}; | |
Klass.prototype.incrementPointer = function () { | |
var origin; | |
origin = index.deepCopy(); | |
index = index.plus_(1); | |
(origin.concat_(' to ').concat_(index)).print(); | |
return index; | |
}; | |
})(superClass); | |
(function () { | |
var _cascade; | |
_cascade = Klass.new_(); | |
_cascade.incrementPointer(); | |
_cascade.current_(3); | |
_cascade.current().print(); | |
})(); | |
[1, 2, 3, 4].inject_into_(0, function (a, lastres) { return lastres.plus_(a); }); | |
}).call(this); |