mirror of
				https://github.com/flynx/Course-JavaScript.git
				synced 2025-10-30 11:30:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1119 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1119 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| /**********************************************************************
 | |
| * 
 | |
| *
 | |
| *
 | |
| **********************************************************************/
 | |
| 
 | |
| /* 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!
 | |
| 
 | |
| 
 | |
| 
 | |
| -------------------------------------------------------------------------------
 | |
| 
 | |
|  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 (not yet fully implemented)
 | |
| 
 | |
|  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 a complete word-set for work with lists/blocks
 | |
|  TODO: add a complete word-set for work with dicts/namespaces
 | |
|  TODO: add topological comparison/diff
 | |
| 
 | |
| 
 | |
| 
 | |
| -----------------------------------------------------------------------------
 | |
| 
 | |
|  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.
 | |
| 
 | |
| 
 | |
| 
 | |
| -------------------------------------------------------------------------------
 | |
| 
 | |
|  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:
 | |
| 
 | |
|  Create a variable word o and p and set them to empty objects...
 | |
| 
 | |
| 		ns
 | |
| 			o {} item!
 | |
| 			p {} item!
 | |
| 		.
 | |
| 
 | |
|  Set attribute (key-value pair) on object...
 | |
| 
 | |
| 		o x 123 item!
 | |
| 				-> o
 | |
| 
 | |
|  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 sorthands...
 | |
| 	. ( 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
 | |
| 
 | |
| 	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 :                                                */
 |