mirror of
https://github.com/flynx/Slang.git
synced 2025-10-29 10:40:07 +00:00
add a small stack language called Slang...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
13b436456d
commit
b99fc69bd2
11
Slang/slang.html
Executable file
11
Slang/slang.html
Executable file
@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<script src="slang.js"></script>
|
||||
<body>
|
||||
</body>
|
||||
<script>
|
||||
document.body.innerHTML='<pre>'+BOOTSTRAP
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')+'</pre>'
|
||||
</script>
|
||||
</html>
|
||||
504
Slang/slang.js
Executable file
504
Slang/slang.js
Executable file
@ -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: :: <ident> <block>
|
||||
// --
|
||||
'::': 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'+
|
||||
'-- [ <cond> ] ? [ <main> ] \n'+
|
||||
'-- [ <cond> ] ? [ <main> ] else [ <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 : */
|
||||
Loading…
x
Reference in New Issue
Block a user