diff --git a/resources/rapydscript/compiler.js b/resources/rapydscript/compiler.js index 74d453ce99..20b00ffaf6 100644 --- a/resources/rapydscript/compiler.js +++ b/resources/rapydscript/compiler.js @@ -6051,6 +6051,8 @@ function OutputStream(options) { }); })(); -rs_baselib_pyj = {"beautifed": {"mixin": "function _$rapyd$_mixin(target, source, overwrite) {\n for (var i in source) {\n if (source.hasOwnProperty(i) && overwrite || typeof target[i] === \"undefined\") {\n target[i] = source[i];\n }\n }\n}", "len": "function len(obj) {\n if (Array.isArray(obj) || typeof obj === \"string\") {\n return obj.length;\n }\n return Object.keys(obj).length;\n}", "range": "function range(start, stop, step) {\n var length, idx, range;\n if (arguments.length <= 1) {\n stop = start || 0;\n start = 0;\n }\n step = arguments[2] || 1;\n length = Math.max(Math.ceil((stop - start) / step), 0);\n idx = 0;\n range = new Array(length);\n while (idx < length) {\n range[idx++] = start;\n start += step;\n }\n return range;\n}", "eslice": "function _$rapyd$_eslice(arr, step, start, end) {\n var isString;\n arr = arr.slice(0);\n if (typeof arr === \"string\" || arr instanceof String) {\n isString = true;\n arr = arr.split(\"\");\n }\n if (step < 0) {\n step = -step;\n arr.reverse();\n if (typeof start !== \"undefined\") {\n start = arr.length - start - 1;\n }\n if (typeof end !== \"undefined\") {\n end = arr.length - end - 1;\n }\n }\n if (typeof start === \"undefined\") {\n start = 0;\n }\n if (typeof end === \"undefined\") {\n end = arr.length;\n }\n arr = arr.slice(start, end).filter(function(e, i) {\n return i % step === 0;\n });\n return isString ? arr.join(\"\") : arr;\n}", "dir": "function dir(item) {\n var arr;\n arr = [];\n for (var i in item) {\n arr.push(i);\n }\n return arr;\n}", "bind": "function _$rapyd$_bind(fn, thisArg) {\n var ret;\n if (fn.orig) {\n fn = fn.orig;\n }\n if (thisArg === false) {\n return fn;\n }\n ret = function() {\n return fn.apply(thisArg, arguments);\n };\n ret.orig = fn;\n return ret;\n}", "extends": "function _$rapyd$_extends(child, parent) {\n child.prototype = Object.create(parent.prototype);\n child.prototype.constructor = child;\n}", "sum": "function sum(arr, start) {\n if (typeof start === \"undefined\") start = 0;\n return arr.reduce(function(prev, cur) {\n return prev + cur;\n }, start);\n}", "setattr": "function setattr(obj, name, value) {\n obj[name] = value;\n}", "reversed": "function reversed(arr) {\n var tmp;\n tmp = arr.slice(0);\n return tmp.reverse();\n}", "in": "function _$rapyd$_in(val, arr) {\n if (Array.isArray(arr) || typeof arr === \"string\") {\n return arr.indexOf(val) !== -1;\n } else {\n if (arr.hasOwnProperty(val)) {\n return true;\n }\n return false;\n }\n}", "getattr": "function getattr(obj, name) {\n return obj[name];\n}", "symbolfor()": "function _$rapyd$_symbolfor_polyfill() {\n if (typeof Symbol === \"function\" && typeof Symbol.for === \"function\") {\n return Symbol.for;\n }\n return function(name) {\n return name + \"-Symbol-\" + \"5d0927e5554349048cf0e3762a228256\";\n };\n}", "hasattr": "function hasattr(obj, name) {\n return name in obj;\n}", "enumerate": "function enumerate(item) {\n var arr;\n arr = [];\n for (var i=0;i\n *\n * Distributed under terms of the BSD license.\n */\n\n\nvar fs = require('fs');\nvar path = require('path');\nvar vm = require('vm');\nvar readline = require('readline');\nvar util = require('util');\nvar RapydScript = require('./compiler');\n\nfunction create_ctx(baselib, show_js, console) {\n var ctx = vm.createContext({'console':console, 'show_js': !!show_js, 'RapydScript':RapydScript, 'require':require});\n\tvm.runInContext(baselib, ctx, {'filename':'baselib.js'});\n\tvar b = vm.runInContext('this', ctx);\n\tfor (var key in b) {\n\t\tif (key.substr(0, 9) == '_$rapyd$_' && key.substr(key.length - 9) == '_polyfill') {\n\t\t\tvar symname = key.substr(9, key.length - 18);\n\t\t\tvm.runInContext('var ' + symname + ' = ' + key + '();', ctx);\n\t\t}\n\t}\n\tRapydScript.AST_Node.warn_function = function() {};\n return ctx;\n}\n\nfunction ansi(code) {\n code = code || 0;\n return '\\033[' + code + 'm';\n}\n\nvar colors = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];\n\nvar homedir = process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];\nvar cachedir = expanduser(process.env.XDG_CACHE_HOME || '~/.cache');\nvar all_keywords = RapydScript.ALL_KEYWORDS.split(' ');\nvar enum_global = \"var global = Function('return this')(); Object.getOwnPropertyNames(global);\";\n\nfunction expanduser(x) {\n if (!x) return x;\n if (x === '~') return homedir;\n if (x.slice(0, 2) != '~/') return path;\n return path.join(homedir, x.slice(2));\n}\n\nfunction colored(string, color, bold) {\n var prefix = [];\n if (bold) prefix.push(ansi(1));\n if (color) prefix.push(ansi(colors.indexOf(color) + 31));\n return prefix.join('') + string + ansi(0);\n}\n\nfunction repl_defaults(options) {\n options = options || {};\n if (!options.input) options.input = process.stdin;\n if (!options.output) options.output = process.stdout;\n if (options.show_js === undefined) options.show_js = true;\n if (!options.ps1) options.ps1 = '>>> ';\n if (!options.ps2) options.ps2 = '... ';\n if (!options.console) options.console = console;\n if (!options.readline) options.readline = readline;\n if (options.terminal === undefined) options.terminal = options.output.isTTY;\n if (options.histfile === undefined) options.histfile = path.join(cachedir, 'rapydscript-repl.history');\n if (options.baselib === undefined) options.baselib = fs.readFileSync(path.join(options.lib_path, 'baselib.js'), 'utf-8');\n if (!options.enum_global) options.enum_global = enum_global;\n \n options.colored = (options.terminal) ? colored : (function (string) { return string; });\n return options;\n}\n\nfunction read_history(options) {\n if (options.histfile) {\n try {\n return fs.readFileSync(options.histfile, 'utf-8').split('\\n');\n } catch (e) { return []; }\n }\n}\n\nfunction write_history(options, history) {\n if (options.histfile) {\n history = history.join('\\n');\n try {\n return fs.writeFileSync(options.histfile, history, 'utf-8');\n } catch (e) {}\n }\n}\n\n// Completion {{{\n\nfunction global_names(ctx, options) {\n try {\n var ans = vm.runInContext(options.enum_global, ctx);\n ans = ans.concat(all_keywords);\n ans.sort();\n var seen = {};\n ans.filter(function (item) { \n if (Object.prototype.hasOwnProperty.call(seen, item)) return false;\n seen[item] = true;\n return true;\n });\n return ans;\n } catch(e) {\n console.log(e.stack || e.toString());\n }\n return [];\n}\n\nfunction object_names(obj, prefix) {\n if (obj === null || obj === undefined) return [];\n var groups = [], prefix_len = prefix.length, p;\n\n function prefix_filter(name) { return (prefix_len) ? (name.substr(0, prefix_len) === prefix) : true; }\n\n function add(o) {\n var items = Object.getOwnPropertyNames(o).filter(prefix_filter);\n if (items.length) groups.push(items);\n }\n\n if (typeof obj === 'object' || typeof obj === 'function') {\n add(obj);\n p = Object.getPrototypeOf(obj);\n } else p = obj.constructor ? obj.constructor.prototype : null; \n\n // Walk the prototype chain\n try {\n var sentinel = 5;\n while (p !== null && sentinel > 0) {\n add(p);\n p = Object.getPrototypeOf(p);\n // Circular refs possible? Let's guard against that.\n sentinel--;\n }\n } catch (e) {\n // console.error(\"completion error walking prototype chain:\" + e);\n }\n if (!groups.length) return [];\n var seen = {}, ans = [];\n function uniq(name) {\n if (Object.prototype.hasOwnProperty.call(seen, name)) return false;\n seen[name] = true;\n return true;\n }\n for (var i = 0; i < groups.length; i++) {\n var group = groups[i];\n group.sort();\n ans = ans.concat(group.filter(uniq));\n ans.push(''); // group separator\n\n }\n while (ans.length && ans[ans.length - 1] === '') ans.pop();\n return ans;\n}\n\nfunction prefix_matches(prefix, items) {\n var len = prefix.length;\n var ans = items.filter(function(item) { return item.substr(0, len) === prefix; });\n ans.sort();\n return ans;\n}\n\nfunction find_completions(line, ctx, options) {\n try {\n t = RapydScript.tokenizer(line, '');\n } catch(e) { return []; }\n var tokens = [], token;\n while (true) {\n try {\n token = t();\n } catch (e) { return []; }\n if (token.type === 'eof') break;\n if (token.type === 'punc' && '(){},;:'.indexOf(token.value) > -1)\n tokens = [];\n tokens.push(token);\n }\n if (!tokens.length) {\n // New line or trailing space\n return [global_names(ctx, options), ''];\n }\n var last_tok = tokens[tokens.length - 1];\n if (last_tok.value === '.' || (last_tok.type === 'name' && RapydScript.IDENTIFIER_PAT.test(last_tok.value))) {\n last_tok = last_tok.value;\n if (last_tok === '.') {\n tokens.push({'value':''});\n last_tok = '';\n }\n if (tokens.length > 1 && tokens[tokens.length - 2].value === '.') {\n // A compound expression\n var prefix = '', result;\n tokens.slice(0, tokens.length - 2).forEach(function (tok) { prefix += tok.value; });\n if (prefix) {\n try {\n result = vm.runInContext(prefix, ctx, {'displayErrors':false});\n } catch(e) { return []; }\n return [object_names(result, last_tok), last_tok];\n }\n } else {\n return [prefix_matches(last_tok, global_names(ctx, options)), last_tok];\n }\n }\n return [];\n}\n// }}}\n\nmodule.exports = function(options) {\n\tvar output_options = {'omit_baselib':true, 'write_name':false, 'private_scope':false, 'beautify':true};\n options = repl_defaults(options);\n options.completer = completer;\n var rl = options.readline.createInterface(options);\n\tps1 = options.colored(options.ps1, 'green');\n\tps2 = options.colored(options.ps2, 'yellow');\n\tvar ctx = create_ctx(options.baselib, options.show_js, options.console);\n var buffer = [];\n var more = false;\n var LINE_CONTINUATION_CHARS = ':\\\\';\n var toplevel;\n\n options.console.log(options.colored('Welcome to the RapydScript REPL! Press Ctrl+C then Ctrl+D to quit.', 'green', true));\n if (options.show_js)\n options.console.log(options.colored('Use show_js=False to stop the REPL from showing the compiled JavaScript.', 'green', true));\n else\n options.console.log(options.colored('Use show_js=True to have the REPL show the compiled JavaScript before executing it.', 'green', true));\n options.console.log();\n\n function resetbuffer() { buffer = []; }\n\n function completer(line) {\n return find_completions(line, ctx, options);\n }\n\n function prompt() {\n var lw = '';\n if (more && buffer.length) {\n var prev_line = buffer[buffer.length - 1];\n if (prev_line.trimRight().substr(prev_line.length - 1) == ':') lw = ' ';\n prev_line = prev_line.match(/^\\s+/);\n if (prev_line) lw += prev_line;\n }\n rl.setPrompt((more) ? ps2 : ps1);\n if (rl.sync_prompt) rl.prompt(lw);\n else {\n rl.prompt();\n if (lw) rl.write(lw);\n }\n }\n\n function runjs(js) {\n var result;\n if (vm.runInContext('show_js', ctx)) {\n options.console.log(options.colored('---------- Compiled JavaScript ---------', 'green', true));\n options.console.log(js);\n options.console.log(options.colored('---------- Running JavaScript ---------', 'green', true));\n }\n try {\n // Despite what the docs say node does not actually output any errors by itself\n // so, in case this bug is fixed alter, we turn it off explicitly.\n result = vm.runInContext(js, ctx, {'filename':'', 'displayErrors':false});\n } catch(e) {\n if (e.stack) options.console.error(e.stack);\n else options.console.error(e.toString());\n }\n\n if (result !== undefined) {\n options.console.log(util.inspect(result, {'colors':options.terminal}));\n }\n }\n\n function compile_source(source, output_options) {\n var classes = (toplevel) ? toplevel.classes : undefined;\n try {\n toplevel = RapydScript.parse(source, {\n 'filename':'',\n 'readfile': fs.readFileSync,\n 'basedir': process.cwd(),\n 'libdir': options.lib_path,\n 'classes': classes\n });\n } catch(e) {\n if (e.is_eof && e.line == buffer.length && e.col > 0) return true;\n if (e.message && e.line !== undefined) options.console.log(e.line + ':' + e.col + ':' + e.message);\n else options.console.log(e.stack || e.toString());\n return false;\n }\n var output = RapydScript.OutputStream(output_options);\n toplevel.print(output);\n output = output.toString();\n if (classes) {\n var exports = {};\n toplevel.exports.forEach(function (name) { exports[name] = true; });\n Object.getOwnPropertyNames(classes).forEach(function (name) {\n if (!exports.hasOwnProperty(name) && !toplevel.classes.hasOwnProperty(name))\n toplevel.classes[name] = classes[name];\n });\n }\n runjs(output);\n return false;\n }\n\n function push(line) {\n buffer.push(line);\n var ll = line.trimRight();\n if (ll && LINE_CONTINUATION_CHARS.indexOf(ll.substr(ll.length - 1)) > -1)\n return true;\n var source = buffer.join('\\n');\n if (!source.trim()) { resetbuffer(); return false; }\n var incomplete = compile_source(source, output_options);\n if (!incomplete) resetbuffer();\n return incomplete;\n }\n\n\trl.on('line', function(line) {\n if (more) {\n // We are in a block \n var line_is_empty = !line.trimLeft();\n if (line_is_empty && buffer.length && !buffer[buffer.length - 1].trimLeft()) {\n // We have two empty lines, evaluate the block\n more = push(line.trimLeft());\n } else buffer.push(line);\n } else more = push(line); // Not in a block, evaluate line\n\t\tprompt();\n\t})\n\t\n\t.on('close', function() {\n\t\toptions.console.log('Bye!');\n if (rl.history) write_history(options, rl.history);\n\t\tprocess.exit(0);\n\t})\n\n\t.on('SIGINT', function() {\n rl.clearLine();\n\t\toptions.console.log('Keyboard Interrupt');\n resetbuffer();\n more = false;\n\t\tprompt();\n\t})\n\n\t.on('SIGCONT', function() {\n\t\tprompt();\n\t});\n\n rl.history = read_history(options);\n\tprompt();\n};\n"; rs_package_version = "0.3.4"; diff --git a/src/calibre/utils/rapydscript.py b/src/calibre/utils/rapydscript.py index 720e6e0fc1..3ae4ae7d1a 100644 --- a/src/calibre/utils/rapydscript.py +++ b/src/calibre/utils/rapydscript.py @@ -6,12 +6,15 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import os, json, sys, errno, re, atexit +import os, json, sys, re, atexit, errno from threading import local from functools import partial +from threading import Thread +from Queue import Queue -from calibre.constants import cache_dir, iswindows -from calibre.utils.terminal import ANSIStream, colored +from duktape import Context, JSError, to_python +from calibre.constants import cache_dir +from calibre.utils.terminal import ANSIStream COMPILER_PATH = 'rapydscript/compiler.js' @@ -57,7 +60,6 @@ def compile_baselib(ctx, baselib, beautify=True): return {k:doit(v) for k, v in sorted(baselib.iteritems())} def update_rapydscript(): - from duktape import Context, JSError vm_js = ''' exports.createContext = function(x) { x.AST_Node = {}; return x; } exports.runInContext = function() { return null; } @@ -88,10 +90,12 @@ def update_rapydscript(): ctx = Context() ctx.eval(data.decode('utf-8')) baselib = {'beautifed': compile_baselib(ctx, baselib), 'minified': compile_baselib(ctx, baselib, False)} + repl = open(os.path.join(base, 'tools', 'repl.js'), 'rb').read() with open(P(COMPILER_PATH, allow_user_override=False), 'wb') as f: f.write(data) f.write(b'\n\nrs_baselib_pyj = ' + json.dumps(baselib) + b';') + f.write(b'\n\nrs_repl_js = ' + json.dumps(repl) + b';') f.write(b'\n\nrs_package_version = ' + json.dumps(package['version']) + b';\n') # }}} @@ -118,7 +122,6 @@ def to_dict(obj): def compiler(): c = getattr(tls, 'compiler', None) if c is None: - from duktape import Context c = tls.compiler = Context(base_dirs=(P('rapydscript', allow_user_override=False),)) c.eval(P(COMPILER_PATH, data=True, allow_user_override=False).decode('utf-8'), fname='rapydscript-compiler.js') c.g.current_output_options = {} @@ -164,20 +167,13 @@ def leading_whitespace(line): def format_error(data): return ':'.join(map(type(''), (data['file'], data['line'], data['col'], data['message']))) -class Repl(object): +class Repl(Thread): LINE_CONTINUATION_CHARS = r'\:' + daemon = True def __init__(self, ps1='>>> ', ps2='... ', show_js=False, libdir=None): - from duktape import Context, undefined, JSError, to_python - self.lines = [] - self.libdir = libdir - self.ps1, self.ps2 = ps1, ps2 - if not iswindows: - self.ps1, self.ps2 = colored(self.ps1, fg='green'), colored(self.ps2, fg='green') - self.ctx = Context() - self.ctx.g.show_js = show_js - self.undefined = undefined + Thread.__init__(self, name='RapydScriptREPL') self.to_python = to_python self.JSError = JSError self.enc = getattr(sys.stdin, 'encoding', None) or 'utf-8' @@ -187,28 +183,100 @@ class Repl(object): except ImportError: pass self.output = ANSIStream(sys.stdout) - c = compiler() - baselib = dict(dict(c.g.rs_baselib_pyj)['beautifed']) + self.to_repl = Queue() + self.from_repl = Queue() + self.ps1, self.ps2 = ps1, ps2 + self.show_js, self.libdir = show_js, libdir + self.prompt = '' + self.completions = None + self.start() + + def init_ctx(self): + cc = ''' + exports.AST_Node = AST_Node; + exports.ALL_KEYWORDS = ALL_KEYWORDS; + exports.tokenizer = tokenizer; + exports.parse = parse; + exports.OutputStream = OutputStream; + exports.IDENTIFIER_PAT = IDENTIFIER_PAT; + ''' + self.prompt = self.ps1 + readline = ''' + exports.createInterface = function(options) { rl.completer = options.completer; return rl; } + ''' + self.ctx = Context(builtin_modules={'readline':readline, 'compiler':cc}) + self.ctx.g.Duktape.write = self.output.write + self.ctx.eval(r'''console = { log: function() { Duktape.write(Array.prototype.slice.call(arguments).join(' ') + '\n');}}; + console['error'] = console['log'];''') + cc = P(COMPILER_PATH, data=True, allow_user_override=False) + self.ctx.eval(cc) + baselib = dict(dict(self.ctx.g.rs_baselib_pyj)['beautifed']) baselib = '\n\n'.join(baselib.itervalues()) - self.ctx.eval(baselib) + self.ctx.eval('module = {}') + self.ctx.eval(self.ctx.g.rs_repl_js, fname='repl.js') + self.ctx.g.repl_options = { + 'baselib': baselib, 'show_js': self.show_js, + 'histfile':False, + 'input':True, 'output':True, 'ps1':self.ps1, 'ps2':self.ps2, + 'terminal':self.output.isatty, + 'enum_global': 'Object.keys(this)', + 'lib_path': self.libdir or os.path.dirname(P(COMPILER_PATH)) # TODO: Change this to load pyj files from the src code + } - def resetbuffer(self): - self.lines = [] + def run(self): + self.init_ctx() + rl = None - def prints(self, *args, **kwargs): - sep = kwargs.get('sep', ' ') - for x in args: - self.output.write(type('')(x)) - if sep and x is not args[-1]: - self.output.write(sep) - end = kwargs.get('end', '\n') - if end: - self.output.write(end) + def set_prompt(p): + self.prompt = p + + def prompt(lw): + self.from_repl.put(to_python(lw)) + + self.ctx.g.set_prompt = set_prompt + self.ctx.g.prompt = prompt + + self.ctx.eval(''' + listeners = {}; + rl = { + setPrompt:set_prompt, + write:Duktape.write, + clearLine:function() {}, + on: function(ev, cb) { listeners[ev] = cb; return rl; }, + prompt: prompt, + sync_prompt: true, + send_line: function(line) { listeners['line'](line); }, + send_interrupt: function() { listeners['SIGINT'](); }, + close: function() {listeners['close'](); } + }; + ''') + rl = self.ctx.g.rl + self.ctx.eval('module.exports(repl_options)') + while True: + ev, line = self.to_repl.get() + try: + if ev == 'SIGINT': + self.output.write('\n') + rl.send_interrupt() + elif ev == 'line': + rl.send_line(line) + else: + val = rl.completer(line) + val = to_python(val) + self.from_repl.put(val[0]) + except Exception as e: + if 'JSError' in e.__class__.__name__: + e = JSError(e) # A bare JSError + print (e.stack or e.message, file=sys.stderr) + else: + import traceback + traceback.print_exc() + + for i in xrange(100): + # Do this many times to ensure we dont deadlock + self.from_repl.put(None) def __call__(self): - self.prints(colored('Welcome to the RapydScript REPL! Press Ctrl+D to quit.\n' - 'Use show_js = True to have the REPL print out the' - ' compiled javascript before executing it.\n', bold=True)) if hasattr(self, 'readline'): history = os.path.join(cache_dir(), 'pyj-repl-history.txt') self.readline.parse_and_bind("tab: complete") @@ -218,80 +286,38 @@ class Repl(object): if e.errno != errno.ENOENT: raise atexit.register(partial(self.readline.write_history_file, history)) - more = False - while True: + + def completer(text, num): + if self.completions is None: + self.to_repl.put(('complete', text)) + self.completions = self.from_repl.get() + if self.completions is None: + return None try: - prompt = self.ps2 if more else self.ps1 - lw = '' - if more and self.lines: - if self.lines: - if self.lines[-1][-1:] == ':': - lw = ' ' * 4 # autoindent - lw = leading_whitespace(self.lines[-1]) + lw - if hasattr(self, 'readline'): - self.readline.set_pre_input_hook(lambda:(self.readline.insert_text(lw), self.readline.redisplay())) - else: - prompt += lw - try: - line = raw_input(prompt).decode(self.enc) - except EOFError: - self.prints() - break - else: - if more and line.lstrip(): - self.lines.append(line) - continue - if more and not line.lstrip(): - line = line.lstrip() - more = self.push(line) - except KeyboardInterrupt: - self.prints("\nKeyboardInterrupt") - self.resetbuffer() - more = False + return self.completions[num] + except (IndexError, TypeError, AttributeError, KeyError): + self.completions = None - def push(self, line): - self.lines.append(line) - rl = line.rstrip() - if rl and rl[-1] in self.LINE_CONTINUATION_CHARS: - return True - source = '\n'.join(self.lines) - more = self.runsource(source) - if not more: - self.resetbuffer() - return more + if hasattr(self, 'readline'): + self.readline.set_completer(completer) - def runsource(self, source): - try: - js = compile_pyj(source, filename='', private_scope=False, libdir=self.libdir, omit_baselib=True, write_name=False) - except PYJError as e: - for data in e.errors: - msg = data.get('message') or '' - if data['line'] == len(self.lines) and data['col'] > 0 and ( - 'Unexpected token: eof' in msg or 'Unterminated regular expression' in msg): - return True + while True: + lw = self.from_repl.get() + if lw is None: + raise SystemExit(1) + q = self.prompt + if hasattr(self, 'readline'): + self.readline.set_pre_input_hook(lambda:(self.readline.insert_text(lw), self.readline.redisplay())) else: - for e in e.errors: - self.prints(format_error(e)) - except self.JSError as e: - self.prints(e.message) - except Exception as e: - self.prints(e) - else: - self.runjs(js) - return False + q += lw + try: + line = raw_input(q) + self.to_repl.put(('line', line)) + except EOFError: + return + except KeyboardInterrupt: + self.to_repl.put(('SIGINT', None)) - def runjs(self, js): - if self.ctx.g.show_js: - self.prints(colored('Compiled Javascript:', fg='green'), js, sep='\n') - try: - result = self.ctx.eval(js, fname='line') - except self.JSError as e: - self.prints(e.message) - except Exception as e: - self.prints(str(e)) - else: - if result is not self.undefined: - self.prints(colored(repr(self.to_python(result)), bold=True)) # }}} def main(args=sys.argv): @@ -311,7 +337,6 @@ def main(args=sys.argv): if sys.stdin.isatty(): Repl(show_js=args.show_js, libdir=libdir)() else: - from duktape import JSError try: enc = getattr(sys.stdin, 'encoding', 'utf-8') or 'utf-8' data = compile_pyj(sys.stdin.read().decode(enc), libdir=libdir, private_scope=not args.no_private_scope, omit_baselib=args.omit_baselib) diff --git a/src/duktape/__init__.py b/src/duktape/__init__.py index 50b917f2ab..a88e69eaf5 100644 --- a/src/duktape/__init__.py +++ b/src/duktape/__init__.py @@ -19,8 +19,25 @@ if err: del err Context_, undefined = dukpy.Context, dukpy.undefined +fs = ''' +exports.readFileSync = Duktape.readfile; +''' +vm = ''' +exports.createContext = Duktape.create_context; +exports.runInContext = Duktape.run_in_context; +''' +path = ''' +exports.join = function () { return arguments[0] + '/' + arguments[1]; } +''' +util = ''' +exports.inspect = function(x) { return x.toString(); }; +''' + def load_file(base_dirs, builtin_modules, name): ans = builtin_modules.get(name) + if ans is not None: + return ans + ans = {'fs':fs, 'vm':vm, 'path':path, 'util':util}.get(name) if ans is not None: return ans if not name.endswith('.js'): @@ -60,12 +77,15 @@ class Function(object): return self.func(*args, **kwargs) def to_python(x): - if isinstance(x, (numbers.Number, type(''), bytes, bool)): - if isinstance(x, type('')): - x = x.encode('utf-8') - if isinstance(x, numbers.Integral): - x = int(x) - return x + try: + if isinstance(x, (numbers.Number, type(''), bytes, bool)): + if isinstance(x, type('')): + x = x.encode('utf-8') + if isinstance(x, numbers.Integral): + x = int(x) + return x + except TypeError: + pass name = x.__class__.__name__ if name == 'Array proxy': return [to_python(y) for y in x] @@ -93,6 +113,21 @@ class JSError(Exception): Exception.__init__(self, type('')(e)) self.name = self.js_message = self.fileName = self.lineNumber = self.stack = None +contexts = {} + +def create_context(base_dirs, *args): + data = to_python(args[0]) if args else {} + ctx = Context(base_dirs=base_dirs) + for k, val in data.iteritems(): + setattr(ctx.g, k, val) + key = id(ctx) + contexts[key] = ctx + return key + +def run_in_context(code, ctx, options=None): + ans = contexts[ctx].eval(code) + return to_python(ans) + class Context(object): def __init__(self, base_dirs=(), builtin_modules=None): @@ -100,59 +135,75 @@ class Context(object): self.g = self._ctx.g self.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),), builtin_modules or {}) self.g.Duktape.pyreadfile = readfile + self.g.Duktape.create_context = partial(create_context, base_dirs) + self.g.Duktape.run_in_context = run_in_context + self.g.Duktape.cwd = os.getcwdu self.eval(''' - console = { log: function() { print(Array.prototype.join.call(arguments, ' ')); } }; - Duktape.modSearch = function (id, require, exports, module) { return Duktape.load_file(id); } - if (!String.prototype.trim) { - (function() { - // Make sure we trim BOM and NBSP - var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; - String.prototype.trim = function() { - return this.replace(rtrim, ''); - }; - })(); - }; - if (!String.prototype.trimLeft) { - (function() { - // Make sure we trim BOM and NBSP - var rtrim = /^[\s\uFEFF\xA0]+/g; - String.prototype.trimLeft = function() { - return this.replace(rtrim, ''); - }; - })(); - }; - if (!String.prototype.trimRight) { - (function() { - // Make sure we trim BOM and NBSP - var rtrim = /[\s\uFEFF\xA0]+$/g; - String.prototype.trimRight = function() { - return this.replace(rtrim, ''); - }; - })(); - }; - if (!String.prototype.startsWith) { - String.prototype.startsWith = function(searchString, position) { - position = position || 0; - return this.indexOf(searchString, position) === position; + console = { + log: function() { print(Array.prototype.join.call(arguments, ' ')); }, + error: function() { print(Array.prototype.join.call(arguments, ' ')); }, + debug: function() { print(Array.prototype.join.call(arguments, ' ')); } + }; + + Duktape.modSearch = function (id, require, exports, module) { return Duktape.load_file(id); } + + if (!String.prototype.trim) { + (function() { + // Make sure we trim BOM and NBSP + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + String.prototype.trim = function() { + return this.replace(rtrim, ''); }; - } - if (!String.prototype.endsWith) { - String.prototype.endsWith = function(searchString, position) { - var subjectString = this.toString(); - if (position === undefined || position > subjectString.length) { - position = subjectString.length; - } - position -= searchString.length; - var lastIndex = subjectString.indexOf(searchString, position); - return lastIndex !== -1 && lastIndex === position; + })(); + }; + if (!String.prototype.trimLeft) { + (function() { + // Make sure we trim BOM and NBSP + var rtrim = /^[\s\uFEFF\xA0]+/g; + String.prototype.trimLeft = function() { + return this.replace(rtrim, ''); }; - } - Duktape.readfile = function(path, encoding) { - var x = Duktape.pyreadfile(path, encoding); - var data = x[0]; var errcode = x[1]; var errmsg = x[2]; - if (errmsg !== null) throw {code:errcode, message:errmsg}; - return data; - } + })(); + }; + if (!String.prototype.trimRight) { + (function() { + // Make sure we trim BOM and NBSP + var rtrim = /[\s\uFEFF\xA0]+$/g; + String.prototype.trimRight = function() { + return this.replace(rtrim, ''); + }; + })(); + }; + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(searchString, position) { + position = position || 0; + return this.indexOf(searchString, position) === position; + }; + } + if (!String.prototype.endsWith) { + String.prototype.endsWith = function(searchString, position) { + var subjectString = this.toString(); + if (position === undefined || position > subjectString.length) { + position = subjectString.length; + } + position -= searchString.length; + var lastIndex = subjectString.indexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + } + Duktape.readfile = function(path, encoding) { + var x = Duktape.pyreadfile(path, encoding); + var data = x[0]; var errcode = x[1]; var errmsg = x[2]; + if (errmsg !== null) throw {code:errcode, message:errmsg}; + return data; + } + + process = { + 'platform': 'duktape', + 'env': {'HOME': '_HOME_'}, + 'exit': function() {}, + 'cwd':Duktape.cwd + } ''')