Skip to content

Commit

Permalink
same exact codebase now runs in the browser as in node
Browse files Browse the repository at this point in the history
  • Loading branch information
atsepkov committed Oct 13, 2016
1 parent 41b872d commit 0d393d8
Show file tree
Hide file tree
Showing 9 changed files with 487 additions and 104 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ Now simply invoking the file (assuming it has execute permissions on it) should

Getting Started
---------------
As you read the following sections, I suggest you start a RapydScript shell (by typing `rapydscript` without arguments) and follow along. You'll be able to see both, the generated JavaScript, and the output produced by the given RapydScript command.
As you read the following sections, I suggest you start a RapydScript shell (by typing `rapydscript` without arguments in your terminal) and follow along. You'll be able to see both, the generated JavaScript, and the output produced by the given RapydScript command.

You can also run the compiler in the browser. Just add a `script` tag linking back to `lib/rapydscript.js` and invoke the compiler as `rapydscript.compile(stringOfCode, options)`.

Like JavaScript, RapydScript can be used to create anything from a quick function to a complex web-app. RapydScript can access anything regular JavaScript can, in the same manner. Let's say we want to write a function that greets us with a "Hello World" pop-up. The following code will do it:

Expand Down
180 changes: 109 additions & 71 deletions lib/rapydscript.js

Large diffs are not rendered by default.

