Created
April 19, 2015 14:08
-
-
Save makenowjust/c5f23d93efefe335dcd1 to your computer and use it in GitHub Desktop.
Moon is Macro based Programming Language.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
// class Context | |
function Context(parent) { | |
this.parent = parent; | |
this.local = new Map(); | |
this.whiteSpace = (parent || this).whiteSpace; | |
this.specialChar = clone((parent || this).specialChar); | |
this.isYield = false; | |
this.ruleName = '<global>'; | |
this.fileName = '<string>'; | |
this.lineNumber = 1; | |
} | |
Context.primitiveRule = new Map(); | |
Context.primitive = function primitive(name, exec, body) { | |
Context.primitiveRule.set(name, { | |
name: name, | |
fileName: '<primitive>', | |
lineNumber: 0, | |
isPrimitive: true, | |
exec: exec, | |
body: body || '', | |
}); | |
return Context; | |
}; | |
Context | |
.primitive('def', function (ctx, args, block) { | |
if (args.length < 1) { | |
ctx.throwError( | |
'rule "def" expects ' + argsString(1, true) + | |
', but given ' + argsString(args.length) + '.'); | |
} | |
if (block === false) { | |
ctx.throwError('rule "def" expects block.'); | |
} | |
var | |
name, argNames, | |
vargName = false, blockName = false, | |
body = block, | |
i, j = 0; | |
loop: | |
for (i = 0; i < args.length; i++) { | |
args[i] = removeWhitespace(args[i], ctx); | |
switch (args[i][0]) { | |
case ctx.specialChar.callBegin: | |
if (args[i][args[i].length - 1] === ctx.specialChar.callEnd) { | |
j += 1; | |
vargName = validateName(args[i++].slice(1, -1), ctx); | |
if (i < args.length && args[i][0] === ctx.specialChar.blockBegin && args[i][args[i].length - 1] === ctx.specialChar.blockEnd) { | |
j += 1; | |
blockName = validateName(args[i++].slice(1, -1), ctx); | |
} | |
break loop; | |
} | |
break; | |
case ctx.specialChar.blockBegin: | |
if (args[i][args[i].length - 1] === ctx.specialChar.blockEnd) { | |
j += 1; | |
blockName = validateName(args[i++].slice(1,-1), ctx); | |
break loop; | |
} | |
break; | |
} | |
args[i] = validateName(args[i], ctx); | |
} | |
if (i !== args.length) { | |
ctx.throwError('rule "def" cannot specify argument name after `(varg)` and `{block}`.'); | |
} | |
name = args[0]; | |
argNames = args.slice(1, args.length - j); | |
ctx.parent.define(name, argNames, vargName, blockName, body); | |
}) | |
.primitive('yield', function (ctx, args, block) { | |
if (args.length !== 0) { | |
ctx.throwError( | |
'rule "yield" expects ' + argsString(0) + | |
', but given ' + argsString(args.length) + '.'); | |
} | |
if (block !== false) { | |
ctx.throwError('rule "yield" expects no block.'); | |
} | |
ctx.parent.isYield = true; | |
}) | |
.primitive('return', function (ctx, args, block) { | |
if (args.length !== 0) { | |
ctx.throwError( | |
'rule "return" expects ' + argsString(0) + | |
', but given ' + argsString(args.length) + '.'); | |
} | |
if (block !== false) { | |
ctx.throwError('rule "return" expects no block.'); | |
} | |
ctx.parent.isReturn = true; | |
}) | |
.primitive('uniq-name', function (ctx, args, block) { | |
if (args.length !== 0) { | |
ctx.throwError( | |
'rule "uniq-name" expects ' + argsString(0) + | |
', but given ' + argsString(args.length) + '.'); | |
} | |
if (block !== false) { | |
ctx.throwError('rule "uniq-name" expects no block.'); | |
} | |
return [new UniqName()]; | |
}); | |
Context.prototype.specialChar = { | |
replace: '@', | |
separate: ',', | |
comment: '#', | |
callBegin: '(', | |
callEnd: ')', | |
blockBegin: '{', | |
blockEnd: '}', | |
}; | |
Context.prototype.whiteSpace = ' \t\n\r'.split(''); | |
Context.prototype.rule = function rule(name) { | |
if (this.local.has(name)) { | |
return this.local.get(name); | |
} | |
if (this.parent) { | |
return this.parent.rule(name); | |
} | |
if (Context.primitiveRule.has(name)) { | |
return Object.create(Context.primitiveRule.get(name)); | |
} | |
this.throwError('rule ' + nameString(name) + ' is not defined.'); | |
}; | |
Context.prototype.bind = function bind(rule, args, block) { | |
var | |
newCtx = new Context(this), | |
vargs; | |
newCtx.ruleName = rule.name; | |
newCtx.fileName = rule.fileName; | |
newCtx.lineNumber = rule.lineNumber; | |
if (rule.isPrimitive) { | |
rule.body = rule.exec(newCtx, args, block) || []; | |
return newCtx; | |
} | |
if (rule.varArgName === false && rule.argNames.length !== args.length || | |
rule.argNames.length > args.length) { | |
this.throwError( | |
'rule ' + nameString(rule.name) + ' expects ' + argsString(rule.argNames.length, rule.varArgName) + | |
', but given ' + argsString(args.length) + '.'); | |
} | |
rule.argNames.forEach(function loop(name, i) { | |
newCtx.define(name, [], false, false, args[i]); | |
}); | |
if (rule.varArgName !== true) { | |
newCtx.define(rule.varArgName, [], false, false, join(args.slice(rule.argNames.length), ctx)); | |
} | |
if (rule.blockName === false) { | |
if (block !== false) { | |
this.throwError('rule ' + nameString(rule.name) + ' expects no block.'); | |
} | |
} else { | |
if (block === false) { | |
this.throwError('rule ' + nameString(rule.name) + ' expects block.'); | |
} | |
newCtx.define(rule.blockName, [], false, false, block); | |
} | |
return newCtx; | |
}; | |
Context.prototype.throwError = function throwError(msg) { | |
var | |
err = new Error('moon error:' | |
+ '(' + this.fileName + ':' + this.lineNumber + ':' + this.ruleName + ') ' | |
+ msg); | |
err.moon = { | |
message: msg, | |
stack: createStackTrace(this), | |
}; | |
throw err; | |
}; | |
Context.prototype.define = function define(name, argNames, vargName, blockName, body) { | |
this.local.set(name, { | |
name: name, | |
fileName: this.fileName, | |
lineNumber: this.lineNumber, | |
argNames: argNames, | |
varArgName: vargName, | |
blockName: blockName, | |
body: body, | |
}); | |
}; | |
function createStackTrace(ctx) { | |
var | |
stack = []; | |
while (ctx) { | |
stack.push({ | |
ruleName: ctx.ruleName, | |
fileName: ctx.fileName, | |
lineNumber: ctx.lineNumber, | |
}); | |
ctx = ctx.parent; | |
} | |
return stack; | |
} | |
function argsString(len, varg) { | |
return len + (varg !== false && varg !== undefined ? '+' : '') + ' argument' + (len >= 2 ? 's' : ''); | |
} | |
function removeWhitespace(name, ctx) { | |
var | |
i = 0, j = name.length - 1; | |
while (i < name.length) { | |
if (ctx.whiteSpace.indexOf(name[i]) !== -1) { | |
i += 1; | |
} else { | |
break; | |
} | |
} | |
while (j >= 0) { | |
if (ctx.whiteSpace.indexOf(name[j]) !== -1) { | |
j -= 1; | |
} else { | |
break; | |
} | |
} | |
return name.slice(i, j + 1); | |
} | |
function validateName(name, ctx) { | |
var | |
i; | |
if (name.length === 1 && isUniqName(name[0], ctx)) { | |
return name[0]; | |
} | |
for (i = 0; i < name.length; i++) { | |
if (!isNameChar(name[i], ctx)) { | |
ctx.throwError('rule "def" cannot accept argument name.'); | |
} | |
} | |
return name.join(''); | |
} | |
// UniqName | |
function UniqName() { | |
this.uid = UniqName.uid++; | |
} | |
UniqName.prototype.toString = function () { | |
return '<uniq:' + this.uid + '>'; | |
}; | |
UniqName.uid = 0; | |
function nameString(name) { | |
return typeof name === 'string' ? JSON.stringify(name) : | |
'<uniq:' + name.uid + '>'; | |
} | |
// executer | |
function execute(src, i, ctx, separate) { | |
var | |
c, | |
output = [], | |
result; | |
while (i < src.length) { | |
switch (c = src[i++]) { | |
case ctx.specialChar.replace: | |
result = execReplace(src, i - 1, ctx); | |
if (result.isYield) { | |
src.splice.apply(src, [result.begin, result.end - result.begin + 1].concat(result.output)); | |
i -= 1; | |
} else { | |
output.push.apply(output, result.output); | |
i = result.end + 1; | |
} | |
break; | |
case ctx.specialChar.comment: | |
while (i < src.length) { | |
if (src[i++] == '\n') break; | |
} | |
ctx.lineNumber += 1; | |
break; | |
case '\n': | |
ctx.lineNumber += 1; | |
// fallthrough | |
default: | |
if (separate && (c === ctx.specialChar.separate || c === ctx.specialChar.callEnd)) { | |
return { | |
output: output, | |
index: i - 1, | |
}; | |
} | |
output.push(c); | |
break | |
} | |
if (ctx.isYield) { | |
return { | |
output: output.concat(src.slice(i)), | |
index: src.length, | |
}; | |
} else if (ctx.isReturn) { | |
return { | |
output: output.concat(src.slice(i)), | |
index: src.length, | |
}; | |
} | |
} | |
return { | |
output: output, | |
index: i, | |
}; | |
} | |
function execReplace(src, begin, ctx) { | |
var | |
i = begin + 1, | |
c, | |
name = '', args = [], block = false, | |
argsResult, blockResult, result, | |
rule, newCtx; | |
c = src[i]; | |
if (isSpecialChar(c, ctx)) { | |
return { | |
begin: begin, | |
end: i, | |
isYield: false, | |
output: [c], | |
}; | |
} | |
if (isUniqName(c, ctx)) { | |
name = c; | |
i += 1; | |
} else { | |
while (i < src.length) { | |
c = src[i++]; | |
if (isNameChar(c, ctx)) { | |
name += c; | |
} else { | |
i -= 1; | |
break; | |
} | |
} | |
} | |
switch (src[i]) { | |
case ctx.specialChar.callBegin: | |
argsResult = execCall(src, i, ctx); | |
if (argsResult.isFail) { | |
break; | |
} | |
args = argsResult.args; | |
i = argsResult.end + 1; | |
if (src[i] === ctx.specialChar.blockBegin) { | |
blockResult = execBlock(src, i, ctx); | |
if (blockResult.isFail) break; | |
block = blockResult.block; | |
i = blockResult.end + 1; | |
} | |
break; | |
case ctx.specialChar.blockBegin: | |
blockResult = execBlock(src, i, ctx); | |
if (blockResult.isFail) break; | |
block = blockResult.block; | |
i = blockResult.end + 1; | |
break; | |
default: | |
break; | |
} | |
rule = ctx.rule(name); | |
newCtx = ctx.bind(rule, args, block); | |
result = execute(rule.body.slice(), 0, newCtx, false); | |
return { | |
begin: begin, | |
end: i - 1, | |
isYield: newCtx.isYield, | |
output: result.output, | |
}; | |
} | |
function execCall(src, begin, ctx) { | |
var | |
i = begin, | |
c, | |
result, | |
args = []; | |
while (i < src.length) { | |
c = src[i]; | |
if (c === ctx.specialChar.callEnd) { | |
return { | |
end: i, | |
args: args, | |
}; | |
} | |
result = execute(src, i + 1, ctx, true); | |
if (ctx.isYield || ctx.isReturn) { | |
return { | |
isFail: true, | |
} | |
} | |
i = result.index; | |
args.push(result.output); | |
} | |
return { | |
isFail: true, | |
}; | |
} | |
function execBlock(src, begin, ctx) { | |
var | |
i = begin, | |
c, | |
nest = [], | |
result; | |
while (i < src.length) { | |
switch (src[i++]) { | |
case ctx.specialChar.callBegin: | |
nest.push('('); | |
break; | |
case ctx.specialChar.callEnd: | |
if (nest[nest.length - 1] === '(') { | |
nest.pop(); | |
} | |
break; | |
case ctx.specialChar.blockBegin: | |
nest.push('{'); | |
break; | |
case ctx.specialChar.blockEnd: | |
if (nest[nest.length - 1] === '{') { | |
nest.pop(); | |
} | |
break; | |
case ctx.specialChar.comment: | |
while (i < src.length) { | |
if (src[i++] === '\n') break; | |
} | |
break; | |
case ctx.specialChar.replace: | |
i++; | |
break; | |
} | |
if (nest.length === 0) { | |
return { | |
end: i - 1, | |
block: src.slice(begin + 1, i - 1), | |
}; | |
} | |
} | |
return { | |
isFail: true, | |
} | |
} | |
function join(args, ctx) { | |
return args.length === 0 ? [] : | |
args.slice(1).reduce(function (varg, arg) { | |
return varg.concat(ctx.specialChar.separate, arg); | |
}, args[0]); | |
} | |
// predicates of a character | |
function isSpecialChar(c, ctx) { | |
return c === ctx.specialChar.replace || | |
c === ctx.specialChar.comment || | |
c === ctx.specialChar.separate || | |
c === ctx.specialChar.callBegin || | |
c === ctx.specialChar.callEnd || | |
c === ctx.specialChar.blockBegin || | |
c === ctx.specialChar.blockEnd; | |
} | |
function isUniqName(name, ctx) { | |
return name instanceof UniqName; | |
} | |
function isNameChar(c, ctx) { | |
return ctx.whiteSpace.indexOf(c) === -1 && !isSpecialChar(c, ctx); | |
} | |
// utility functions | |
function clone(object) { | |
return Object.keys(object).reduce(function (newObject, key) { | |
newObject[key] = object[key]; | |
return newObject; | |
}, {}); | |
} | |
var | |
fs = require('fs'), file, out, | |
ctx = new Context(); | |
if (process.argv.length < 3) { | |
console.log('node moon.js <file>'); | |
process.exit(0); | |
} | |
try { | |
file = fs.readFileSync(process.argv[2], 'utf8'); | |
out = execute(file.split(''), 0, ctx, false); | |
process.stdout.write(out.output.join('')); | |
} catch (e) { | |
ctx.local.forEach(function (val, key) { | |
console.log("%j: %j", key, val); | |
}); | |
if (e.moon) console.log(e.moon.stack); | |
throw e; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Bitwise Cyclic Tag | |
@def(pop,_,(data@)){@data}# | |
@def(pop2,_1,_2,(data@)){@data}# | |
@def(top,data,(_@)){@data}# | |
@def(top2,_1,data,(_2@)){@data}# | |
@def(empty,(data@),{block}){@def(ok){@block}@def(wrap1,@(_@)){@ok}@def(if0){}@def(if1){}@def(if){@@def(ok){}@yield}@def(wrap2){@@wrap1(@@if@data)@yield}@wrap2}# | |
@def(0){@@def(data){@pop(@data)@@yield}@@def(src){@pop(@src),0@@yield}@yield}# | |
@def(1){@@1@top2(@src)@yield}# | |
@def(11){@@def(if0){}@@def(if1){@@@@def(data){@@data,1@@@@yield}@@yield}@@def(src){@pop2(@src),1,1@@yield}@@if@top(@data)@yield}# | |
@def(10){@@def(if0){}@@def(if1){@@@@def(data){@@data,0@@@@yield}@@yield}@@def(src){@pop2(@src),1,0@@yield}@@if@top(@data)@yield}# | |
@def(exec){@src @data | |
@empty(@data){@@@top(@src)@@exec}@yield}# | |
# | |
### data section ### | |
# | |
@def(src){1,1,0,1,0,0@yield}# | |
@def(data){1,0@yield}# | |
# | |
@exec# | |
Finish! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment