diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..4276016 --- /dev/null +++ b/manifest.json @@ -0,0 +1,7 @@ + +{ + "short_name": "Slang", + "name": "Slang", + "display": "standalone", + "Theme_color": "white" +} diff --git a/slang.appcache b/slang.appcache new file mode 100644 index 0000000..9f31719 --- /dev/null +++ b/slang.appcache @@ -0,0 +1,11 @@ +CACHE MANIFEST +# Timestamp: 20170929020625 + +CACHE: +slang.html +slang.js +manifest.json + +NETWORK: +* + diff --git a/slang.html b/slang.html new file mode 100644 index 0000000..4896af6 --- /dev/null +++ b/slang.html @@ -0,0 +1,279 @@ + + + +Slang + + + + + + + + + + + + + +

Slang

+ Toggle bootstrap code view... +
+

Available words

+

+ This section includes constants (red) and constant-like + words (words that allways yield the same value), + built-in words (blue), and 2'nd gen words (black): +

+

+

Slang Console

+
+
+
+
+ + + diff --git a/slang.js b/slang.js new file mode 100644 index 0000000..17809a8 --- /dev/null +++ b/slang.js @@ -0,0 +1,1134 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + +/* XXX for some odd reason this breaks the interpreter... +Array.prototype.toString = function(){ + return '[ ' + this.join(', ') + ' ]' +} +*/ + + +/*********************************************************************/ + +function run(context){ + + context.stack = context.stack == null ? [] : context.stack + + 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 context.ns){ + var word = context.ns[cur] + // low-level word... + if(typeof(word) == typeof(function(){})){ + var res = context.ns[cur](context) + + // hi-level word... + } else if(typeof(word) == typeof([]) && word && word.constructor.name == 'Array'){ + // XXX add isolation with a continuation... + context.code.splice.apply(context.code, [0, 0].concat(word)) + var res = undefined + + // variable... + } else { + var res = word + } + + if(res !== undefined){ + context.stack.push(res) + } + + // everything else... + } else { + context.stack.push(cur) + } + } + + return context +} + +// XXX make this add '\n' / EOL words to the stream... +//var SPLITTER = /\s*\([^\)]*\)\s*|\s*--.*[\n$]|\s*"([^"]*)"\s*|\s*'([^']*)'\s*|\s+/m +var SPLITTER = RegExp([ + /* XXX there are two ways to deal with comments: + // 1) lexer-based -- this section commented, next uncommented... + // 2) macro-based -- this section uncommented, next commented... + // #2 is a bit buggy... + // terms to keep in the stream... + '\\s*('+[ + '\\n', + '--', + ].join('|')+')', + //*/ + + //* lexer comments... + '\\s*\\([^\\)]*\\)\\s*', + '\\s*--.*[\\n$]', + //*/ + + // quoted strings... + '\\s*"([^"]*)"\\s*', + "\\s*'([^']*)'\\s*", + + // quote... + '\\s*(\\\\)', + + // whitespace... + '\\s+', + ].join('|'), + 'm') + + +// helpers... +// XXX should these skip quoted ends? +var collect = function(start, end){ + return function(context){ + var res = start ? [start] : [] + var code = context.code + var cur = code.shift() + res.push(cur) + while(cur != end && code.length > 0){ + cur = code.shift() + res.push(cur) + } + return res + } +} +var drop = function(start, end){ + var collector = collect(start, end) + //return function(context){ collector(context) } + return function(context){ console.log('XXX', collector(context).join(' ')) } +} + + +// pre-processor namespace... +var PRE_NAMESPACE = { + // comment... + // syntax: -- ... \n + // + // drop everything until '\n' + // + // NOTE: this depends on explicit '\n' words... + //'--': drop('--', '\n'), + '(': drop('(', ')'), + + // XXX use the real reader... + // block... + // syntax: [ ... ] + // NOTE: the shorthand ']]' will close ALL the open blocks. + // XXX should ']]' be smart enough to look ahead and close only the + // blocks not explicitly closed later??? + // ..see below for more... + '[': function(context){ + var block = [] + var code = context.code + var cur = code.splice(0, 1)[0] + while(cur != ']' && cur != ']]' && code.length > 0){ + if(cur == '['){ + cur = this['['](context) + } + block.push(cur) + cur = code.splice(0, 1)[0] + } + // we need this to be able to jump out of all the nested blocks, + // thus we'll keep the ']]' in code and remove it explicitly + // later... + if(cur == ']]'){ + // XXX should we look ahead and count the explicitly closed + // via ']' and ']]' blocks??? + // ...at this point this seems a bit complex... + // ...if there are more than one ']]' in a structure + // this might stop being deterministic... + code.splice(0, 0, cur) + } + if(code.length == 0 && cur != ']' && cur != ']]'){ + console.error('Did not find expected "]".') + } + return block + }, + // drop the closing words... + ']]': function(context){}, + ']': function(context){ console.error('Unexpected "]".') }, + + // XXX macros are not recursive... + '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] + }, + + // a no op... + '\n': function(){ }, +} + + +// main namespace... +var NAMESPACE = { + // constants... + 'true': true, + 'false': false, + 'null': 'null', + + 'nop': function(){}, + + 'is?': function(context){ + return context.stack.pop() === context.stack.pop() }, + + // 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() + c = c === undefined ? [] : c + 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(/^\s*(['"]).*\1\s*$/m.test(code)){ + code = code.split(/^\s*(['"])(.*)\1\s*$/m)[2] + } + + //console.log(code) + + 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 + }, + + // s c -- s + '_exec': function(context){ + // block... + var b = context.stack.pop() + if(typeof(b) == typeof([]) && b && 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 + }) + // XXX is this the right way to go? + context.ns = res.ns + context.pre_ns = res.pre_ns + return res.stack + }, + // quote - push the next elem as-is to stack... + // -- x + '\\': function(context){ + return context.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 -- x' + // NOTE: this will do a deep copy... + 'clone': function(context){ + return JSON.parse(JSON.stringify(context.stack.pop())) }, + // x -- + 'drop': function(context){ + context.stack.pop() }, + + // a -- b + // NOTE: all names are also strings so moo string? and 'moo' string? + // are the same... + 'string?': function(context){ + return typeof(context.stack.pop()) == typeof('str') }, + + // basic number operations... + // a -- b + 'number?': 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 1/context.stack.pop() * context.stack.pop() }, + + // block/list operations... + 'block?': function(context){ + var e = context.stack.pop() + return typeof(e) == typeof([]) && e && 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, 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 + 'map': 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, b[i], c], + 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 + } + } + }, + + + // object stuff... + '{}': function(){ return {} }, + + 'object?': function(context){ + var o = context.stack[context.stack.length - 1] + return o && o.constructor === Object + }, + + // set item... + // o k v -- o + 'item!': function(context){ + var v = context.stack.pop() + var k = context.stack.pop() + var o = context.stack[context.stack.length - 1] + + o[k] = v + }, + + // test item... + // o k -- o t + 'item?': function(context){ + return context.stack.pop() in context.stack[context.stack.length - 1] }, + + // get item... + // o k -- o v + 'item': function(context){ + var k = context.stack.pop() + return context.stack[context.stack.length - 1][k] }, + + // remove/pop item from object... + // o k -- o v + 'popitem': function(context){ + var k = context.stack.pop() + var o = context.stack[context.stack.length - 1] + + var v = o[k] + delete o[k] + + return v + }, + + // o -- k + 'keys': function(context){ + return Object.keys(context.stack.pop()) }, + + // make a prototype of b... + // a b -- b + // NOTE: if a is false, reset prototype... + 'proto!': function(context){ + var b = context.stack.pop() + var a = context.stack.pop() + b.__proto__ = a === false ? {}.__proto__ : a + return b + }, + + // o -- p + // XXX what should this be: + // {} getproto + 'proto': function(context){ + return context.stack.pop().__proto__ }, + + // -- o + 'ns': function(context){ + return context.ns }, + // o -- + 'ns!': function(context){ + context.ns = context.stack.pop() }, +} + + +// NOTE: hate how JS handles multi-line strings... +var BOOTSTRAP = +`------------------------------------------------------------------------------- + + [S]lang is a [s]imple and complete [S]tack [lang]uage. + + Slang was designed for three main reasons: + - a means to experiment with several aspects of language design, + - an educational tool, to illustrate several programming language + concepts in a simple, hands-on manner, + - fun! + + + +------------------------------------------------------------------------------- + + Slang Basics + ------------ + + The system consists of: + - Stack + - Code + - Namespace + - basic runtime + + { NAMESPACE } + ^ + | + [ .. STACK .. ] <-- runtime -- [ .. CODE .. ] + + + A namespace is a basic key/value store. + + The runtime "reads" entities from the code stream one by one and depending on + whether an entity exists in the namespace it is either pushed on the stack + or evaluated. + + The evaluated entity is traditionally called a "word" (function in non-stack + languages). The only thing that makes a word different from any other entity + is that it matches a key in the namespace, as mentioned above. + + In Slang evaluation is done simply by executing the value of the matched + key/value pair in the namespace. An over-simplified way to explain + evaluation is to say that the content of the value is pushed to the + code stream to be read right away, that\'s almost it, if we skip a + couple of details (see: _exec, exec and for details see: eval) + + The system contains two types of words: + - Host words -- defined by the host system, + - User words -- defined within the system (like this bootstrap code). + + Words may read and affect any of the three system parts: + - Stack + - Code + - Namespace + + Traditioannly, in stack languages words affect only the stack, this is + one of the motivations to implement Slang, that is, to experiment with + different ways to go with stack languages. + + + TODO: add topological comparison/diff + + + +----------------------------------------------------------------------------- + + Stack effect notation + --------------------- + + Traditionally, stack languages use a "stack effect" notation to document how + words affect the stack state, a kind of before-after transformation. here is + a basic example showing how the word "add" works: + + stack code + | 1 2 add + 1 | 2 add + 1 2 | add + 1 2 [add] (a) + 3 | (b) + + + Here the stack effect represents the difference between two states: the + moment when the word is "read" (a) and the stack state after it is + evaluated (b) and is written like this: + + ( a b -- c ) + + + But, due to the fact that in Slang all three of the stack, code and namespace + can be affected by words, we need an extended stack effect notation. to + include at least the second most common case, the "code effect". + To illustrate, here is an example of a word that has a simple code effect, + the "+": + + stack code + | 1 + 2 + 1 | + 2 + 1 [+] 2 (a) + 3 | (b) + + + Here we see that in addition to affecting the stack, 2 is "pulled" from the + code stream. To indicate this we will use "|" that splits the stack (left) + and code (right) states, and write the stack effect for the word "+" like + this: + + ( a | b -- c | ) + + + NOTE: this notation is currently used as a means to documenting words and is + not interpreted in any way. + + + +------------------------------------------------------------------------------- + + Blocks / Lists + -------------- + + Basic words for block manipulation: + + Get block length + + [ 1 2 3 ] len + -> [ 1 2 3 ] 3 + + + Pop element form block tail + + [ 1 2 3 ] pop + -> [ 1 2 ] 3 + + + Push element to block tail + + [ 1 2 3 ] 4 push + -> [ 1 2 3 4 ] + + + NOTE: all indexes can be negative values, these will indicate the + position relative to the tail, -1 being the last element. + + Get element at position (0) + + [ 1 2 3 ] -1 at + -> [ 1 2 3 ] 3 + + + Put element (123) at position (0) + + [ 1 2 3 ] 123 0 to + -> [ 123 2 3 ] + + + Put element (123) before position (0) + + [ 1 2 3 ] 123 0 before + -> [ 123 1 2 3 ] + + + Like before but puts the element after position + + [ 1 2 3 ] 123 0 after + -> [ 1 123 2 3 ] + + + Expand block to stack -- "block 2 stack" + + [ 1 2 3 ] b2s + -> 1 2 3 + + + Map a block/word to each element in a block + + [ 1 2 3 ] [ 1 add ] map + -> [ 2 3 4 ] + + the returned value (stack) of the input block is put into the result + block, this enables us to both remove (empty stack) and expand (more + than one value) the resulting list... + + [ 1 2 3 4 ] [ dup ] map + -> [ 1 1 2 2 3 3 4 4 ] + + [ 1 2 3 4 ] [ dup 2 gt ? [ ] else [ . ] ] map + -> [ 3 4 ] + + + this enables us to construct words like filter, which makes the code + in the last example more readable: + + [ 1 2 3 4 ] [ 2 gt ] filter + -> [ 3 4 ] + + Reduce enables us to take a list and "reduce" it to a single value... + + [ 1 2 3 4 ] \\add reduce + -> 10 + + +------------------------------------------------------------------------------- + + Objects and namespaces + ---------------------- + + Get the namespace object... + + ns -> namespace + + + Set attribute (key-value pair) on object... + + o x 123 item! + -> o + + Since almost all object words return the original object we can chain + object operations like this: + + Create a variable word o and p and set them to empty objects... + + ns + o {} item! + p {} item! + . + + Get attribute x value... + + o x item + -> 123 + + Test if attribute x exists... + + o x item? + -> true + + Get block of attribute idents... + + o keys + -> [ ... ] + + Get and remove an attribute value from o... + + o x popitem + -> 123 + + Set prototype of o to p + + o p proto! + -> o + + Get prototype of o + + o proto + -> p + + +------------------------------------------------------------------------------- +s2b drop -- cleanup after docs... +ns {} proto! ns! . -- keep new words in a seporate context... +-- +-- With that out of the way, let\'s start with the bootstrap... + +-- prepare the basic syntax for defining words... +ns + -- Some shorthands... + . ( x -- ) + [ drop ] item! + rot2 ( .. x y -- x y .. ) + [ rot rot ] item! + tor2 ( x y .. -- .. x y ) + [ tor tor ] item! + + -- Friendly exec... + exec ( b -- ... ) + [ s2b pop _exec b2s ] item! + -- Create a word... + word! ( w b -- ) + [ rot2 ns tor2 item! . ] item! + -- Word definition... + -- syntax: :: + :: ( | w b -- | ) + [ \\word! \\exec 2 2 _swapN ] item! +. + + +-- misc... + +:: true? ( a -- b ) [ not not true eq ] +:: false? ( a -- b ) [ not true? ] + +-- we already have gt and eq, now let\'s define the rest... +:: ne ( a b -- c ) [ eq not ] +:: lt ( a b -- c ) [ swap gt ] +:: ge ( a b -- c ) [ lt not ] +:: le ( a b -- c ) [ gt not ] + +:: inc ( a -- b ) [ 1 add ] +:: dec ( a -- b ) [ 1 sub ] +:: ! ( a -- b ) [ [ dup 1 ne ] ? [ dup 1 sub ! mul ] ] + + + +-- Stack/code manipulation... + +:: _swap ( x | y -- y | x ) [ 1 1 _swapN ] +:: _push ( x | -- | x ) [ 0 _swapN ] +:: _pull ( | x -- x | ) [ 0 swap _swapN ] + +:: eval ( c -- ... ) [ lex prep exec ] +-- like exec but will run a block in current context... +:: b2c [ len rot b2s tor 0 _swapN ] + +:: swap2 ( a _ b -- b _ a ) [ swap rot swap tor swap ] +:: dup2 ( a b -- a b a b ) [ dup swap2 dup rot swap2 tor swap ] + +-- this is here for devel use only +:: _clear ( ... -- ) [ s2b print drop ] +:: _stack_size ( -- l ) [ s2b len swap b2s tor ] + + + +-- Flow control... + +-- Classic conditional word: +-- [ cond ] [ A ] [ B ] if +-- +-- A bit too "polish" in my view ;) +:: if ( cond a b -- ... ) [ rot rot exec true? tor and tor or exec ] + +-- Ternary operator, this can take two forms: +-- COND ? A +-- COND ? A else B +-- +-- We will define this in stages, first the helpers: +-- run then block and drop \'else B\' if it exists... +:: _run_then ( a x | -- ... | x ) + ( a else | b -- ... | ) + [ \\exec swap dup \\else eq [ (drop else) drop \\drop _swap 6 ] and + [ (run as-is) 1 _push 4 ] or + b2s 0 _swapN ] +-- if \'else B\' exists, run it, else cleanup... +:: _run_else ( a | -- | a ) + ( b else | b -- ... | ) + [ drop dup \\else eq [ drop \\exec _swap 4 ] and + [ 1 _push 2 ] or + b2s 0 _swapN ] +-- And now the main operator... +-- NOTE: this may actually have one of three stack effects... +:: ? ( c | a -- | ) + ( c | a -- ... | ) + ( c | a else b -- ... | ) + [ exec [ _run_then 1 ] and [ swap _run_else 2 ] or b2s 2 _swapN ] + + + +-- List/block 2\'nd gen stuff... + +-- make a new block instance shorthand... +:: [] [ [ ] clone ] + +-- insert element after index... +:: after ( b e i -- b ) [ + -- special case, when at end, need to push the alement after it... + dup [ -1 eq ] ? + [ . push ] + else + [ inc before ]] + +-- NOTE: the "]]" in the last definition, it\'s a shorthand, it closes +-- ALL the open blocks to this point. +-- ...thus it can be used ONLY as the very last word in a set. + +-- push element to tail of block... +:: push ( b e -- b ) [ swap len rot swap tor to ] + +-- Replace a pattern (p) in block with value (v)... +-- NOTE: this will replace ALL patterns... +:: replace ( l v p -- l ) [ + swap + [ . \\VALUE ] clone + swap 2 to + rot + -- XXX for some reason ? without else messes things up... + [ dup \\PATTERN eq ? VALUE_BLOCK else [ ] ] clone + swap 2 to + tor 5 to + map ] + +-- Filter the block via a condition... +-- +-- the condition block must have the folowing stack effect: elem -- bool +:: filter ( b c -- b ) [ + -- prepare the condition... + [ dup \\TEST exec ] clone + swap TEST replace + -- prepare the template... + [ TEST ? [ ] else [ . ] ] clone + swap TEST replace + map ] + +:: reduce ( L b -- s ) [ + rot clone + -- empty list, reduction is null... + [ len 0 eq ] ? + [ . tor . null ] + -- reduction of list of len 1 is it\'s content, so just pop it... + else [ [ len 1 eq ] ? + [ tor . b2s ] + -- and now recursively reduce the elements till the list is 1 in length... + -- XXX ugly + else [ + pop rot pop rot + [] tor push tor push + -- get and run the block... + tor dup clone rot _exec + -- process the result... + pop rot . tor push tor + reduce ]] + +-- Create a block containing a range of numbers form 0 to n-1... +:: range ( n -- b ) [ + -- initial state... + [ dup number? ] ? + [ [] swap ] + -- get first elem... + else + [ 0 at ] + -- we got to the end... + [ dup 0 eq ] ? + drop + -- dec push new and continue... + else + [ 1 sub 0 before range ]] + +-- Sum up the elements of a block... +:: sum ( L -- s ) [ [ add ] reduce ] + + +-- Meta-word examples (experimental)... + +-- Here is an infix operator example... +-- :: + ( a | b -- c | ) [ \\exec 2 0 _swapN \\exec \\add 2 1 _swapN ] +-- now let\'s make a meta function to make things shorter... +:: infix: ( | op word -- | ) [ + [ + -- format the word definition... + -- NAME WORD -> :: NAME WORD + s2b \\:: -2 before b2s + + -- our template... + -- exec the left side... + [ \\exec 2 0 _swapN + -- exec the right side and arragne the args for WORD... + \\exec \\WORD 2 1 _swapN ] clone + -- get the WORD and insert it into the template above (opsition 8)... + swap WORD replace + + -- push to code / run + 3 0 _swapN + -- swap the arguments and the code to be executed... + ] \\exec 2 2 _swapN ] + +-- Now making a word/2 an infix operator is trivial... +-- NOTE: these are at this point stupid and do not support priorities... +infix: + add +infix: - sub +infix: * mul +infix: / div + +-- these need more thought... +infix: == eq +infix: != ne +infix: > gt +infix: < lt +infix: <= le +infix: >= ge + +-- experimental... +infix: = word! + + +-- Prefix operation definition... +-- Example: +-- :: echo: ( | txt -- | ) [ \\_flip \\print _flip ] +-- swap stack and code untill the block finishes and consumes it's arguments +-- then swap them back... +:: prefix: ( | op word -- | ) [ + [ + -- format the word definition... + -- NAME WORD -> :: NAME WORD + s2b \\:: -2 before b2s + + -- the code template + [ \\_flip \\exec \\WORD _flip ] clone + swap WORD replace + 3 0 _swapN + ] \\exec 2 2 _swapN ] + + + +-- Tests and examples... + +-- Mandatory "hello word" word example... +:: hi ( -- ) [ "Hello World!" print drop ] + +-- Create a block containg a range of numbers from f to t, inclusive... +:: range/2 ( f t -- b ) + [ dup2 swap sub swap . inc range swap [] swap push \\+ 0 before map ] + +-- this will enable us to create ranges via 0 .. 4 +infix: .. range/2 + +--:: range/3 ( a n s -- b ) +-- [ swap range swap [] swap push \\* 0 before map ] + +-- Execute block in a context... +-- synctx: context: +prefix: context: [ ns {} proto! ns! exec ns proto ns! ] + + +` + + +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, 'prep', 'exec' ] + } + + context.code = code + return run(context).stack +} + + + +/********************************************************** RSlang ***/ +/* +var RS_PRE_NAMESPACE = { + // XXX using the ";" here just for the experiment, in the real thing + // if this thing pans out, that is, use indent... (a-la make/Python) + // XXX this reads ahead at the moment, but it must read back... + ';': function(context){ + var line = [] + var code = context.code + var cur = code.splice(0, 1)[0] + while(cur != ';' && code.length > 0){ + line.push(cur) + cur = code.splice(0, 1)[0] + } + + context.code.splice.apply(context.code, [0, 0].concat(line.reverse())) + }, + + '[': PRE_NAMESPACE['['], + 'macro:': PRE_NAMESPACE['macro:'], +} + +RS_CONTEXT = { + stack: [], + code: STARTUP.slice(), + ns: NAMESPACE, + pre_ns: PRE_NAMESPACE, +} + +// NOTE: we use the traditional bootstrap for this... +run(RS_CONTEXT) + +RS_CONTEXT.pre_ns = RS_PRE_NAMESPACE + +function rslang(code, context){ + context = context == null ? RS_CONTEXT : context + + return slang(code, context) +} +//*/ + + + +/********************************************************************** +* vim:set ts=4 sw=4 spell : */