Anonymous avatar Anonymous committed 2a25e77 Merge

Added in fixes from the official branch

Comments (0)

Files changed (16)

 get Int true? Bool = this != 0
 get String true? Bool = this count > 0
 get Nothing true? Bool = false
-
-// TODO(bob): Hack. This fixes a weird infinite regress problem. Let's say you
-// have an expression like:
-//
-//   foo()
-//
-// It turns out "foo" is undefined. So when we evaluate "foo" , we fail to find
-// it and return nothing. Then we try to evaluate "nothing()" (the apply).
-// nothing is not a function, so that gets translated to "nothing call()". So
-// we look up "call" on nothing, which fails, returning "nothing". Now we try
-// to evaluate "nothing()"... stack overflow.
-//
-// This short-circuits that by actually making nothing callable.
-def Nothing call(arg) nothing
 import("language/message.mag")
 import("language/newline.mag")
 import("language/or.mag")
+import("language/record.mag")
 import("language/return.mag")
 import("language/string.mag")
 import("language/var.mag")

spec/language/newline.mag

         i _1 shouldBe(2)
     end
 
-    // TODO(bob): Skip for now. Object literals are broken until we generate
-    // classes with getters for their fields.
-    /*
     it should("be ignored after a colon") with
         var a = x:
             1 y: 2
         a x shouldBe(1)
         a y shouldBe(2)
     end
-    */
     
     // TODO(bob): Skip for now. Periods are not used by the grammar.
     /*

spec/language/record.mag

+specify("A record expression") with
+    it should("evaluate the fields left to right") with
+        var e = ""
+        var rec = z: (e = e + "1") x: (e = e + "2") y: (e = e + "3")
+        e shouldBe("123")
+    end
+end

src/com/stuffwithstuff/magpie/Magpie.java

     System.out.println();
     
     Interpreter interpreter = new Interpreter(new ScriptInterpreterHost());
+    
+    // The REPL runs and imports relative to the current directory.
+    interpreter.pushScriptPath(".");
+    
     try {
       Script.loadBase(interpreter);
       

src/com/stuffwithstuff/magpie/interpreter/Checker.java

 import com.stuffwithstuff.magpie.ast.Expr;
 import com.stuffwithstuff.magpie.ast.FnExpr;
 import com.stuffwithstuff.magpie.parser.Position;
+import com.stuffwithstuff.magpie.util.Expect;
 
 /**
  * The type checker. Given an interpreter this walks through the entire global
     for (Entry<String, Obj> entry : mInterpreter.getGlobals().entries()) {
       if (entry.getValue() instanceof FnObj) {
         FnObj function = (FnObj)entry.getValue();
-        checkFunction(function.getFunction(), mInterpreter.getNothingType(),
+        checkFunction(function.getFunction(), mInterpreter.getNothingClass(),
             staticContext);
       } else if (entry.getValue() instanceof ClassObj) {
         checkClass((ClassObj)entry.getValue());
           function.getType().getReturnType()));
     }
     
-    return mInterpreter.invokeMethod(mInterpreter.getFunctionType(),
+    return mInterpreter.invokeMethod(mInterpreter.getFunctionClass(),
         Identifiers.NEW_TYPE, mInterpreter.createTuple(
             paramType, returnType,
             mInterpreter.createBool(function.isStatic())));
         mInterpreter.createTopLevelContext());
 
     Scope globals = typeScope(mInterpreter.getGlobals());
-    EvalContext context = new EvalContext(globals, mInterpreter.getNothingType());
+    EvalContext context = new EvalContext(globals,
+        mInterpreter.getNothingClass());
 
     // Get the expression's type.
     Obj type = checker.check(expr, context, true);
 
     Scope scope = new Scope(parent);
     for (Entry<String, Obj> entry : valueScope.entries()) {
-      Obj type = mInterpreter.getProperty(Position.none(), entry.getValue(),
+      Obj type = mInterpreter.getMember(Position.none(), entry.getValue(),
           Identifiers.TYPE);
       scope.define(entry.getKey(), type);
     }
    */
   public void checkTypes(Obj expected, Obj actual,
       boolean nothingOverrides, Position position, String message) {
+    Expect.notNull(expected);
+    Expect.notNull(actual);
     
     boolean success;
-    if (nothingOverrides && (expected == mInterpreter.getNothingType())) {
+    if (nothingOverrides && (expected == mInterpreter.getNothingClass())) {
       success = true;
     } else {
       Obj matches = mInterpreter.invokeMethod(expected,

src/com/stuffwithstuff/magpie/interpreter/ClassObj.java

   public Obj instantiate(Object primitiveValue) {
     return new Obj(this, primitiveValue);
   }
-  
+    
   public String getName() { return mName; }
   
   public ClassObj getParent() { return mParent; }

src/com/stuffwithstuff/magpie/interpreter/EvalContext.java

       // Make sure the argument's structure matches our expected parameter list.
       // If it doesn't, ignore extra tuple fields and pad missing ones with
       // nothing.
-      if (value.getClassObj() != interpreter.getTupleType()) {
+      if (value.getClassObj() != interpreter.getTupleClass()) {
         // Not a tuple and we're expecting it to be, so just bind it to the
         // first parameter and define the others as nothing.
         define(names.get(0), value);

src/com/stuffwithstuff/magpie/interpreter/ExprChecker.java

   public Obj check(Expr expr, EvalContext context, boolean allowNever) {
     Obj result = expr.accept(this, context);
     
-    if (!allowNever && result == mInterpreter.getNeverType()) {
+    if (!allowNever && result == mInterpreter.getNeverClass()) {
       mChecker.addError(expr.getPosition(),
           "An early return here will cause unreachable code.");
     }
         mChecker.addError(expr.getTarget().getPosition(),
             "The expression \"%s\" does not evaluate to a static function.",
             expr.getTarget());
-        return mInterpreter.getNothingType();
+        return mInterpreter.getNothingClass();
       }
       
       FnExpr staticFn = (FnExpr)targetType.getValue();
         targetType = getMemberType(expr.getPosition(), targetType,
             Identifiers.CALL);
   
-        if (targetType == mInterpreter.nothing()) {
+        if (targetType == mInterpreter.getNothingClass()) {
           mChecker.addError(expr.getPosition(),
               "Target of type %s is not a function and does not have a 'call' method.",
               targetType);
-          return mInterpreter.getNothingType();
+          return mInterpreter.getNothingClass();
         }
       }
       
           "Could not find a setter \"%s\" on %s when checking.",
           expr.getName(), receiverType);
 
-      return mInterpreter.getNothingType();
+      return mInterpreter.getNothingClass();
     }
     
     // Make sure the assigned value if compatible with the setter.
 
   @Override
   public Obj visit(BoolExpr expr, EvalContext context) {
-    return mInterpreter.getBoolType();
+    return mInterpreter.getBoolClass();
   }
   
   @Override
   public Obj visit(BreakExpr expr, EvalContext context) {
     if (context.isInLoop()) {
-      return mInterpreter.getNeverType();
+      return mInterpreter.getNeverClass();
     }
     
     mChecker.addError(expr.getPosition(),
         "A break expression should not appear outside of a for loop.");
-    return mInterpreter.getNothingType();
+    return mInterpreter.getNothingClass();
   }
 
   @Override
   public Obj visit(ExpressionExpr expr, EvalContext context) {
-    return mInterpreter.getExpressionType();
+    return mInterpreter.getExpressionClass();
   }
 
   @Override
 
   @Override
   public Obj visit(IntExpr expr, EvalContext context) {
-    return mInterpreter.getIntType();
+    return mInterpreter.getIntClass();
   }
 
   @Override
     check(expr.getBody(), context);
     
     // Loops always return nothing.
-    return mInterpreter.getNothingType();
+    return mInterpreter.getNothingClass();
   }
 
   @Override
 
   @Override
   public Obj visit(NothingExpr expr, EvalContext context) {
-    return mInterpreter.getNothingType();
+    return mInterpreter.getNothingClass();
   }
-  
-  @Override
-  public Obj visit(RecordExpr expr, EvalContext context) {
-    // TODO(bob): Need to create a structural type here.
-    // Also, should check for duplicate fields?
-    return mInterpreter.getObjectType();
-  }
-  
+    
   @Override
   public Obj visit(OrExpr expr, EvalContext context) {
     // TODO(bob): Should eventually check that both arms implement ITrueable
   }
 
   @Override
+  public Obj visit(RecordExpr expr, EvalContext context) {
+    // TODO(bob): Need to create a structural type here.
+    // Also, should check for duplicate fields?
+    return mInterpreter.getRecordClass();
+  }
+
+  @Override
   public Obj visit(ReturnExpr expr, EvalContext context) {
     // Get the type of value being returned.
     Obj returnedType = check(expr.getValue(), context);
     mReturnedTypes.add(returnedType);
     
-    return mInterpreter.getNeverType();
+    return mInterpreter.getNeverClass();
   }
   
   @Override
   
   @Override
   public Obj visit(StringExpr expr, EvalContext context) {
-    return mInterpreter.getStringType();
+    return mInterpreter.getStringClass();
   }
 
   @Override
   @Override
   public Obj visit(TypeofExpr expr, EvalContext context) {
     // TODO(bob): This should eventually return Type | Nothing
-    return mInterpreter.getNothingType();
+    return mInterpreter.getNothingClass();
   }
 
   @Override
   
   private Obj orTypes(Obj left, Obj right) {
     // Never is omitted.
-    if (left == mInterpreter.getNeverType()) return right;
-    if (right == mInterpreter.getNeverType()) return left;
+    if (left == mInterpreter.getNeverClass()) return right;
+    if (right == mInterpreter.getNeverClass()) return left;
     
     return mInterpreter.invokeMethod(left, Identifiers.OR, right);
   }
           "Could not find a member named \"%s\" on %s when checking.",
           name, receiverType);
       
-      return mInterpreter.getNothingType();
+      return mInterpreter.getNothingClass();
     }
 
     return memberType;

src/com/stuffwithstuff/magpie/interpreter/ExprEvaluator.java

 
   @Override
   public Obj visit(ExpressionExpr expr, EvalContext context) {
-    return mInterpreter.getExpressionType().instantiate(expr.getBody());
+    return mInterpreter.getExpressionClass().instantiate(expr.getBody());
   }
   
   @Override
   public Obj visit(MessageExpr expr, EvalContext context) {
     Obj receiver = evaluate(expr.getReceiver(), context);
     
+    // If there is an implicit receiver, try to determine who to send the
+    // message to.
     if (receiver == null) {
       // Just a name, so maybe it's a variable.
       Obj variable = context.lookUp(expr.getName());
       receiver = context.getThis();
     }
     
-    return mInterpreter.getProperty(expr.getPosition(), receiver,
-        expr.getName());
+    return mInterpreter.getMember(expr.getPosition(), receiver, expr.getName());
   }
   
   @Override
   }
   
   @Override
-  public Obj visit(RecordExpr expr, EvalContext context) {
-    Obj obj = mInterpreter.getObjectType().instantiate();
-    
-    // Define the fields.
-    for (Pair<String, Expr> entry : expr.getFields()) {
-      Obj value = evaluate(entry.getValue(), context);
-      obj.setField(entry.getKey(), value);
-    }
-    
-    return obj;
-  }
-
-  @Override
   public Obj visit(OrExpr expr, EvalContext context) {
     Obj left = evaluate(expr.getLeft(), context);
     
   }
 
   @Override
+  public Obj visit(RecordExpr expr, EvalContext context) {
+    Obj obj = mInterpreter.getRecordClass().instantiate();
+    
+    // Define the fields.
+    for (Pair<String, Expr> entry : expr.getFields()) {
+      Obj value = evaluate(entry.getValue(), context);
+      obj.setField(entry.getKey(), value);
+    }
+    
+    return obj;
+  }
+
+  @Override
   public Obj visit(ReturnExpr expr, EvalContext context) {
     Obj value = evaluate(expr.getValue(), context);
     throw new ReturnException(value);
   }
   
   private boolean isTruthy(Expr expr, Obj receiver) {
-    Obj truthy = mInterpreter.getProperty(expr.getPosition(), receiver,
+    Obj truthy = mInterpreter.getMember(expr.getPosition(), receiver,
         Identifiers.IS_TRUE);
     return truthy.asBool();
   }

src/com/stuffwithstuff/magpie/interpreter/Interpreter.java

     mExpressionClass = createGlobalClass("Expression");
     mFnClass = createGlobalClass("Function");
     mIntClass = createGlobalClass("Int");
+    mRecordClass = createGlobalClass("Record");
     mRuntimeClass = createGlobalClass("Runtime");
 
     mStringClass = createGlobalClass("String");
     BuiltIns.register(FunctionBuiltIns.class, mFnClass);
     BuiltIns.register(IntBuiltIns.class, mIntClass);
     BuiltIns.register(ObjectBuiltIns.class, mObjectClass);
+    BuiltIns.register(RecordBuiltIns.class, mRecordClass);
     BuiltIns.register(RuntimeBuiltIns.class, mRuntimeClass);
     BuiltIns.register(StringBuiltIns.class, mStringClass);
   }
     mainFn.invoke(this, mNothing, mNothing);
   }
   
-  public Obj getProperty(Position position, Obj receiver, String name) {
+  public Obj getMember(Position position, Obj receiver, String name) {
     // Look for a getter.
     Callable getter = receiver.getClassObj().findGetter(name);
     if (getter != null) {
       return new FnObj(mFnClass, receiver, method);
     }
    
+    // Look for a field.
+    Obj value = receiver.getField(name);
+    if (value != null) return value;
+    
     // If all else fails, try finding a matching native Java method on the
     // primitive value.
     // TODO(bob): The bound method refactoring broke this. Unbreak.
     Expect.notNull(receiver);
     Expect.notNull(arg);
     
-    Obj resolved = getProperty(position, receiver, name);
+    Obj resolved = getMember(position, receiver, name);
     return apply(position, resolved, arg);
   }
 
    */
   public Obj nothing() { return mNothing; }
 
+  public ClassObj getArrayClass() { return mArrayClass; }
+  public ClassObj getBoolClass() { return mBoolClass; }
+  public ClassObj getDynamicClass() { return mDynamicClass; }
+  public ClassObj getExpressionClass() { return mExpressionClass; }
+  public ClassObj getFunctionClass() { return mFnClass; }
+  public ClassObj getIntClass() { return mIntClass; }
   public ClassObj getMetaclass() { return mClass; }
-  public ClassObj getBoolType() { return mBoolClass; }
-  public ClassObj getDynamicType() { return mDynamicClass; }
-  public ClassObj getExpressionType() { return mExpressionClass; }
-  public ClassObj getFunctionType() { return mFnClass; }
-  public ClassObj getIntType() { return mIntClass; }
-  public ClassObj getNothingType() { return mNothingClass; }
-  public ClassObj getObjectType() { return mObjectClass; }
-  public ClassObj getStringType() { return mStringClass; }
-  public ClassObj getTupleType() { return mTupleClass; }
-  public ClassObj getNeverType() { return mNeverClass; }
-  public ClassObj getArrayType() { return mArrayClass; }
+  public ClassObj getNeverClass() { return mNeverClass; }
+  public ClassObj getNothingClass() { return mNothingClass; }
+  public ClassObj getObjectClass() { return mObjectClass; }
+  public ClassObj getRecordClass() { return mRecordClass; }
+  public ClassObj getStringClass() { return mStringClass; }
+  public ClassObj getTupleClass() { return mTupleClass; }
   
   public Obj createArray(List<Obj> elements) {
     return mArrayClass.instantiate(elements);
   }
   
   public String evaluateToString(Obj value) {
-    return getProperty(Position.none(), value, Identifiers.STRING).asString();
+    return getMember(Position.none(), value, Identifiers.STRING).asString();
   }
 
   public void pushScriptPath(String path) {
   private final ClassObj mNothingClass;
   private final ClassObj mNeverClass;
   private final ClassObj mObjectClass;
+  private final ClassObj mRecordClass;
   private final ClassObj mRuntimeClass;
   private final ClassObj mStringClass;
   private final ClassObj mTupleClass;

src/com/stuffwithstuff/magpie/interpreter/builtin/ClassBuiltIns.java

       }
   
       // Member not found.
-      return interpreter.getNothingType();
+      return interpreter.getNothingClass();
     }
   }
   
       // Create the class object itself. This will hold the instance methods for
       // objects of the class.
       ClassObj classObj = new ClassObj(metaclass, name,
-          interpreter.getObjectType());
+          interpreter.getObjectClass());
       
       return classObj;
     }
       ClassObj parent = classObj.getParent();
       
       // If a class has no parent, its parent is implicitly Object.
-      if (parent == null) return interpreter.getObjectType();
+      if (parent == null) return interpreter.getObjectClass();
       
       return parent;
     }

src/com/stuffwithstuff/magpie/interpreter/builtin/RecordBuiltIns.java

+package com.stuffwithstuff.magpie.interpreter.builtin;
+
+public class RecordBuiltIns {
+}

src/com/stuffwithstuff/magpie/interpreter/builtin/RuntimeBuiltIns.java

       
       FnObj function = (FnObj)arg;
       EvalContext staticContext = interpreter.createTopLevelContext();
-      checker.checkFunction(function.getFunction(), interpreter.getNothingType(),
-          staticContext);
+      checker.checkFunction(function.getFunction(),
+          interpreter.getNothingClass(), staticContext);
       
       return translateErrors(interpreter, checker.getErrors());
     }

src/com/stuffwithstuff/magpie/parser/MagpieParser.java

     Expr message = primary();
     
     while (true) {
-      if (match(TokenType.NAME)) {
+      if (lookAhead(TokenType.NAME, TokenType.COLON)) {
+        // Do nothing. This ensures we don't consume a name and think it's a
+        // message when it's really a field name in a record.
+        break;
+      } else if (match(TokenType.NAME)) {
         message = new MessageExpr(last(1).getPosition(), message,
             last(1).getString());
       } else if (match(TokenType.LEFT_BRACKET)) {

src/com/stuffwithstuff/magpie/util/Expect.java

   }
   
   public static void notNull(Object arg) {
-    if (arg == null) throw new NullPointerException("Argument cannot be null.");
+    if (arg == null) {
+      throw new NullPointerException("Argument cannot be null.");
+    }
   }
   
   public static void positive(int arg) {
-    if (arg <= 0) throw new IllegalArgumentException("Argument must be positive.");
+    if (arg <= 0) {
+      throw new IllegalArgumentException("Argument must be positive.");
+    }
   }
   
   private Expect() {}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.