27 changes: 12 additions & 15 deletions lib/self.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,8 @@ path = require("path");
fs = require("fs");
rapydscript = require("../lib/rapydscript");
module.exports = function compile_self(base_path, src_path, lib_path, start_time) {
var ՐՏitr9, ՐՏidx9, ՐՏitr10, ՐՏidx10;
var compiled, baselib, options, file, filename;
var ՐՏitr8, ՐՏidx8, ՐՏitr9, ՐՏidx9, ՐՏitr10, ՐՏidx10;
var compiled, baselib, options, src, ast, key, output, file, filename;
compiled = {};
baselib = rapydscript.parse_baselib(src_path, true);
options = {
Expand All @@ -407,19 +407,16 @@ module.exports = function compile_self(base_path, src_path, lib_path, start_time
libdir: path.join(src_path, "lib"),
baselib: baselib
};
(function() {
var ՐՏitr8, ՐՏidx8;
var src, ast, key, output;
src = fs.readFileSync(path.join(src_path, "baselib.pyj"), "utf8");
ast = rapydscript.parse(src, options);
ՐՏitr8 = ՐՏ_Iterable(baselib);
for (ՐՏidx8 = 0; ՐՏidx8 < ՐՏitr8.length; ՐՏidx8++) {
key = ՐՏitr8[ՐՏidx8];
ast.baselib[key] = true;
}
output = rapydscript.output(ast, options);
compiled.baselib = output.toString();
})();
src = fs.readFileSync(path.join(src_path, "baselib.pyj"), "utf8");
ast = rapydscript.parse(src, options);
ՐՏitr8 = ՐՏ_Iterable(baselib);
for (ՐՏidx8 = 0; ՐՏidx8 < ՐՏitr8.length; ՐՏidx8++) {
key = ՐՏitr8[ՐՏidx8];
ast.baselib[key] = true;
}
output = rapydscript.output(ast, options);
compiled.baselib = output.toString();
fs.writeFileSync(path.join(src_path, "_baselib.pyj"), "BASELIB = '''" + src.replace(/\\/g, "\\\\") + "'''", "utf8");
function compile(file) {
var filepath;
filepath = path.join(src_path, file + ".pyj");
Expand Down
305 changes: 305 additions & 0 deletions src/_baselib.pyj
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
BASELIB = '''"""
**********************************************************************

A RapydScript to JavaScript compiler.
https://github.com/atsepkov/RapydScript

-------------------------------- (C) ---------------------------------

Author: Alexander Tsepkov
<atsepkov@pyjeon.com>
http://www.pyjeon.com

Distributed under BSD license:
Copyright 2013 (c) Alexander Tsepkov <atsepkov@pyjeon.com>

**********************************************************************
"""


# for convenience we'll use a convention here that will work as follows:
#
# if function is named, assume we'll be outputting the function itself
# if the given baselib chunk is triggered
#
# if function is unnamed, assume the function is a container for the logic
# to be output. We're basically ignoring the wrapper and dumping what's inside

{
"abs": def abs(n):
return Math.abs(n)
,
"all": def all(a):
for e in a:
if not e: return False
return True
,
"any": def any(a):
for e in a:
if e: return True
return False
,
"bin": def bin(a): return '0b' + (a >>> 0).toString(2)
,
"bind": def ՐՏ_bind(fn, thisArg):
if fn.orig: fn = fn.orig
if thisArg is False: return fn
ret = def():
return fn.apply(thisArg, arguments)
ret.orig = fn
return ret
,
"rebind_all": def ՐՏ_rebindAll(thisArg, rebind):
if rebind is undefined: rebind = True
for JS('var p in thisArg'):
if thisArg[p] and thisArg[p].orig:
if rebind: thisArg[p] = bind(thisArg[p], thisArg)
else: thisArg[p] = thisArg[p].orig
,
"cmp": def cmp(a, b): return a < b ? -1 : a > b ? 1 : 0
,
"chr": def(): chr = String.fromCharCode
,
"dir": def dir(item):
# TODO: this isn't really representative of real Python's dir(), nor is it
# an intuitive replacement for "for ... in" loop, need to update this logic
# and introduce a different way of achieving "for ... in"
arr = []
for JS('var i in item'): arr.push(i)
return arr
,
"enumerate": def enumerate(item):
arr = []
iter = ՐՏ_Iterable(item)
for i in range(iter.length):
arr[arr.length] = [i, item[i]]
return arr
,
"eslice": def ՐՏ_eslice(arr, step, start, end):
arr = arr[:]
if JS('typeof arr') is 'string' or isinstance(arr, String):
isString = True
arr = arr.split('')

if step < 0:
step = -step
arr.reverse()
if JS('typeof start') is not "undefined": start = arr.length - start - 1
if JS('typeof end') is not "undefined": end = arr.length - end - 1
if JS('typeof start') is "undefined": start = 0
if JS('typeof end') is "undefined": end = arr.length

arr = arr.slice(start, end).filter(def(e, i): return i % step is 0;)
return isString ? arr.join('') : arr
,
"extends": def ՐՏ_extends(child, parent):
child.prototype = Object.create(parent.prototype)
child.prototype.__base__ = parent # since we don't support multiple inheritance, __base__ seemed more appropriate than __bases__ array of 1
child.prototype.constructor = child
,
"filter": def filter(oper, arr):
return arr.filter(oper)
,
"hex": def hex(a): return '0x' + (a >>> 0).toString(16)
,
"in": def ՐՏ_in(val, arr):
if JS('typeof arr.indexOf') is 'function': return arr.indexOf(val) is not -1
return arr.hasOwnProperty(val)
,
"iterable": def ՐՏ_Iterable(iterable):
# can't use Symbol.iterator yet since it's not supported on all platforms until ES6 (i.e. mobile browsers don't have it)
if iterable.constructor is [].constructor
or iterable.constructor is ''.constructor
or (tmp = Array.prototype.slice.call(iterable)).length:
return tmp or iterable
return Object.keys(iterable) # so we can use 'for ... in' syntax with hashes
,
"len": def len(obj):
# can't use Symbol.iterator yet since it's not supported on all platforms until ES6 (i.e. mobile browsers don't have it)
if obj.constructor is [].constructor
or obj.constructor is ''.constructor
or (tmp = Array.prototype.slice.call(obj)).length:
return (tmp or obj).length
return Object.keys(obj).length
,
"map": def map(oper, arr):
return arr.map(oper)
,
"max": def max(a):
if Array.isArray(a):
return Math.max.apply(null, a)
else:
return Math.max.apply(null, arguments)
,
"min": def min(a):
if Array.isArray(a):
return Math.min.apply(null, a)
else:
return Math.min.apply(null, arguments)
,
"merge": def ՐՏ_merge(target, source, overwrite):
for JS('var i in source'):
# instance variables
if source.hasOwnProperty(i) and (overwrite or JS('typeof target[i]') is 'undefined'): target[i] = source[i]
for prop in Object.getOwnPropertyNames(source.prototype):
# methods
if overwrite or JS('typeof target.prototype[prop]') is 'undefined': target.prototype[prop] = source.prototype[prop]
,
"mixin": def ՐՏ_mixin(*classes):
return def(baseClass):
for cls in classes:
for key in Object.getOwnPropertyNames(cls.prototype):
if key not in baseClass.prototype:
baseClass.prototype[key] = cls.prototype[key]
return baseClass

,
"print": def ՐՏ_print():
if JS('typeof console') is 'object': console.log.apply(console, arguments)
,
"range": def range(start, stop, step):
if arguments.length <= 1:
stop = start or 0
start = 0
step = arguments[2] or 1

length = Math.max(Math.ceil((stop - start) / step), 0)
idx = 0
range = Array(length)

while idx < length:
range[JS('idx++')] = start
start += step
return range
,
"reduce": def reduce(f, a): return Array.reduce(a, f)
,
"reversed": def reversed(arr):
tmp = arr[:]
return tmp.reverse()
,
"sorted": def sorted(arr):
tmp = arr[:]
return tmp.sort()
,
"sum": def sum(arr, start=0):
return arr.reduce(
def(prev, cur): return prev+cur
,
start
)
,
"type": def ՐՏ_type(obj):
return obj.constructor and obj.constructor.name ? obj.constructor.name : Object.prototype.toString.call(obj).slice(8, -1)
,
"zip": def zip(a, b):
return [[a[i], b[i]] for i in range(Math.min(a.length, b.length))]
,
"getattr": def getattr(obj, name):
return obj[name]
,
"setattr": def setattr(obj, name, value):
obj[name] = value
,
"hasattr": def hasattr(obj, name):
return JS('name in obj')
,
"eq": def ՐՏ_eq(a, b):
"""
Equality comparison that works with all data types, returns true if structure and
contents of first object equal to those of second object

Arguments:
a: first object
b: second object
"""
if a is b:
# simple object
return True

if (Array.isArray(a) and Array.isArray(b)) or (isinstance(a, Object) and isinstance(b, Object)):
# if length ot type doesn't match, they can't be equal
if a.constructor is not b.constructor or a.length is not b.length:
return False

if Array.isArray(a):
# arrays
for i in range(len(a)):
if not ՐՏ_eq(a[i], b[i]):
return False
else:
# hashes
# compare individual properties (order doesn't matter if it's a hash)
if Object.keys(a).length is not Object.keys(b).length: return False
for i in a:
# recursively test equality of object children
if not ՐՏ_eq(a[i], b[i]):
return False
return True
return False
,
"kwargs": def():
# WARNING: when using this function decorator, you will not be able to use obfuscators that rename local variables
def kwargs(f):
argNames = f.toString().match(/\\(([^\\)]+)/)[1]
if not kwargs.memo[argNames]:
kwargs.memo[argNames] = argNames ? argNames.split(',').map(def(s): return s.trim();) : []
argNames = kwargs.memo[argNames]
return def():
args = [].slice.call(arguments)
if args.length:
kw = args[-1]
if JS('typeof kw') is 'object':
for i in range(argNames.length):
if argNames[i] in dir(kw):
args[i] = kw[argNames[i]]
else:
args.push(kw)

# This logic is very fragile and very subtle, it needs to work both in ES6 and ES5, don't try to optimize the
# apply away into *args because having it in this format ensures correct 'this' context, otherwise the function
# ends up unbound. Similarly, the fallthrough to except handles class creation in ES6.
try:
return f.apply(this, args)
except as e:
if /Class constructor \\w+ cannot be invoked without 'new'/.test(e):
return new f(*args)
raise
kwargs.memo = {}
,

# Errors
# temporarily implemented via a wrapper pattern since there is no mechanism for assigning
# classes to dictionary keys yet
"AssertionError": def():
class AssertionError(Error):
def __init__(self, message):
self.name = "AssertionError"
self.message = message
,
"IndexError": def():
class IndexError(Error):
def __init__(self, message):
self.name = "IndexError"
self.message = message
,
"KeyError": def():
class KeyError(Error):
def __init__(self, message):
self.name = "KeyError"
self.message = message
,
"TypeError": def():
class TypeError(Error):
def __init__(self, message):
self.name = "TypeError"
self.message = message
,
"ValueError": def():
class ValueError(Error):
def __init__(self, message):
self.name = "ValueError"
self.message = message
,
}
'''
Loading

0 comments on commit 0d393d8

Please sign in to comment.