Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

feat(parser): Add support for member field setters #8

Merged
merged 1 commit into from
Jun 18, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/angular.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ part 'directives/ng_repeat.dart';
part 'dom_utilities.dart';
part 'exception_handler.dart';
part 'http.dart';
part 'interface_typing.dart';
part 'interpolate.dart';
part 'mirrors.dart';
part 'node_cursor.dart';
Expand Down Expand Up @@ -55,17 +56,19 @@ typedef FnWith3Args(a0, a1, a2);
typedef FnWith4Args(a0, a1, a2, a3);
typedef FnWith5Args(a0, a1, a2, a3, a4);

_relaxFnApply(Function fn, List args) {
relaxFnApply(Function fn, List args) {
// Check the args.length to support functions with optional parameters.
var argsLen = args.length;
if (fn is Function && fn != null) {
if (fn is FnWith5Args) {
if (fn is FnWith5Args && argsLen > 4) {
return fn(args[0], args[1], args[2], args[3], args[4]);
} else if (fn is FnWith4Args) {
} else if (fn is FnWith4Args && argsLen > 3) {
return fn(args[0], args[1], args[2], args[3]);
} else if (fn is FnWith3Args) {
} else if (fn is FnWith3Args&& argsLen > 2 ) {
return fn(args[0], args[1], args[2]);
} else if (fn is FnWith2Args) {
} else if (fn is FnWith2Args && argsLen > 1 ) {
return fn(args[0], args[1]);
} else if (fn is FnWith1Args) {
} else if (fn is FnWith1Args && argsLen > 0) {
return fn(args[0]);
} else if (fn is FnWith0Args) {
return fn();
Expand Down
64 changes: 64 additions & 0 deletions lib/interface_typing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
part of angular;

var OBJECT_QUAL_NAME = fastReflectClass(Object).qualifiedName;

_equalTypes(ClassMirror a, ClassMirror b) => a.qualifiedName == b.qualifiedName;

_isType(obj, type) {
InstanceMirror instanceMirror = reflect(obj);
ClassMirror classM = instanceMirror.type;

if (type is Type) {
type = fastReflectClass(type);
}

if (classM.superinterfaces.any((si) {return _equalTypes(si, type);})) return true;

while (classM != null) {
if (_equalTypes(classM, type)) return true;
if (classM.qualifiedName == OBJECT_QUAL_NAME) classM = null; // dart bug 5794
else classM = classM.superclass;

}
return false;
}

isInterface(obj, Type type) {
if (_isType(obj, type)) return true;

var objMembers = reflect(obj).type.members;

bool allPresent = true;
fastReflectClass(type).members.forEach((symbol, mirror) {
if (!objMembers.containsKey(symbol)) allPresent = false;
var objMirror = objMembers[symbol];
if (!_isType(objMirror, reflect(mirror).type)) allPresent = false;

// TODO(deboer): Check that the method signatures match. Waiting on dartbug 11334
/*if (mirror is MethodMirror) {
// are the paremeters the same?
var sameParameters = true;
var interfaceParams = mirror.parameters;
var objParams = objMirror.parameters;

Map<Symbol, ParameterMirror> namedParams;
int minParams = 0;
int maxParams = 0;
mirror.parameters.forEach((ParameterMirror param) {
if (param.isNamed) namedParams[param.qualifiedName] = param;
if (param.isOptional) maxParams++;
else { minParams++; minParams++; }
});

objMirror.parameters.forEach((param) {
if (param.isNamed) namedParams.remove(param.qualifiedName);
if (param.isOptional) maxParams--;
else { minParams--; maxParams--; }
});

if (minParams > 0) return false;
if (maxParams < 0) return false;
}*/
});
return allPresent;
}
43 changes: 28 additions & 15 deletions lib/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,13 @@ ParsedFn ZERO = new ParsedFn((_, _x) => 0);

class BreakException {}

// returns a function that calls fn with numArgs args as an array
varArgs(numArgs, fn) {
switch (numArgs) {
case 0: return () => fn([]);
case 1: return (p0) => fn([p0]);
case 2: return (p0, p1) => fn([p0, p1]);
case 3: return (p0, p1, p2) => fn([p0, p1, p3]);
case 4: return (p0, p1, p2, p3) => fn([p0, p1, p2, p3]);
case 5: return (p0, p1, p2, p3) => fn([p0, p1, p2, p3, p4]);
}
throw "varArgs with $numArgs is not supported.";
class Setter {
operator[]=(name, value){}
}

abstract class Getter {
bool containsKey(name);
operator[](name);
}

// Returns a tuple [found, value]
Expand All @@ -146,7 +142,9 @@ getterChild(value, childKey) {
} else {
return [false, null];
}
} else if (value is Map && value.containsKey(childKey)) {
}

if (isInterface(value, Getter) && value.containsKey(childKey)) {
return [true, value[childKey]];
} else {
InstanceMirror instanceMirror = reflect(value);
Expand All @@ -159,6 +157,7 @@ getterChild(value, childKey) {
if (instanceMirror.type.members.containsKey(curSym)) {
MethodMirror methodMirror = instanceMirror.type.members[curSym];
return [true, _relaxFnArgs((args) {
if (args == null) args = [];
try {
return instanceMirror.invoke(curSym, args).reflectee;
} catch (e) {
Expand Down Expand Up @@ -195,6 +194,21 @@ getter(scope, locals, path) {
return currentValue;
}

setterChild(obj, childKey, value) {
if (isInterface(obj, Setter)) {
obj[childKey] = value;
return value;
}
InstanceMirror instanceMirror = reflect(obj);
Symbol curSym = new Symbol(childKey);
try {
// maybe it is a member field?
return instanceMirror.setField(curSym, value).reflectee;
} catch (e) {
throw "$e \n\n${e.stacktrace}";
}
}

setter(obj, path, setValue) {
var element = path.split('.');
for (var i = 0; element.length > 1; i++) {
Expand All @@ -206,8 +220,7 @@ setter(obj, path, setValue) {
}
obj = propertyObj;
}
obj[element.removeAt(0)] = setValue;
return setValue;
return setterChild(obj, element.removeAt(0), setValue);
}

class Parser {
Expand Down Expand Up @@ -698,7 +711,7 @@ class Parser {
args.add(argsFn[i](self, locals));
}
var userFn = fn(self, locals);
return _relaxFnApply(userFn, args);
return relaxFnApply(userFn, args);
});
};

Expand Down
6 changes: 3 additions & 3 deletions lib/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class Scope implements Map {
};

var $watchCollectionAction = () {
_relaxFnApply(listener, [newValue, oldValue, self]);
relaxFnApply(listener, [newValue, oldValue, self]);
};

return this.$watch($watchCollectionWatch, $watchCollectionAction);
Expand Down Expand Up @@ -324,7 +324,7 @@ class Scope implements Map {
i = 0;
for (var length = namedListeners.length; i<length; i++) {
try {
_relaxFnApply(namedListeners[i], listenerArgs);
relaxFnApply(namedListeners[i], listenerArgs);
if (event.propagationStopped) return event;
} catch (e, s) {
_exceptionHandler(e, s);
Expand Down Expand Up @@ -356,7 +356,7 @@ class Scope implements Map {
if (current._listeners.containsKey(name)) {
current._listeners[name].forEach((listener) {
try {
_relaxFnApply(listener, listenerArgs);
relaxFnApply(listener, listenerArgs);
} catch(e, s) {
_exceptionHandler(e, s);
}
Expand Down
31 changes: 31 additions & 0 deletions test/angular_spec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import '_specs.dart';

main() {
describe('relaxFnApply', () {
it('should work with 6 arguments', () {
var sixArgs = [1, 1, 2, 3, 5, 8];
expect(relaxFnApply(() => "none", sixArgs)).toEqual("none");
expect(relaxFnApply((a) => a, sixArgs)).toEqual(1);
expect(relaxFnApply((a, b) => a + b, sixArgs)).toEqual(2);
expect(relaxFnApply((a, b, c) => a + b + c, sixArgs)).toEqual(4);
expect(relaxFnApply((a, b, c, d) => a + b + c + d, sixArgs)).toEqual(7);
expect(relaxFnApply((a, b, c, d, e) => a + b + c + d + e, sixArgs)).toEqual(12);
});

it('should work with 0 arguments', () {
var noArgs = [];
expect(relaxFnApply(() => "none", noArgs)).toEqual("none");
expect(relaxFnApply(([a]) => a, noArgs)).toEqual(null);
expect(relaxFnApply(([a, b]) => b, noArgs)).toEqual(null);
expect(relaxFnApply(([a, b, c]) => c, noArgs)).toEqual(null);
expect(relaxFnApply(([a, b, c, d]) => d, noArgs)).toEqual(null);
expect(relaxFnApply(([a, b, c, d, e]) => e, noArgs)).toEqual(null);
});

it('should fail with not enough arguments', () {
expect(() {
relaxFnApply((required, alsoRequired) => "happy", [1]);
}).toThrow('Unknown function type, expecting 0 to 5 args.');
});
});
}
63 changes: 63 additions & 0 deletions test/interface_typing_spec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import '_specs.dart';
import "dart:mirrors";

class InterfaceWithFields {
int aNumber;
String aString;
}

class ClassWithFields {
int aNumber;
String aString;
bool somethingElse;
}

class ClassWithDifferentFields {
int aDifferentNumber;
}

class ClassWithNotFields {
aNumber(){}
aString(){}
}

class InterfaceWithMethods {
method({b}) {}
}

class ClassWithMethods {
method({b, c}) {}
}

class ClassWithDifferentMethods {
method({c, d}) {}
}

main() {
describe('Interface Typing', () {
it('should recognize built-in objects as an object', () {
var im = reflect(new Object());
var cm = reflectClass(Object);
expect(im.type.qualifiedName).toEqual(cm.qualifiedName);

expect(isInterface(new Object(), Object)).toBeTruthy();

expect(isInterface([], Object)).toBeTruthy();
expect(isInterface({}, Object)).toBeTruthy();
expect(isInterface(6, Object)).toBeTruthy();
expect(isInterface('s', Object)).toBeTruthy();
});

it('should recognize interfaces with fields', () {
expect(isInterface(new ClassWithFields(), InterfaceWithFields)).toBeTruthy();
expect(isInterface(new ClassWithDifferentFields(), InterfaceWithFields)).toBeFalsy();
expect(isInterface(new ClassWithNotFields(), InterfaceWithFields)).toBeFalsy();
});

// waiting on dartbug 11334
xit('should recognize interfaces with methods', () {
expect(isInterface(new ClassWithMethods(), InterfaceWithMethods)).toBeTruthy();
expect(isInterface(new ClassWithDifferentMethods(), InterfaceWithMethods)).toBeFalsy();
});
});
}
37 changes: 37 additions & 0 deletions test/parser_spec.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import '_specs.dart';

// Used to test getter / setter logic.
class TestData {
String _str = "testString";
get str => _str;
set str(x) => _str = x;

method() => "testMethod";
}

class LexerExpect extends Expect {
LexerExpect(actual) : super(actual);
toBeToken(int index, String text) {
Expand Down Expand Up @@ -528,6 +537,34 @@ main() {
// DOES NOT WORK. bool.toString is not reflected
// expect(eval('bool.toString()')).toEqual('false');
});

it('should support map getters', () {
expect(Parser.parse('a')({'a': 4})).toEqual(4);
});

it('should support member getters', () {
expect(Parser.parse('str')(new TestData())).toEqual('testString');
});

it('should support returning member functions', () {
expect(Parser.parse('method')(new TestData())()).toEqual('testMethod');
});

it('should support calling member functions', () {
expect(Parser.parse('method()')(new TestData())).toEqual('testMethod');
});

it('should support array setters', () {
var data = {'a': [1,3]};
expect(Parser.parse('a[1]=2')(data)).toEqual(2);
expect(data['a'][1]).toEqual(2);
});

it('should support member field setters', () {
TestData data = new TestData();
expect(Parser.parse('str="bob"')(data)).toEqual('bob');
expect(data.str).toEqual("bob");
});
});

describe('assignable', () {
Expand Down