Rework MustacheContext (#64)

Introduce `mirrors.dart` and make use of it for creating mirrors. This should make dart2js work a little bit better. As a side effect of the reworks dropped support of invoking methods starting with `get`. 

fix #62
diff --git a/.travis.yml b/.travis.yml
index e4a33b7..1e78928 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,32 @@
 language: dart
+dist: trusty
 sudo: false
+
+addons:
+  firefox: 53.0.3
+  apt:
+    packages:
+      - google-chrome-stable
+
 dart:
   - stable
+  - 1.23.0
   - 1.22.0
   - 1.21.1
   - 1.20.1
   - 1.19.1
-script: ./build.sh
\ No newline at end of file
+
+before_script:
+  - export DISPLAY=:99.0
+  - sh -e /etc/init.d/xvfb start &
+  - sleep 3
+
+script: ./build.sh
+
+branches:
+  only:
+    - master
+
+cache:
+  directories:
+    - $HOME/.pub-cache
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3262ed9..d882ec8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,14 @@
 # CHANGELOG
 
-## next
+## 2.0.0 (2017-06-13)
+
+* Rework MustacheContext ([#64][pr-64])
 * Use dartanalyzer `--strong` mode ([#61](https://github.com/valotas/mustache4dart/issues/61))
 
+As part of the [MustacheContext rework][pr-64], a couple of simplifications have been made. Most
+notable one is the drop support of mirroring methods starting with `get` as it does not make any
+sense with dart. Use a getter instead.
+
 ## 1.1.0 (2017-05-10)
 
 * Avoid trapping exceptions by using reflection ([#59](https://github.com/valotas/mustache4dart/pull/59))
@@ -29,3 +35,5 @@
 ## 1.0.8 (2015-02-01)
 
 * Find property names in superclasses [issue](https://github.com/valotas/mustache4dart/issues/33)
+
+[pr-64][https://github.com/valotas/mustache4dart/pull/64]
diff --git a/README.md b/README.md
index 4411999..79345ff 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Mustache for Dart
 
-[![Build Status](https://travis-ci.org/valotas/mustache4dart.svg?branch=v1.0.12)](https://travis-ci.org/valotas/mustache4dart)
+[![Build Status](https://travis-ci.org/valotas/mustache4dart.svg?branch=master)](https://travis-ci.org/valotas/mustache4dart)
 [![Coverage Status](https://coveralls.io/repos/github/valotas/mustache4dart/badge.svg?branch=master)](https://coveralls.io/github/valotas/mustache4dart?branch=master)
 
 A simple implementation of [Mustache][mustache] for the
@@ -13,7 +13,7 @@
 In order to use the library, just add it to your `pubspec.yaml` as a dependency
 
 	dependencies:
-	  mustache4dart: '>= 1.0.0 < 2.0.0'
+	  mustache4dart: '>= 2.0.0 < 3.0.0'
 
 and then import the package
 
@@ -39,22 +39,21 @@
 1. use the `[]` operator with `firstname` as the parameter
 2. search for a field named `firstname`
 3. search for a getter named `firstname`
-4. search for a method named `firstname`
-5. search for a method named `getFirstname`
+4. search for a method named `firstname` (see Lambdas support)
 
 in each case the first valid value will be used.
 
 #### @MirrorsUsed
 In order to do the stuff described above the mirror library is being used which
-could lead to big js files when compiling the library with dartjs. The
-implementation does use the `@MirrorsUsed` annotation but
-[as documented][mirrorsused] this is experimental.
+could lead to big js files when compiling the library with dartjs. In order to
+preserve the type information you have to annotate the objects used as
+contextes with `@MirrorsUsed`. Have in mind though that [as documented][mirrorsused]
+this is experimental.
 
 In order to avoid the use of the mirrors package, make sure that you compile
 your library with `dart2js -DMIRRORS=false `. In that case though you must
 always make sure that your context object have a right implementation of the
-`[]` operator as it will be the only check made against them (from the ones
-described above) in order to define a value.
+`[]` operator as no other checks on the object will be available.
 
 ### Partials
 mustache4dart support partials but it needs somehow to know how to find a
diff --git a/build.sh b/build.sh
index dc07881..e86da58 100755
--- a/build.sh
+++ b/build.sh
@@ -7,14 +7,16 @@
 dartanalyzer --strong --fatal-warnings lib/*.dart test/*.dart
 
 # Assert that code is formatted.
-pub global activate dart_style
-dirty_code=$(pub global run dart_style:format --dry-run lib/ test/ example/)
-if [[ -n "$dirty_code" ]]; then
-  echo Unformatted files:
-  echo "$dirty_code" | sed 's/^/    /'
-  exit 1
-else
-  echo All Dart source files are formatted.
+if [ "$TRAVIS_DART_VERSION" = "stable" ]; then
+  pub global activate dart_style
+  dirty_code=$(pub global run dart_style:format --dry-run lib/ test/ example/)
+  if [[ -n "$dirty_code" ]]; then
+    echo Unformatted files:
+    echo "$dirty_code" | sed 's/^/    /'
+    exit 1
+  else
+    echo All Dart source files are formatted.
+  fi
 fi
 
 # run the tests
@@ -26,6 +28,9 @@
   pub global run dart_coveralls report \
     --retry 2 \
     --exclude-test-files \
-    --debug \
     test/mustache_all.dart
-fi
\ No newline at end of file
+fi
+
+if [ "$TRAVIS_DART_VERSION" = "stable" ]; then
+  pub run test -p chrome,firefox
+fi
diff --git a/lib/mustache_context.dart b/lib/mustache_context.dart
index 921d8cf..8480485 100644
--- a/lib/mustache_context.dart
+++ b/lib/mustache_context.dart
@@ -2,8 +2,7 @@
 
 import 'dart:collection';
 
-@MirrorsUsed(symbols: '*')
-import 'dart:mirrors';
+import 'package:mustache4dart/src/mirrors.dart';
 
 const USE_MIRRORS = const bool.fromEnvironment('MIRRORS', defaultValue: true);
 const String DOT = '\.';
@@ -25,30 +24,27 @@
         assumeNullNonExistingProperty: assumeNullNonExistingProperty);
   }
 
-  call([arg]);
+  get ctx;
+
+  value([arg]);
 
   bool get isFalsey;
+
   bool get isLambda;
-  MustacheContext operator [](String key);
+
+  MustacheContext field(String key);
+
   MustacheContext _getMustachContext(String key);
-  String get rootContextString;
 }
 
-abstract class MustacheToString {
-  dynamic get ctx;
-  MustacheContext get parent;
-
-  String get rootContextString =>
-      parent == null ? ctx.toString() : parent.rootContextString;
-}
-
-class _MustacheContext extends MustacheToString implements MustacheContext {
+class _MustacheContext implements MustacheContext {
   static final FALSEY_CONTEXT = new _MustacheContext(false);
+
   final ctx;
   final _MustacheContext parent;
   final bool assumeNullNonExistingProperty;
   bool useMirrors = USE_MIRRORS;
-  _ObjectReflector _ctxReflector;
+  Mirror _ctxReflection;
 
   _MustacheContext(this.ctx,
       {_MustacheContext this.parent, this.assumeNullNonExistingProperty});
@@ -57,24 +53,28 @@
 
   bool get isFalsey => ctx == null || ctx == false;
 
-  call([arg]) => isLambda ? callLambda(arg) : ctx.toString();
+  value([arg]) => isLambda ? callLambda(arg) : ctx.toString();
 
-  callLambda(arg) => ctx is NoParamLambda
-      ? ctx()
-      : ctx is TwoParamLambda
-          ? ctx(arg, nestedContext: this)
-          : ctx is OptionalParamLambda ? ctx(nestedContext: this) : ctx(arg);
+  callLambda(arg) {
+    if (ctx is NoParamLambda) {
+      return ctx is OptionalParamLambda ? ctx(nestedContext: this) : ctx();
+    }
+    if (ctx is TwoParamLambda) {
+      return ctx(arg, nestedContext: this);
+    }
+    return ctx(arg);
+  }
 
-  operator [](String key) {
+  MustacheContext field(String key) {
     if (ctx == null) return null;
     return _getInThisOrParent(key);
   }
 
-  _getInThisOrParent(String key) {
+  MustacheContext _getInThisOrParent(String key) {
     var result = _getContextForKey(key);
     //if the result is null, try the parent context
     if (result == null && !_hasActualValueSlot(key) && parent != null) {
-      result = parent[key];
+      result = parent.field(key);
       if (result != null) {
         return _newMustachContextOrNull(result.ctx);
       }
@@ -82,7 +82,7 @@
     return result;
   }
 
-  _getContextForKey(String key) {
+  MustacheContext _getContextForKey(String key) {
     if (key == DOT) {
       return this;
     }
@@ -101,12 +101,12 @@
     return _getMustachContext(key);
   }
 
-  _getMustachContext(String key) {
-    var v = _getActualValue(key);
+  MustacheContext _getMustachContext(String key) {
+    final v = _getActualValue(key);
     return _newMustachContextOrNull(v);
   }
 
-  _newMustachContextOrNull(v) {
+  MustacheContext _newMustachContextOrNull(v) {
     if (v == null) {
       return null;
     }
@@ -124,24 +124,11 @@
   }
 
   dynamic _getActualValue(String key) {
-    //Try to make dart2js understand that when we define USE_MIRRORS = false
-    //we do not want to use any reflector as that inflates the generated
-    //javascript.
+    if (ctx is Map) {
+      return ctx[key];
+    }
     if (useMirrors && USE_MIRRORS) {
-      if (reflect(ctx).type.instanceMembers.containsKey(new Symbol("[]"))) {
-        MethodMirror m = reflect(ctx).type.instanceMembers[new Symbol("[]")];
-        TypeMirror reflectedString = reflectType(String);
-        if (reflectedString.isAssignableTo(m.parameters[0].type)) {
-          try {
-            return ctx[key];
-          } catch (NoSuchMethodError) {
-            //This should never happen unless we were trapping a lower level
-            //NoSuchMethodError before.  Continue to do so to be bug-for-bug
-            //compatible.
-          }
-        }
-      }
-      return ctxReflector[key];
+      return ctxReflector.field(key).val();
     } else {
       try {
         return ctx[key];
@@ -159,23 +146,20 @@
       return (ctx as Map).containsKey(key);
     } else if (useMirrors && USE_MIRRORS) {
       //TODO test the case of no mirrors
-      return ctxReflector.hasSlot(key);
+      return ctxReflector.field(key).exists;
     }
     return false;
   }
 
-  get ctxReflector {
-    if (_ctxReflector == null) {
-      _ctxReflector = new _ObjectReflector(ctx);
+  Mirror get ctxReflector {
+    if (_ctxReflection == null) {
+      _ctxReflection = reflect(ctx);
     }
-    return _ctxReflector;
+    return _ctxReflection;
   }
-
-  String toString() => "MustacheContext($ctx, $parent)";
 }
 
 class _IterableMustacheContextDecorator extends IterableBase<_MustacheContext>
-    with MustacheToString
     implements MustacheContext {
   final Iterable ctx;
   final _MustacheContext parent;
@@ -184,7 +168,8 @@
   _IterableMustacheContextDecorator(this.ctx,
       {this.parent, this.assumeNullNonExistingProperty});
 
-  call([arg]) => throw new Exception('Iterable can be called as a function');
+  value([arg]) =>
+      throw new Exception('Iterable can not be called as a function');
 
   Iterator<_MustacheContext> get iterator =>
       new _MustachContextIteratorDecorator(ctx.iterator,
@@ -199,22 +184,18 @@
 
   bool get isLambda => false;
 
-  operator [](String key) {
-    if (key == DOT) {
-      return this;
-    }
-    throw new Exception(
-        'Iterable can only be iterated. No [] implementation is available');
+  field(String key) {
+    assert(key ==
+        DOT); // 'Iterable can only be iterated. No [] implementation is available'
+    return this;
   }
 
   _getMustachContext(String key) {
-    if (key == 'empty' || key == 'isEmpty') {
-      return new _MustacheContext(isEmpty,
-          parent: parent,
-          assumeNullNonExistingProperty: assumeNullNonExistingProperty);
-    }
-    throw new Exception(
-        'Iterable can only be asked for empty or isEmpty keys or be iterated');
+    // 'Iterable can only be asked for empty or isEmpty keys or be iterated'
+    assert(key == 'empty' || key == 'isEmpty');
+    return new _MustacheContext(isEmpty,
+        parent: parent,
+        assumeNullNonExistingProperty: assumeNullNonExistingProperty);
   }
 }
 
@@ -240,113 +221,3 @@
     }
   }
 }
-
-/**
- * Helper class which given an object it will try to get a value by key analyzing
- * the object by reflection
- */
-class _ObjectReflector {
-  final InstanceMirror m;
-
-  factory _ObjectReflector(o) {
-    return new _ObjectReflector._(reflect(o));
-  }
-
-  _ObjectReflector._(this.m);
-
-  operator [](String key) {
-    var declaration = getDeclaration(key);
-
-    if (declaration == null) {
-      return null;
-    }
-
-    return declaration.value;
-  }
-
-  bool hasSlot(String key) {
-    return getDeclaration(key) != null;
-  }
-
-  _ObjectReflectorDeclaration getDeclaration(String key) {
-    return new _ObjectReflectorDeclaration(m, key);
-  }
-}
-
-class _ObjectReflectorDeclaration {
-  final InstanceMirror mirror;
-  final MethodMirror declaration;
-
-  factory _ObjectReflectorDeclaration(
-      InstanceMirror m, String declarationName) {
-    var methodMirror = m.type.instanceMembers[new Symbol(declarationName)];
-    if (methodMirror == null) {
-      //try appending the word get to the name:
-      var nameWithGet =
-          "get${declarationName[0].toUpperCase()}${declarationName.substring(1)}";
-      methodMirror = m.type.instanceMembers[new Symbol(nameWithGet)];
-    }
-    return methodMirror == null
-        ? null
-        : new _ObjectReflectorDeclaration._(m, methodMirror);
-  }
-
-  _ObjectReflectorDeclaration._(this.mirror, this.declaration);
-
-  bool get isLambda => declaration.parameters.length >= 1;
-
-  Function get lambda => (val, {MustacheContext nestedContext}) {
-        var im = mirror.invoke(
-            declaration.simpleName,
-            _createPositionalArguments(val),
-            _createNamedArguments(nestedContext));
-        return im is InstanceMirror ? im.reflectee : null;
-      };
-
-  _createPositionalArguments(val) {
-    var positionalParam = declaration.parameters
-        .firstWhere((p) => !p.isOptional, orElse: () => null);
-    if (positionalParam == null) {
-      return [];
-    } else {
-      return [val];
-    }
-  }
-
-  Map<Symbol, dynamic> _createNamedArguments(MustacheContext ctx) {
-    var map = new Map<Symbol, dynamic>();
-    var nestedContextParameterExists = declaration.parameters.firstWhere(
-        (p) => p.simpleName == new Symbol('nestedContext'),
-        orElse: () => null);
-    if (nestedContextParameterExists != null) {
-      map[nestedContextParameterExists.simpleName] = ctx;
-    }
-    return map;
-  }
-
-  get value {
-    if (isLambda) {
-      return lambda;
-    }
-
-    //Now we try to find out a field or a getter named after the given name
-    var im = null;
-    if (isVariableOrGetter) {
-      im = mirror.getField(declaration.simpleName);
-    } else if (isParameterlessMethod) {
-      im = mirror.invoke(declaration.simpleName, []);
-    }
-    if (im != null && im is InstanceMirror) {
-      return im.reflectee;
-    }
-    return null;
-  }
-
-  //TODO check if we really need the declaration is VariableMirror test
-  bool get isVariableOrGetter =>
-      (declaration is VariableMirror) ||
-      (declaration is MethodMirror && declaration.isGetter);
-
-  bool get isParameterlessMethod =>
-      declaration is MethodMirror && declaration.parameters.length == 0;
-}
diff --git a/lib/src/mirrors.dart b/lib/src/mirrors.dart
new file mode 100644
index 0000000..a8a876e
--- /dev/null
+++ b/lib/src/mirrors.dart
@@ -0,0 +1,92 @@
+import 'dart:mirrors' as mirrors;
+
+reflect(o) {
+  return new Mirror(o, mirrors.reflect(o));
+}
+
+final _bracketsOperator = new Symbol("[]");
+
+class Mirror {
+  final mirrors.InstanceMirror instanceMirror;
+  final dynamic object;
+
+  Mirror(this.object, this.instanceMirror);
+
+  Field field(String name) {
+    final Map<Symbol, mirrors.MethodMirror> members =
+        _instanceMembers(instanceMirror);
+    if (_isStringAssignableToBracketsOperator(members)) {
+      return new _BracketsField(object, name);
+    }
+    final methodMirror = members[new Symbol(name)];
+    if (methodMirror == null) {
+      return _noField;
+    }
+    return new _MethodMirrorField(this.instanceMirror, methodMirror);
+  }
+}
+
+Map<Symbol, mirrors.MethodMirror> _instanceMembers(mirrors.InstanceMirror m) {
+  if (m != null && m.type != null) {
+    return m.type.instanceMembers;
+  }
+  return null;
+}
+
+_isStringAssignableToBracketsOperator(
+    Map<Symbol, mirrors.MethodMirror> members) {
+  if (!members.containsKey(_bracketsOperator)) {
+    return false;
+  }
+  try {
+    mirrors.MethodMirror m = members[_bracketsOperator];
+    return mirrors.reflectType(String).isAssignableTo(m.parameters[0].type);
+  } catch (e) {
+    return false;
+  }
+}
+
+class Field {
+  bool get exists {
+    return false;
+  }
+
+  dynamic val() => null;
+}
+
+final _noField = new Field();
+
+class _MethodMirrorField extends Field {
+  final mirrors.InstanceMirror instance;
+  final mirrors.MethodMirror method;
+
+  _MethodMirrorField(this.instance, this.method);
+
+  bool get exists => isVariable || isGetter || isLambda;
+
+  bool get isGetter => method.isGetter;
+
+  bool get isVariable => method is mirrors.VariableMirror;
+
+  bool get isLambda => method.parameters.length >= 0;
+
+  val() {
+    if (!exists) {
+      return null;
+    }
+    final resultMirror = instance.getField(method.simpleName);
+    return resultMirror.reflectee;
+  }
+}
+
+class _BracketsField extends Field {
+  var value;
+
+  _BracketsField(objectWithBracketsOperator, String key) {
+    this.value = objectWithBracketsOperator[key];
+  }
+
+  bool get exists => value != null;
+
+  val() => value;
+}
diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart
index d23ce75..81107e0 100644
--- a/lib/src/tokens.dart
+++ b/lib/src/tokens.dart
@@ -166,19 +166,19 @@
   _ExpressionToken.withSource(this.value, source) : super.withSource(source);
 
   apply(MustacheContext ctx, {bool errorOnMissingProperty: false}) {
-    var val = ctx[value];
-    if (val == null) {
+    var field = ctx.field(value);
+    if (field == null) {
       //TODO define an exception for such cases
       if (errorOnMissingProperty) {
-        throw "Could not find '$value' property in ${ctx.rootContextString}}";
+        throw "Could not find '$value' property";
       }
       return EMPTY_STRING;
     }
-    if (val.isLambda) {
+    if (field.isLambda) {
       //A lambda's return value should be parsed
-      return render(val(null), ctx);
+      return render(field.value(null), ctx);
     }
-    return val();
+    return field.value();
   }
 
   String toString() => "ExpressionToken($value)";
@@ -272,31 +272,31 @@
   Token get next => endSection.next;
 
   apply(MustacheContext ctx, {bool errorOnMissingProperty: false}) {
-    var val = ctx[value];
+    var field = ctx.field(value);
     //TODO: remove null check by returning a falsey context
-    if (errorOnMissingProperty && val == null) {
-      throw "Could not find '$value' property in ${ctx.rootContextString}}";
+    if (errorOnMissingProperty && field == null) {
+      throw "Could not find '$value' property";
     }
-    if (val == null || val.isFalsey) {
+    if (field == null || field.isFalsey) {
       return EMPTY_STRING;
     }
     StringBuffer str = new StringBuffer();
-    if (val is Iterable) {
-      (val as Iterable).forEach((v) {
+    if (field is Iterable) {
+      (field as Iterable).forEach((v) {
         forEachUntilEndSection((Token t) => str.write(t.apply(v)));
       });
       return str.toString();
     }
 
-    if (val.isLambda) {
+    if (field.isLambda) {
       //apply the source to the given function
       forEachUntilEndSection((Token t) => str.write(t._source));
       //A lambda's return value should be parsed
-      return render(val(str.toString()), ctx, delimiter: delimiter);
+      return render(field.value(str.toString()), ctx, delimiter: delimiter);
     }
 
     //in any other case:
-    forEachUntilEndSection((Token t) => str.write(t.apply(val)));
+    forEachUntilEndSection((Token t) => str.write(t.apply(field)));
     return str.toString();
   }
 
@@ -330,9 +330,9 @@
   _InvertedSectionToken(String val, Delimiter del) : super(val, del);
 
   apply(MustacheContext ctx, {bool errorOnMissingProperty: false}) {
-    var val = ctx[value];
+    var field = ctx.field(value);
     //TODO: remove null check. Always return a falsey context
-    if (val == null || val.isFalsey) {
+    if (field == null || field.isFalsey) {
       StringBuffer buf = new StringBuffer();
       forEachUntilEndSection((Token t) {
         var val2 = t.apply(ctx);
diff --git a/pubspec.yaml b/pubspec.yaml
index 785ee5d..ede9526 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: mustache4dart
-version: 1.1.0
+version: 2.0.0
 author: Georgios Valotasios <valotas@gmail.com>
 description: A mustache implementation for the Dart language
 homepage: https://github.com/valotas/mustache4dart
diff --git a/test/mustache_all.dart b/test/mustache_all.dart
index e097bae..d155b53 100644
--- a/test/mustache_all.dart
+++ b/test/mustache_all.dart
@@ -3,6 +3,7 @@
 import "mustache_line_test.dart" as line_test;
 import "mustache_specs_test.dart" as specs_test;
 import "mustache_test.dart" as general_test;
+import "mustache_context_reflect_test.dart" as reflect_test;
 
 void main() {
   context_test.main();
@@ -10,4 +11,5 @@
   line_test.main();
   specs_test.main();
   general_test.main();
+  reflect_test.main();
 }
diff --git a/test/mustache_context_reflect_test.dart b/test/mustache_context_reflect_test.dart
new file mode 100644
index 0000000..29dbe33
--- /dev/null
+++ b/test/mustache_context_reflect_test.dart
@@ -0,0 +1,142 @@
+import 'dart:mirrors' as mirrors;
+import 'package:test/test.dart';
+import 'package:mustache4dart/src/mirrors.dart';
+
+@mirrors.MirrorsUsed()
+class Person {
+  final String name;
+  final String lastname;
+  final Person parent;
+
+  Person(this.name, {this.lastname: null, this.parent: null});
+
+  get fullname {
+    return "$name $lastname";
+  }
+
+  getFullnameWithInitial() {
+    final initial = this.parent.name[0].toUpperCase();
+    return "$name $initial. $lastname";
+  }
+}
+
+class ClassWithLambda {
+  final int num;
+
+  ClassWithLambda(this.num);
+
+  lambdaWithArity1(str) => "[[$str $num]]";
+}
+
+@mirrors.MirrorsUsed()
+class ClassWithBrackets {
+  operator [](String input) {
+    if (input == 'nullval') {
+      return null;
+    }
+    return new Person(input);
+  }
+}
+
+void main() {
+  group('reflect', () {
+    test('returns a mirror object', () {
+      final cat = new Person("cat");
+      expect(reflect(cat), isNotNull);
+    });
+
+    group('field', () {
+      test('should return an object', () {
+        final cat = new Person("cat");
+
+        final actual = reflect(cat).field('name');
+
+        expect(actual, isNotNull);
+        expect(actual, new isInstanceOf<Field>());
+      });
+
+      group(".exists", () {
+        final cat = new Person("cat");
+
+        test('returns true if the field exists', () {
+          expect(reflect(cat).field('name').exists, isTrue);
+        });
+
+        test('returns true if the getter exists', () {
+          expect(reflect(cat).field('fullname').exists, isTrue);
+        });
+
+        test('returns false if the method does not exist', () {
+          expect(reflect(cat).field('fullnameWithInitial').exists, isFalse);
+        });
+
+        test('returns false if no field exists', () {
+          expect(reflect(cat).field('xyz').exists, isFalse);
+        });
+
+        test('returns false if [] operator returns a null value', () {
+          expect(reflect(new ClassWithBrackets()).field('nullval').exists,
+              isFalse);
+        });
+      });
+
+      group(".val()", () {
+        test('returns the value of a field', () {
+          final cat = new Person("cat");
+
+          final actual = reflect(cat);
+
+          expect(actual.field('name').val(), "cat");
+        });
+
+        test('returns the value of a getter', () {
+          final george = new Person("George", lastname: "Valotasios");
+
+          final actual = reflect(george);
+
+          expect(actual.field('fullname').val(), "George Valotasios");
+        });
+
+        test('does not returns the value of a get methods', () {
+          final george = new Person("George",
+              lastname: "Valotasios", parent: new Person("Thomas"));
+
+          final actual = reflect(george);
+
+          expect(actual.field('fullnameWithInitial').exists, isFalse);
+        });
+
+        test('returns the value from a [] operator', () {
+          final object = new ClassWithBrackets();
+
+          final actual = reflect(object).field('xyz');
+
+          expect(actual.val(), isNotNull);
+          expect(actual.val(), new isInstanceOf<Person>());
+          expect(actual.val().name, 'xyz');
+        }, onPlatform: {
+          "js": new Skip("[] operator can not be reflected in javascript")
+        });
+
+        test('returns always a reference to the value', () {
+          final thomas = new Person("Thomas");
+          final george =
+              new Person("George", lastname: "Valotasios", parent: thomas);
+
+          final actual = reflect(george);
+
+          expect(actual.field('parent').val(), thomas);
+        });
+
+        test('returns a ref to the function if it has an arity of 1', () {
+          final labmbda = new ClassWithLambda(1);
+
+          final actual = reflect(labmbda).field('lambdaWithArity1');
+
+          expect(actual.val(), new isInstanceOf<Function>());
+          expect(actual.val()("-"), "[[- 1]]");
+        });
+      });
+    });
+  });
+}
diff --git a/test/mustache_context_test.dart b/test/mustache_context_test.dart
index bbec108..0478472 100644
--- a/test/mustache_context_test.dart
+++ b/test/mustache_context_test.dart
@@ -1,5 +1,4 @@
-library mustache_context_tests;
-
+import 'dart:mirrors';
 import 'package:test/test.dart';
 import 'package:mustache4dart/mustache_context.dart';
 
@@ -12,48 +11,26 @@
         'nodes': [contextY]
       };
       var ctx = new MustacheContext(contextX);
-      expect(ctx['nodes'], isNotNull);
-      expect(ctx['nodes'] is Iterable, isTrue);
-      expect((ctx['nodes'] as Iterable).length, 1);
-      (ctx['nodes'] as Iterable).forEach((n) {
-        expect(n['content'](), 'Y');
-        expect(n['nodes'].length, 0);
+      expect(ctx.field('nodes'), isNotNull);
+      expect(ctx.field('nodes') is Iterable, isTrue);
+      expect((ctx.field('nodes') as Iterable).length, 1);
+      (ctx.field('nodes') as Iterable).forEach((n) {
+        expect(n.field('content').value(), 'Y');
+        expect(n.field('nodes').length, 0);
       });
     });
 
     test('Direct interpolation', () {
       var ctx = new MustacheContext({'n1': 1, 'n2': 2.0, 's': 'some string'});
-      expect(ctx['n1']['.'](), '1');
-      expect(ctx['n2']['.'](), '2.0');
-      expect(ctx['s']['.'](), 'some string');
-    });
+      expect(ctx.field('n1').field('.').value(), '1');
+      expect(ctx.field('n2').field('.').value(), '2.0');
+      expect(ctx.field('s').field('.').value(), 'some string');
+    }, testOn: "vm");
 
     test('Direct list interpolation', () {
       var list = [1, 'two', 'three', '4'];
       var ctx = new MustacheContext(list);
-      expect(ctx['.'] is Iterable, isTrue);
-    });
-
-    group('rootContextString()', () {
-      test('should delegate to context toString()', () {
-        var map = {'Simple': 'Map'};
-        expect(new MustacheContext(map).rootContextString, map.toString());
-      });
-
-      test('should delegate to root context toString()', () {
-        var root = new _Person('George', 'George', new _Person('Nick', 'Nick'));
-        var ctx = new MustacheContext(root);
-        expect(ctx['parent'].rootContextString, root.toString());
-      });
-
-      test('should also work with iterrables', () {
-        var list = [
-          {'Map': '1'},
-          {'Map': '2'}
-        ];
-        var ctx = new MustacheContext(list);
-        expect(ctx.rootContextString, list.toString());
-      });
+      expect(ctx.field('.') is Iterable, isTrue);
     });
   });
 
@@ -71,14 +48,14 @@
     test('should return the result of the [] operator', () {
       dynamic ctx = new MustacheContext({'key1': 'value1'});
       ctx.useMirrors = false;
-      expect(ctx['key1'](), 'value1');
+      expect(ctx.field('key1').value(), 'value1');
     });
 
     test('should not be able to analyze classes with reflectioon', () {
       var contactInfo = new _ContactInfo('type', 'value');
       dynamic ctx = new MustacheContext(contactInfo, parent: null);
       ctx.useMirrors = false;
-      expect(ctx['type'], isNull);
+      expect(ctx.field('type'), isNull);
     });
 
     //TODO: add check for lambda returned from within a map
@@ -86,19 +63,19 @@
 
   test('Simple context with map', () {
     var ctx = new MustacheContext({'k1': 'value1', 'k2': 'value2'});
-    expect(ctx['k1'](), 'value1');
-    expect(ctx['k2'](), 'value2');
-    expect(ctx['k3'], null);
+    expect(ctx.field('k1').value(), 'value1');
+    expect(ctx.field('k2').value(), 'value2');
+    expect(ctx.field('k3'), null);
   });
 
   test('Simple context with object', () {
     var ctx = new MustacheContext(new _Person('Γιώργος', 'Βαλοτάσιος'));
-    expect(ctx['name'](), 'Γιώργος');
-    expect(ctx['lastname'](), 'Βαλοτάσιος');
-    expect(ctx['last'], null);
-    expect(ctx['fullname'](), 'Γιώργος Βαλοτάσιος');
-    expect(ctx['reversedName'](), 'ςογρώιΓ');
-    expect(ctx['reversedLastName'](), 'ςοισάτολαΒ');
+    expect(ctx.field('name').value(), 'Γιώργος');
+    expect(ctx.field('lastname').value(), 'Βαλοτάσιος');
+    expect(ctx.field('last'), null);
+    expect(ctx.field('fullname').value(), 'Γιώργος Βαλοτάσιος');
+    expect(ctx.field('reversedName'), null);
+    expect(ctx.field('reversedLastName').value(), 'ςοισάτολαΒ');
   });
 
   test('Simple map with list of maps', () {
@@ -111,7 +88,7 @@
         }
       ]
     });
-    expect(ctx['k'].length, 3);
+    expect(ctx.field('k').length, 3);
   });
 
   test('Map with list of lists', () {
@@ -126,9 +103,9 @@
         }
       ]
     });
-    expect(ctx['k'] is Iterable, isTrue);
-    expect((ctx['k'] as Iterable).length, 2);
-    expect((ctx['k'] as Iterable).last['k3'].length, 2);
+    expect(ctx.field('k') is Iterable, isTrue);
+    expect((ctx.field('k') as Iterable).length, 2);
+    expect((ctx.field('k') as Iterable).last.field('k3').length, 2);
   });
 
   test('Object with iterables', () {
@@ -141,11 +118,12 @@
     }));
     p.contactInfos.add(new _ContactInfo('skype', 'some1'));
     var ctx = new MustacheContext(p);
-    var contactInfos = ctx['contactInfos'];
+    var contactInfos = ctx.field('contactInfos');
     expect(contactInfos is Iterable, isTrue);
     var iterableContactInfos = contactInfos as Iterable;
     expect(iterableContactInfos.length, 2);
-    expect(iterableContactInfos.first['value']['Num'](), '31');
+    expect(
+        iterableContactInfos.first.field('value').field('Num').value(), '31');
   });
 
   test('Deep search with object', () {
@@ -156,33 +134,34 @@
     }
 
     MustacheContext ctx = new MustacheContext(p);
-    expect(ctx['name'](), 'name1');
-    expect(ctx['parent']['lastname'](), 'lastname2');
-    expect(ctx['parent']['parent']['fullname'](), 'name3 lastname3');
+    expect(ctx.field('name').value(), 'name1');
+    expect(ctx.field('parent').field('lastname').value(), 'lastname2');
+    expect(ctx.field('parent').field('parent').field('fullname').value(),
+        'name3 lastname3');
   });
 
   test('simple MustacheFunction value', () {
     var t = new _Transformer();
     var ctx = new MustacheContext(t);
-    var f = ctx['transform'];
+    var f = ctx.field('transform');
 
     expect(f.isLambda, true);
-    expect(f('123 456 777'), t.transform('123 456 777'));
+    expect(f.value('123 456 777'), t.transform('123 456 777'));
   });
 
   test('MustacheFunction from anonymus function', () {
     var map = {'transform': (String val) => "$val!"};
     var ctx = new MustacheContext(map);
-    var f = ctx['transform'];
+    var f = ctx.field('transform');
 
     expect(f.isLambda, true);
-    expect(f('woh'), 'woh!');
+    expect(f.value('woh'), 'woh!');
   });
 
   test('Dotted names', () {
     var ctx =
         new MustacheContext({'person': new _Person('George', 'Valotasios')});
-    expect(ctx['person.name'](), 'George');
+    expect(ctx.field('person.name').value(), 'George');
   });
 
   test('Context with another context', () {
@@ -191,9 +170,9 @@
           'a': {'one': 1},
           'b': {'two': 2}
         }));
-    expect(ctx['name'](), 'George');
-    expect(ctx['a']['one'](), '1');
-    expect(ctx['b']['two'](), '2');
+    expect(ctx.field('name').value(), 'George');
+    expect(ctx.field('a').field('one').value(), '1');
+    expect(ctx.field('b.two').value(), '2');
   });
 
   test('Deep subcontext test', () {
@@ -207,27 +186,30 @@
       'b': {'two': 2},
       'c': {'three': 3}
     });
-    expect(ctx['a'], isNotNull, reason: "a should exists when using $map");
-    expect(ctx['a']['one'](), '1');
-    expect(ctx['a']['two'], isNull);
-    expect(ctx['a']['b'], isNotNull,
+    expect(ctx.field('a'), isNotNull,
+        reason: "a should exists when using $map");
+    expect(ctx.field('a').field('one').value(), '1');
+    expect(ctx.field('a').field('two'), isNull);
+    expect(ctx.field('a').field('b'), isNotNull,
         reason: "a.b should exists when using $map");
-    expect(ctx['a']['b']['one'](), '1',
+    expect(ctx.field('a').field('b').field('one').value(), '1',
         reason: "a.b.one == a.own when using $map");
-    expect(ctx['a']['b']['two'](), '2',
+    expect(ctx.field('a').field('b').field('two').value(), '2',
         reason: "a.b.two == b.two when using $map");
-    expect(ctx['a']['b']['three'], isNull);
-    expect(ctx['a']['b']['c'], isNotNull,
+    expect(ctx.field('a').field('b').field('three'), isNull);
+    expect(ctx.field('a').field('b').field('c'), isNotNull,
         reason: "a.b.c should not be null when using $map");
 
-    expect(ctx['a']['b']['c']['one'](), '1',
+    var abc = ctx.field('a').field('b').field('c');
+    expect(abc.field('one').value(), '1',
         reason: "a.b.c.one == a.one when using $map");
-    expect(ctx['a']['b']['c']['two'](), '2',
+    expect(abc.field('two').value(), '2',
         reason: "a.b.c.two == b.two when using $map");
-    expect(ctx['a']['b']['c']['three'](), '3');
+    expect(abc.field('three').value(), '3');
   });
 }
 
+@MirrorsUsed()
 class _Person {
   final name;
   final lastname;
@@ -238,8 +220,6 @@
 
   get fullname => "$name $lastname";
 
-  getReversedName() => _reverse(name);
-
   static _reverse(String str) {
     StringBuffer out = new StringBuffer();
     for (int i = str.length; i > 0; i--) {
@@ -251,6 +231,7 @@
   reversedLastName() => _reverse(lastname);
 }
 
+@MirrorsUsed()
 class _ContactInfo {
   final String type;
   final value;
@@ -258,6 +239,7 @@
   _ContactInfo(this.type, this.value);
 }
 
+@MirrorsUsed()
 class _Transformer {
   String transform(String val) => "<b>$val</b>";
 }
diff --git a/test/mustache_issues_test.dart b/test/mustache_issues_test.dart
index 239aa6f..79cc7f3 100644
--- a/test/mustache_issues_test.dart
+++ b/test/mustache_issues_test.dart
@@ -1,5 +1,3 @@
-library mustache_issues;
-
 import 'dart:io';
 import 'dart:convert';
 import 'package:test/test.dart';
@@ -7,7 +5,8 @@
 import 'package:mustache4dart/mustache_context.dart';
 
 class A {
-  String getBar() => 'bar';
+  final bar = 'bar';
+
   String get foo => 'foo';
 }
 
@@ -25,55 +24,70 @@
 
 void main() {
   group('mustache4dart issues', () {
-    test('#9',
-        () => expect(render("{{#sec}}[{{var}}]{{/sec}}", {'sec': 42}), '[]'));
+    test(
+        '#9: use empty strings for non existing variable',
+        () => expect(
+            render("{{#sec}}[{{variable}}]{{/sec}}", {'sec': 42}), '[]'));
+
     test('#10',
         () => expect(render('|\n{{#bob}}\n{{/bob}}\n|', {'bob': []}), '|\n|'));
+
     test(
         '#11',
         () => expect(
             () => render("{{#sec}}[{{var}}]{{/somethingelse}}", {'sec': 42}),
             throwsFormatException));
+
     test('#12: Write to a StringSink', () {
       StringSink out = new StringBuffer();
       StringSink outcome = render("{{name}}!", {'name': "George"}, out: out);
       expect(out, outcome);
       expect(out.toString(), "George!");
     });
-    test('#16', () => expect(render('{{^x}}x{{/x}}!!!', null), 'x!!!'));
-    test(
-        '#16 root cause: For null objects the value of any property should be null',
-        () {
-      var ctx = new MustacheContext(null);
-      expect(ctx['xxx'], null);
-      expect(ctx['123'], null);
-      expect(ctx[''], null);
-      expect(ctx[null], null);
-    });
-    test(
-        '#17',
-        () => expect(
-            render('{{#a}}[{{{a}}}|{{b}}]{{/a}}', {'a': 'aa', 'b': 'bb'}),
-            '[aa|bb]'));
-    test('#17 root cause: setting the same context as a subcontext', () {
-      var ctx = new MustacheContext({'a': 'aa', 'b': 'bb'});
-      expect(ctx, isNotNull);
-      expect(ctx['a'].toString(), isNotNull);
 
-      //Here lies a problem if the subaa.other == suba
-      expect(ctx['a']['a'].toString(), isNotNull);
+    group('#16', () {
+      test('side effect',
+          () => expect(render('{{^x}}x{{/x}}!!!', null), 'x!!!'));
+
+      test(
+          'root cause: For null objects the value of any property should be null',
+          () {
+        var ctx = new MustacheContext(null);
+        expect(ctx.field('xxx'), null);
+        expect(ctx.field('123'), null);
+        expect(ctx.field(''), null);
+        expect(ctx.field(null), null);
+      });
     });
+
+    group('#17', () {
+      test(
+          'side effect',
+          () => expect(
+              render('{{#a}}[{{{a}}}|{{b}}]{{/a}}', {'a': 'aa', 'b': 'bb'}),
+              '[aa|bb]'));
+
+      test('root cause: setting the same context as a subcontext', () {
+        final ctx = new MustacheContext({'a': 'aa', 'b': 'bb'});
+        expect(ctx, isNotNull);
+        expect(ctx.field('a').toString(), isNotNull);
+
+        //Here lies a problem if the subaa.other == suba
+        expect(ctx.field('a').field('a').toString(), isNotNull);
+      });
+    });
+
     test('#20', () {
       var currentPath = Directory.current.path;
       if (!currentPath.endsWith('/test')) {
         currentPath = "$currentPath/test";
       }
-      var template = new File("$currentPath/lorem-ipsum.txt")
+      final template = new File("$currentPath/lorem-ipsum.txt")
           .readAsStringSync(encoding: UTF8);
 
-      String out = render(template, {'ma': 'ma'});
+      final String out = render(template, {'ma': 'ma'});
       expect(out, template);
-    });
+    }, onPlatform: {"js": new Skip("io is not available on a browser")});
 
     test('#25', () {
       var ctx = {
@@ -109,7 +123,7 @@
     });
 
     test('#30', () {
-      var txt = '''
+      final txt = '''
 
 <div>
   <h1>Hello World!</h1>
@@ -120,7 +134,7 @@
     });
 
     test('#33', () {
-      var b = new B();
+      final b = new B();
       expect(render('{{b.foo}}', {'b': b}), 'foo');
       expect(render('{{b.bar}}', {'b': b}), 'bar');
     });
@@ -146,7 +160,7 @@
     });
 
     test('#44 should provide a way to check for non empty lists', () {
-      var map = {
+      final map = {
         'list': [1, 2]
       };
       expect(
diff --git a/test/mustache_specs_test.dart b/test/mustache_specs_test.dart
index 689fde5..b560fed 100644
--- a/test/mustache_specs_test.dart
+++ b/test/mustache_specs_test.dart
@@ -1,4 +1,4 @@
-library mustache_specs;
+@TestOn('vm')
 
 import 'dart:io';
 import 'dart:convert';
diff --git a/test/mustache_test.dart b/test/mustache_test.dart
index 7768c51..0c1b050 100644
--- a/test/mustache_test.dart
+++ b/test/mustache_test.dart
@@ -1,5 +1,3 @@
-library mustache_tests;
-
 import 'package:test/test.dart';
 import 'package:mustache4dart/mustache4dart.dart';
 
@@ -19,7 +17,7 @@
 
   lambda2(String s, {nestedContext}) => "2" + render(s, nestedContext) + "2";
 
-  lambda3() => () => "[3]";
+  lambda3() => "[3]";
 
   lambda4({nestedContext}) => "4${nestedContext != null}4";
 }
@@ -161,9 +159,9 @@
       });
 
       test('Provide lambdas as a method() within a class', () {
-        var context = new B([new A('a'), new A('b')]);
+        final context = new B([new A('a'), new A('b')]);
 
-        var template =
+        final template =
             '''{{#map.things}}{{#lambda3}}{{name}}{{/lambda3}}|{{/map.things}}''';
 
         expect(render(template, context), "[3]|[3]|");
@@ -177,7 +175,7 @@
 
         expect(render(template, context), "4true4|4true4|");
       });
-    });
+    }, onPlatform: {'js': new Skip("Broken mirrors, should be investigated")});
   });
 }