Skip to content

Instantly share code, notes, and snippets.

@makenowjust
Created April 19, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save makenowjust/c5f23d93efefe335dcd1 to your computer and use it in GitHub Desktop.
Save makenowjust/c5f23d93efefe335dcd1 to your computer and use it in GitHub Desktop.
Moon is Macro based Programming Language.
'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;
}
# 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