diff --git a/Slang/slang.html b/Slang/slang.html new file mode 100755 index 0000000..e69dd24 --- /dev/null +++ b/Slang/slang.html @@ -0,0 +1,11 @@ + + + + + + diff --git a/Slang/slang.js b/Slang/slang.js new file mode 100755 index 0000000..4ad3f76 --- /dev/null +++ b/Slang/slang.js @@ -0,0 +1,504 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + + +/*********************************************************************/ + +function run(context){ + + var stack = context.stack + stack = stack == null ? [] : stack + context.stack = stack + + var ns = context.ns + + while(context.code.length > 0){ + + var cur = context.code.splice(0, 1)[0] + + // exit... + if(typeof(cur) == typeof('abc') && cur == '_exit'){ + return context + + // word + } else if(typeof(cur) == typeof('abc') && cur in ns){ + var word = ns[cur] + // low-level word... + if(typeof(word) == typeof(function(){})){ + var res = ns[cur](context) + + // hi-level word... + } else if(typeof(word) == typeof([]) && word.constructor.name == 'Array'){ + // XXX add isolation with a continuation... + context.code.splice.apply(context.code, [0, 0].concat(word)) + var res = null + + // variable... + } else { + res = word + } + + if(res != null){ + context.stack.push(res) + } + + // everything else... + } else { + context.stack.push(cur) + } + } + + return context +} + +var SPLITTER = /\s*\([^\)]*\)\s*|\s*--.*[\n$]|\s*(".*")\s*|\s*('.*')\s*|\s+/m + + +// pre-processor namespace... +var PRE_NAMESPACE = { + // XXX use the real reader... + // block... + // syntax: [ ... ] + '[': function(context){ + var block = [] + var code = context.code + var cur = code.splice(0, 1)[0] + while(cur != ']' && code.length > 0){ + if(cur == '['){ + cur = this['['](context) + } + block.push(cur) + cur = code.splice(0, 1)[0] + } + if(code.length == 0 && cur != ']'){ + console.error('Did not find expected "]".') + } + return block + }, + 'macro:': function(context){ + var ident = context.code.splice(0, 1) + var cur = context.code.splice(0, 1) + + // as we do not have blocks yet we need to manually collect one ;) + if(cur[0] == '['){ + cur = [ this['['](context) ] + } + + this[ident] = cur[0] + }, +} + + +// main namespace... +var NAMESPACE = { + // constants... + 'true': true, + 'false': false, + // this is mutable, so we'll need to create a new instance each time + '[]': function(){ return [] }, + + 'nop': function(){}, + + // XXX experimental... + // flip the code and stack... + // ... -- ... + '_flip': function(context){ + var stack = context.stack + context.stack = context.code.reverse() + context.code = stack.reverse() + }, + + // swap heads of stack and code + // ... ns nc -- ... + '_swapN': function(context){ + var c = context.stack.pop() + var s = context.stack.pop() + var l = context.stack.length + + // get the stack/code sections to swap... + var s_c = context.stack.splice(l-s, l) + var c_c = context.code.splice(0, c) + var l = context.stack.length + + // we need to pad e_c and c_c to requested length... + s_c = s_c.length < s ? s_c.concat(Array( s - s_c.length )) : s_c + c_c = c_c.length < c ? c_c.concat(Array( c - c_c.length )) : c_c + + // XXX we also need to shove something more sensible in the + // padding that undefined... + + context.code.splice.apply(context.code, [0, 0].concat(s_c)) + context.stack.splice.apply(context.stack, [l, 0].concat(c_c)) + }, + + // encapsulate stack to a block... + // ... -- [ ... ] + 's2b': function(context){ + context.stack = [ context.stack ] + }, + // expand block to stack... + // NOTE: this will append the block contents to stack, w.o. replacing + // stack contents. this is different to _s2b + // ... [ ... ] -- ... ... + 'b2s': function(context){ + var c = context.stack.pop() + context.stack.splice.apply(context.stack, [context.stack.length, 0].concat(c)) + }, + 'print': function(context){ + console.log('>>>', context.stack[context.stack.length-1]) + }, + + // turn a sting into a lexical list... + // c -- b + // XXX BUG see code... + 'lex': function(context){ + code = context.stack.pop() + if(typeof(code) == typeof('abc')){ + // XXX BUG: '"aaa" "bbb"' translates to ['"aaa"', '" "', '"bbb"'] + // i.e. quotes w/o whitespace are eaten... + if(/^(['"]).*\1$/m.test(code)){ + code = code.split(/^(['"])(.*)\1$/m)[2] + } + var res = [] + code = code + // split by strings whitespace and block comments... + .split(SPLITTER) + // parse numbers... + .map(function(e){ + // numbers... + if(/^[-+]?[0-9]+\.[0-9]+$/.test(e)){ + e = parseFloat(e) + } else if(/^[-+]?[0-9]+$/.test(e)){ + e = parseInt(e) + } + return e + }) + // remove undefined groups... + .filter(function(e){ + // NOTE: in JS 0 == '' is true ;) + return e !== undefined && e !== '' + }) + } + return code + }, + // pre-process a lexical list... + // a -- b + 'prep': function(context){ + var code = context.stack.pop() + + return run({ + stack: [], + code: code, + ns: context.pre_ns, + pre_ns: {}, + }).stack + }, + + // word definition... + // syntax: :: + // -- + '::': function(context){ + var ident = context.code.splice(0, 1) + var cur = context.code.splice(0, 1) + + this[ident] = cur[0] + }, + + // s c -- s + '_exec': function(context){ + // block... + var b = context.stack.pop() + if(typeof(b) == typeof([]) && b.constructor.name == 'Array'){ + b = b.slice() + } else { + b = [ b ] + } + // stack... + var s = context.stack.pop() + var res = run({ + stack: s, + code: b, + // NOTE: this can have side-effects on the context... + ns: context.ns, + pre_ns: context.pre_ns + }).stack + return res + }, + // quote - push the next elem as-is to stack... + // -- x + '\\': function(context){ + var code = context.code + return code.splice(0, 1)[0] + }, + + // comparisons and logic... + // a b -- c + 'and': function(context){ + var b = context.stack.pop() + var a = context.stack.pop() + if(a){ + return b + } else { + return a + } + }, + // a b -- c + 'or': function(context){ + var b = context.stack.pop() + var a = context.stack.pop() + if(a){ + return a + } else { + return b + } + }, + // x -- b + 'not': function(context){ + return !context.stack.pop() + }, + // a b -- c + 'gt': function(context){ + return context.stack.pop() < context.stack.pop() + }, + // a b -- c + 'eq': function(context){ + return context.stack.pop() == context.stack.pop() + }, + + // stack operations... + // ... x -- x ... + 'rot': function(context){ + context.stack.splice(0, 0, context.stack.pop()) + }, + // x ... -- ... x + 'tor': function(context){ + context.stack.push(context.stack.shift()) + }, + // a b -- b a + 'swap': function(context){ + return context.stack.splice(context.stack.length-2, 1)[0] + }, + // x -- x x + 'dup': function(context){ + return context.stack[context.stack.length-1] + }, + // x -- + 'drop': function(context){ + context.stack.pop() + }, + + // basic number operations... + // a -- b + 'isNumber': function(context){ + return typeof(context.stack.pop()) == typeof(123) + }, + // a b -- c + 'add': function(context){ + return context.stack.pop() + context.stack.pop() + }, + 'sub': function(context){ + return - context.stack.pop() + context.stack.pop() + }, + 'mul': function(context){ + return context.stack.pop() * context.stack.pop() + }, + 'div': function(context){ + return context.stack.pop() / context.stack.pop() + }, + + // block/list operations... + 'isBlock': function(context){ + var e = context.stack.pop() + return typeof(e) == typeof([]) && e.constructor.name == 'Array' + }, + // b n -- b e + 'at': function(context){ + var i = context.stack.pop() + if(i < 0){ + var l = context.stack[context.stack.length-1] + return l[l.length + i] + } + return context.stack[context.stack.length-1][i] + }, + // b e n -- b + 'to': function(context){ + var i = context.stack.pop() + var e = context.stack.pop() + if(i < 0){ + var l = context.stack[context.stack.length-1] + l[l.length + i] = e + } else { + context.stack[context.stack.length-1][i] = e + } + }, + // b e n -- b + 'before': function(context){ + var i = context.stack.pop() + var e = context.stack.pop() + if(i < 0){ + var l = context.stack[context.stack.length-1] + l.splice(l.length + i + 1, 0, e) + } else { + context.stack[context.stack.length-1].splice(i, 0, e) + } + }, + // b -- b e + 'pop': function(context){ + return context.stack[context.stack.length-1].pop() + }, + // b -- b l + 'len': function(context){ + return context.stack[context.stack.length-1].length + }, + // b c -- b + 'each': function(context){ + var c = context.stack.pop() + var b = context.stack[context.stack.length-1] + for(var i=0; i < b.length; i++){ + // exec block in a separate context... + var res = run({ + stack: [b[i], c], + code: ['exec'], + // NOTE: this can have side-effects on the context... + ns: context.ns + }).stack + var l = res.length + if(l == 0){ + b.splice(i, 1) + i-- + } else { + b.splice.apply(b, [i, 1].concat(res)) + i += l - 1 + } + } + }, +} + + +var BOOTSTRAP = '\n'+ +':: _swap ( x -- y ) [ 1 1 _swapN ]\n'+ +':: _push ( x -- y ) [ 0 _swapN ]\n'+ +':: _pull ( x -- y ) [ 0 swap _swapN ]\n'+ +'\n'+ +':: exec ( b -- ... ) [ s2b pop _exec b2s ]\n'+ +':: eval ( c -- ... ) [ lex prep exec ]\n'+ +'-- like exec but will run a block in current context...\n'+ +':: b2c [ len rot b2s tor 0 _swapN ]\n'+ +'\n'+ +':: . ( x -- ) [ print drop ]\n'+ +'\n'+ +':: swap2 ( a _ b -- b _ a ) [ swap rot swap tor swap ]\n'+ +'\n'+ +':: isT ( a -- b ) [ not not true eq ]\n'+ +':: isF ( a -- b ) [ not isT ]\n'+ +//'\n'+ +//':: if ( cond a b -- ... ) [ rot rot exec isT tor and tor or exec ]\n'+ +'\n'+ +'-- [ ] ? [
] \n'+ +'-- [ ] ? [
] else [ ] \n'+ +':: _run_else [ drop dup \\ else eq [ drop \\ exec _swap 4 ] and\n'+ +' [ 1 _push 2 ] or\n'+ +' b2s 0 _swapN ]\n'+ +':: _run_then [ \\ exec swap dup \\ else eq [ (drop else) drop \\ drop _swap 6 ] and\n'+ +' [ (run as-is) 1 _push 4 ] or\n'+ +' b2s 0 _swapN ]\n'+ +// XXX BUG: if no code after this will break... +// Ex: +// -- this will not exec [ B ]... +// [ A ] ? [ B ] +// -- while this will... +// [ A ] ? [ B ] nop +':: ? [ exec [ _run_then 1 ] and [ swap _run_else 2 ] or b2s 2 _swapN ]\n'+ +'\n'+ +'\n'+ +'-- list/block 2\'nd gen stuff...\n'+ +':: push ( b e i -- b ) [ -1 before ]\n'+ +'\n'+ +'\n'+ +'-- experimental...\n'+ +'-- NOTE: these are at this point stupid and do not support priorities or grouping...\n'+ +'-- NOTE: these have both stack and code effect, in genera the operations are of \n'+ +'-- the form: A op B\n'+ +':: + [ \\ add _swap ]\n'+ +':: - [ \\ sub _swap ]\n'+ +':: * [ \\ mul _swap ]\n'+ +':: / [ \\ div _swap ]\n'+ +'\n'+ +':: == [ \\ eq _swap ]\n'+ +':: > [ \\ gt _swap ]\n'+ +'\n'+ +'\n'+ +'-- this is here for devel use only\n'+ +':: _clear ( * -- ) [ s2b drop ] \n'+ +':: _stack_size ( -- l ) [ s2b len swap b2s tor ] \n'+ +'\n'+ +'\n'+ +'-- tests and examples...\n'+ +':: hi ( -- ) [ "Hello World!" print drop ]\n'+ +//'-- NOTE: nop at the end is a stub to fix a bug in ? else ...\n'+ +//':: ! ( a -- b ) [ [ dup 1 eq not ] ? [ dup 1 sub ! mul ] nop ]\n'+ +':: ! ( a -- b ) [ [ dup 1 eq not ] ? [ dup 1 sub ! mul ] ]\n'+ +':: range ( n -- b ) [\n'+ +' -- initial state...\n'+ +' [ dup isNumber ] ? \n'+ +' [ [] swap ]\n'+ +' -- get first elem...\n'+ +' else\n'+ +' [ 0 at ]\n'+ +' -- we got to the end...\n'+ +' [ dup 0 eq ] ? \n'+ +' [ drop ]\n'+ +' -- dec push new and continue...\n'+ +' else\n'+ +' [ 1 sub 0 before range ] ]\n'+ +':: range2 ( n s -- b )\n'+ +' [ swap range swap [] swap push \\ * 0 before each ]\n'+ +'\n'+ +'' + + +var STARTUP = [[], BOOTSTRAP, 'lex', 'prep', '_exec', 'drop'] + + +// build bootstrap... +var CONTEXT = { + stack: [], + code: STARTUP.slice(), + ns: NAMESPACE, + pre_ns: PRE_NAMESPACE, +} + + +// run bootstrap... +run(CONTEXT) + + +// convenience... +function _slang(code, context){ + context = context == null ? CONTEXT : context + + context.code = code + return run(context).stack +} + + +function slang(code, context){ + context = context == null ? CONTEXT : context + + if(typeof(code) == typeof('abc')){ + code = [ '"'+code+'"', 'lex', 'prep', 'exec' ] + } else { + code = [ [code], 'b2s', 'prep', 'exec' ] + } + + context.code = code + return run(context).stack +} + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */