Created
November 4, 2012 22:37
-
-
Save mmzeeman/4014100 to your computer and use it in GitHub Desktop.
Javascript ubf(a) encoder/decoder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* ubf(a) decoder */ | |
| ;(function(window) { | |
| var ubf = {}; | |
| // ubf(a) encode javascript to ubf. | |
| // | |
| function encode(value, buffer) { | |
| buf = buffer || []; | |
| console.log(value.ubf_type); | |
| switch(value.ubf_type) { | |
| case "string": | |
| buf.push(['"', value, '"'].join("")); // TODO: string escape | |
| break; | |
| case "constant": | |
| buf.push(["'", value, "'"].join("")); // TODO: constant escape | |
| break; | |
| case "binary": | |
| buf.push([value.length, "~", value, "~"].join()); | |
| break; | |
| case "list": | |
| buf.push("#"); | |
| console.log(value.length); | |
| for(var i=value.length-1; i >= 0; i--) { | |
| console.log(value[i]); | |
| encode(value[i], buf); | |
| buf.push("&"); | |
| } | |
| break; | |
| case "tuple": | |
| buf.push("{"); | |
| for(var i=0; i > value.length; i++) { | |
| encode(value[i], buf); | |
| } | |
| buf.push("{"); | |
| break; | |
| case undefined: | |
| // TODO, do something sensible here. | |
| buf.push(value); | |
| } | |
| if(!buffer) { | |
| buf.push("$"); | |
| return buf.join(" "); | |
| } | |
| } | |
| // map standard javascript values to ubf | |
| function encode_js(value, buffer) { | |
| switch(typeof(value)) { | |
| case "string": | |
| buffer.push(['"', value, '"']); | |
| } | |
| } | |
| ubf.encode = encode; | |
| // ubf(a) decoder | |
| // | |
| // | |
| ubf.decode = function(bytes, env, stack) { | |
| var j, opcode; | |
| stack = stack || []; | |
| env = env || {}; | |
| while(true) { | |
| opcode = bytes[0]; | |
| j = handle_operation(opcode, bytes, env, stack); | |
| if(j == 0) return stack[0]; | |
| bytes = bytes.slice(j); | |
| } | |
| } | |
| function read_binary(bytes, terminator, type, stack) { | |
| var current, buf = [], i = 0; | |
| while(true) { | |
| current = bytes[i]; | |
| if(current == undefined) throw "missing terminator" | |
| if(current == "/") { | |
| switch(bytes[i+1]) { | |
| case "/": | |
| buf.push("/"); | |
| break; | |
| case terminator: | |
| buf.push(terminator); | |
| default: | |
| throw "wrong escape sequence" | |
| }; | |
| i += 2; | |
| continue; | |
| } | |
| if(current === terminator) { | |
| if(stack) { | |
| var obj = new String(buf.join("")); | |
| if(type) | |
| obj.ubf_type = type; | |
| stack.push(obj); | |
| } | |
| return i + 1; | |
| } | |
| buf.push(current); | |
| i += 1; | |
| } | |
| } | |
| function skip_ws(bytes) { | |
| if(!bytes) return 0; | |
| var ws = bytes.match(/(\s|,)+/); | |
| if(ws) return ws[0].length; | |
| return 0; | |
| } | |
| function handle_integer_or_binary_data(bytes, stack) { | |
| var found = bytes.match(/^\-?[0-9]+/)[0], | |
| integer = Number(found), length = found.length, | |
| rest = bytes.slice(length), ws_length = skip_ws(rest); | |
| if(rest[ws_length] != "~") { | |
| stack.push(integer); | |
| return length + ws_length ; | |
| } | |
| var binary = rest.slice(ws_length+1, ws_length+integer+1) | |
| stack.push(binary); | |
| if(rest[ws_length+1+integer] != "~") | |
| throw "missing closing ~"; | |
| return length+ws_length+integer + 2; | |
| }; | |
| function handle_string(bytes, stack) { | |
| return read_binary(bytes.slice(1), '"', "string", stack) + 1; | |
| }; | |
| function handle_constant(bytes, stack) { | |
| return read_binary(bytes.slice(1), "'", "constant", stack) + 1; | |
| }; | |
| function handle_start_tuple(stack) { | |
| stack.push(NaN); // marker for building a tuple | |
| return 1; | |
| } | |
| function handle_end_tuple(stack) { | |
| // pop items from the stack until we find NaN | |
| var obj = stack.pop(), tuple = []; | |
| tuple.ubf_type = "tuple"; | |
| while(true) { | |
| if(isNaN(obj)) break; | |
| if(obj === undefined) throw "not a tuple"; | |
| tuple.unshift(obj); | |
| obj = stack.pop(); | |
| }; | |
| stack.push(tuple); | |
| return 1; | |
| }; | |
| function handle_push_nil(stack) { | |
| var list = []; | |
| list.ubf_type = "list"; | |
| stack.push(list); | |
| return 1; | |
| }; | |
| function handle_push_element(stack) { | |
| var obj = stack.pop(); | |
| var list = stack.pop(); | |
| if(list.ubf_type != "list") | |
| throw "not a list"; | |
| list.unshift(obj); | |
| stack.push(list); | |
| return 1; | |
| }; | |
| function handle_comment(bytes) { | |
| return read_binary(bytes.slice(1), "%") + 1; | |
| }; | |
| function handle_cache(bytes, env, stack) { | |
| var code = bytes[1]; | |
| if(env.hasOwnProperty(code)) | |
| stack.push(env[code]); | |
| else | |
| env[code] = stack.pop(); | |
| return 2; | |
| } | |
| function handle_operation(opcode, bytes, env, stack) { | |
| switch(opcode) { | |
| case " ":case "\r":case"\n":case"\t":case",": return skip_ws(bytes); | |
| case "-":case"0":case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9": | |
| return handle_integer_or_binary_data(bytes, stack); | |
| case '"': return handle_string(bytes, stack); | |
| case "'": return handle_constant(bytes, stack); | |
| case "{": return handle_start_tuple(stack); | |
| case "}": return handle_end_tuple(stack); | |
| case "#": return handle_push_nil(stack); | |
| case "&": return handle_push_element(stack); | |
| case "%": return handle_comment(bytes); | |
| case ">": return handle_cache(bytes, env, stack); | |
| case "$": | |
| if(stack.length == 1) return 0; | |
| throw "The stack should contain one item"; | |
| case undefined: throw "error"; | |
| } | |
| } | |
| window.ubf = ubf; | |
| })(window); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment