Commits

masklinn  committed a03416b

Add formal function for parsing parameters

  • Participants
  • Parent commits 87e9773

Comments (0)

Files changed (2)

 
         throw new Error("Could not convert " + val + " to a pyval");
     }
+
+    // JSAPI, JS-level utility functions for implementing new py.js
+    // types
+    py.py = {};
+
+    py.py.parseArgs = function PY_parseArgs(argument, format) {
+        var out = {};
+        var args = argument[0], kwargs = argument[1];
+        var name = function (spec) {
+            if (typeof spec === 'string') {
+                return spec;
+            } else if (spec instanceof Array && spec.length === 2) {
+                return spec[0];
+            }
+            throw new Error(
+                "TypeError: unknown format specification " +
+                    JSON.stringify(spec));
+        };
+        
+        for(var i=0; i<args.length; ++i) {
+            var spec = format[i];
+            // spec list ended, or specs switching to keyword-only
+            if (!spec || spec === '*') {
+                throw new Error(
+                    "TypeError: function takes exactly " + (i-1) +
+                    " positional arguments (" + args.length +
+                    " given")
+            }
+
+            out[name(spec)] = args[i];
+        }
+        for (var kwarg in kwargs) {
+            if (!kwargs.hasOwnProperty(kwarg)) { continue; }
+
+            // Error out if already matched via positional
+            if (kwarg in out) {
+                throw new Error(
+                    "TypeError: function got multiple values " + 
+                    "for keyword argument '" + kwarg + "'");
+            }
+            // Look for spec matching kwarg, start after positional
+            // arguments as we already checked there
+            var expected = false;
+            for(var j=i; j<format.length; ++j) {
+                var spec = format[j];
+                if (name(spec) === kwarg) {
+                    expected = true; break;
+                }
+            }
+            if (!expected) {
+                throw new Error(
+                    "TypeError: function got an unexpected keyword argument '"
+                    + kwarg + "'");
+            }
+
+            out[kwarg] = kwargs[kwarg];
+        }
+
+        // Fixup args count if there's a kwonly flag
+        var kwonly = 0;
+        for(var k = 0; k < format.length; ++k) {
+            if (format[k] === '*') { kwonly = 1; break; }
+        }
+        // Check that all required arguments have been matched, add
+        // optional values
+        for(var k = 0; k < format.length; ++k) {
+            var spec = format[k], n = name(spec);
+            // keyword only or matched argument
+            if (n === '*' || n in out) { continue; }
+            // Unmatched required argument
+            if (!(spec instanceof Array)) {
+                throw new Error(
+                    "TypeError: function takes exactly " + (format.length - kwonly)
+                    + " arguments");
+            }
+            // Set default value
+            out[n] = spec[1];
+        }
+        
+        return out;
+    };
+
     // Builtins
     py.type = function type(constructor, base, dict) {
         var proto;
         __setitem__: function (key, value) {
             this._store[key.__hash__()] = [key, value];
         },
-        get: function (args) {
-            var h = args[0].__hash__();
-            var def = args.length > 1 ? args[1] : py.None;
+        get: function () {
+            var args = py.py.parseArgs(arguments, ['k', ['d', py.None]]);
+            var h = args.k.__hash__();
             if (!(h in this._store)) {
-                return def;
+                return args.d;
             }
             return this._store[h][1];
         },
         this._inst = null;
         this._func = nativefunc;
     }, py.object, {
-        __call__: function (args, kwargs) {
+        __call__: function () {
             // don't want to rewrite __call__ for instancemethod
-            return this._func.call(this._inst, args, kwargs);
+            return this._func.apply(this._inst, arguments);
         },
         toJSON: function () {
             return this._func;
         this._func = nativefunc;
     }, py.def, {});
 
-    py.issubclass = new py.def(function issubclass(args) {
-        var derived = args[0], parent = args[1];
-        // still hurts my brain that this can work
-        return derived.prototype instanceof py.object
-            ? py.True
-            : py.False;
+    py.issubclass = new py.def(function issubclass() {
+        var args = py.py.parseArgs(arguments, ['C', 'B']);
+        if (args.C === args.B || args.C.prototype instanceof args.B) {
+            return py.True;
+        }
+        return py.False;
     });
 
 

File test/test.js

     return py.evaluate(py.parse(py.tokenize(str)), context);
 };
 
+describe('Utility functions', function () {
+    describe("Arguments parser", function () {
+        it('should return an object', function () {
+            expect(py.py.parseArgs([[], {}], []))
+                .to.be.an(Object);
+        });
+        it('should assert the number of arguments', function () {
+            expect(function () {
+                py.py.parseArgs([[1, 2], {}], []);
+            }).to.throwException(/^TypeError/);
+            expect(function () {
+                py.py.parseArgs([[], {a: 3}], []);
+            }).to.throwException(/^TypeError/);
+            expect(function () {
+                py.py.parseArgs([[], {}], ['a']);
+            }).to.throwException(/^TypeError/);
+            expect(function () {
+                py.py.parseArgs([[], {}], ['*', 'a']);
+            }).to.throwException(/^TypeError/);
+        });
+        it('should prevent arguments conflicts', function () {
+            expect(function () {
+                py.py.parseArgs([[1], {foo: 1}], ['foo']);
+            }).to.throwException(/^TypeError/);
+        });
+        it('should not require optional arguments', function () {
+            var val = new py.float(3);
+            expect(py.py.parseArgs([[], {}], [['foo', val]]))
+                .to.eql({
+                    foo: val
+                });
+            expect(py.py.parseArgs([[], {}], ['*', ['foo', val]]))
+                .to.eql({
+                    foo: val
+                });
+        });
+        it('should override defaults', function () {
+            var def = new py.float(3);
+            var val = new py.float(4);
+
+            expect(py.py.parseArgs([[val], {}], [['foo', def]]))
+                .to.eql({
+                    foo: val
+                });
+            expect(py.py.parseArgs([[], {foo: val}], [['foo', def]]))
+                .to.eql({
+                    foo: val
+                });
+        });
+    });
+});
+
 describe('Literals', function () {
     describe('Number', function () {
         it('should have the right type', function () {