mirror of
https://github.com/flynx/Slang.git
synced 2025-10-28 10:10:07 +00:00
1119 lines
27 KiB
JavaScript
1119 lines
27 KiB
JavaScript
/**********************************************************************
|
|
*
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
|
|
/* XXX for some odd reason this breaks the interpreter...
|
|
Array.prototype.toString = function(){
|
|
return '[ ' + this.join(', ') + ' ]'
|
|
}
|
|
*/
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
var run =
|
|
function(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 }
|
|
|
|
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)
|
|
|
|
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()
|
|
return a ?
|
|
b
|
|
: a },
|
|
// a b -- c
|
|
'or': function(context){
|
|
var b = context.stack.pop()
|
|
var a = context.stack.pop()
|
|
return a ?
|
|
a
|
|
: 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: :: <ident> <value>
|
|
:: ( | 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: <block>
|
|
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
|
|
|
|
code = typeof(code) == typeof('abc') ?
|
|
[ '\\', code, 'lex', 'prep', 'exec' ]
|
|
: [ 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 : */
|