about summary refs log tree commit diff
path: root/src/libexpr
diff options
context:
space:
mode:
authorChristian Theune <ct@flyingcircus.io>2016-01-04T23·40+0100
committerChristian Theune <ct@flyingcircus.io>2016-01-04T23·40+0100
commit14ebde52893263930cdcde1406cc91cc5c42556f (patch)
tree2b65c11405f9aef33eb284208716f9cb5b434599 /src/libexpr
parentb8258a4475726b56a4caa6553568c67a343a091d (diff)
First hit at providing support for floats in the language.
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/eval.cc56
-rw-r--r--src/libexpr/eval.hh1
-rw-r--r--src/libexpr/get-drvs.cc21
-rw-r--r--src/libexpr/get-drvs.hh3
-rw-r--r--src/libexpr/json-to-value.cc25
-rw-r--r--src/libexpr/lexer.l7
-rw-r--r--src/libexpr/nixexpr.cc9
-rw-r--r--src/libexpr/nixexpr.hh9
-rw-r--r--src/libexpr/parser.y3
-rw-r--r--src/libexpr/primops.cc41
-rw-r--r--src/libexpr/value-to-json.cc4
-rw-r--r--src/libexpr/value-to-xml.cc4
-rw-r--r--src/libexpr/value.hh11
13 files changed, 171 insertions, 23 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index df1600bc1963..e5a108a4385e 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -128,6 +128,8 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con
     case tExternal:
         str << *v.external;
         break;
+    case tFloat:
+        str << v.fpoint;
     default:
         throw Error("invalid value");
     }
@@ -161,6 +163,7 @@ string showType(const Value & v)
         case tPrimOp: return "a built-in function";
         case tPrimOpApp: return "a partially applied built-in function";
         case tExternal: return v.external->showType();
+        case tFloat: return "a float";
     }
     abort();
 }
@@ -577,6 +580,12 @@ Value * ExprInt::maybeThunk(EvalState & state, Env & env)
     return &v;
 }
 
+Value * ExprFloat::maybeThunk(EvalState & state, Env & env)
+{
+    nrAvoided++;
+    return &v;
+}
+
 Value * ExprPath::maybeThunk(EvalState & state, Env & env)
 {
     nrAvoided++;
@@ -664,6 +673,11 @@ void ExprInt::eval(EvalState & state, Env & env, Value & v)
 }
 
 
+void ExprFloat::eval(EvalState & state, Env & env, Value & v)
+{
+    v = this->v;
+}
+
 void ExprString::eval(EvalState & state, Env & env, Value & v)
 {
     v = this->v;
@@ -1209,6 +1223,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
     PathSet context;
     std::ostringstream s;
     NixInt n = 0;
+    NixFloat nf = 0;
 
     bool first = !forceString;
     ValueType firstType = tString;
@@ -1227,15 +1242,30 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
         }
 
         if (firstType == tInt) {
-            if (vTmp.type != tInt)
+            if (vTmp.type == tInt) {
+                n += vTmp.integer;
+            } else if (vTmp.type == tFloat) {
+                // Upgrade the type from int to float;
+                firstType = tFloat;
+                nf = n;
+                nf += vTmp.fpoint;
+            } else
                 throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp), pos);
-            n += vTmp.integer;
+        } else if (firstType == tFloat) {
+            if (vTmp.type == tInt) {
+                nf += vTmp.integer;
+            } else if (vTmp.type == tFloat) {
+                nf += vTmp.fpoint;
+            } else
+                throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp), pos);
         } else
             s << state.coerceToString(pos, vTmp, context, false, firstType == tString);
     }
 
     if (firstType == tInt)
         mkInt(v, n);
+    else if (firstType == tFloat)
+        mkFloat(v, nf);
     else if (firstType == tPath) {
         if (!context.empty())
             throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos);
@@ -1293,6 +1323,17 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
 }
 
 
+NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
+{
+    forceValue(v, pos);
+    if (v.type == tInt)
+        return v.integer;
+    else if (v.type != tFloat)
+        throwTypeError("value is %1% while a float was expected, at %2%", v, pos);
+    return v.fpoint;
+}
+
+
 bool EvalState::forceBool(Value & v)
 {
     forceValue(v);
@@ -1404,6 +1445,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
         if (v.type == tBool && v.boolean) return "1";
         if (v.type == tBool && !v.boolean) return "";
         if (v.type == tInt) return std::to_string(v.integer);
+        if (v.type == tFloat) return std::to_string(v.fpoint);
         if (v.type == tNull) return "";
 
         if (v.isList()) {
@@ -1465,6 +1507,13 @@ bool EvalState::eqValues(Value & v1, Value & v2)
        uniqList on a list of sets.)  Will remove this eventually. */
     if (&v1 == &v2) return true;
 
+    // Special case type-compatibility between float and int
+    if (v1.type == tInt && v2.type == tFloat)
+        return v1.integer == v2.fpoint;
+    if (v1.type == tFloat && v2.type == tInt)
+        return v1.fpoint == v2.integer;
+
+    // All other types are not compatible with each other.
     if (v1.type != v2.type) return false;
 
     switch (v1.type) {
@@ -1522,6 +1571,9 @@ bool EvalState::eqValues(Value & v1, Value & v2)
         case tExternal:
             return *v1.external == *v2.external;
 
+        case tFloat:
+            return v1.fpoint == v2.fpoint;
+
         default:
             throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2));
     }
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index eb55f6d4d431..13353a43f4e0 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -144,6 +144,7 @@ public:
 
     /* Force `v', and then verify that it has the expected type. */
     NixInt forceInt(Value & v, const Pos & pos);
+    NixFloat forceFloat(Value & v, const Pos & pos);
     bool forceBool(Value & v);
     inline void forceAttrs(Value & v);
     inline void forceAttrs(Value & v, const Pos & pos);
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 1002ee6285af..996c2c5f4975 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -106,7 +106,8 @@ bool DrvInfo::checkMeta(Value & v)
             if (!checkMeta(*i.value)) return false;
         return true;
     }
-    else return v.type == tInt || v.type == tBool || v.type == tString;
+    else return v.type == tInt || v.type == tBool || v.type == tString ||
+                v.type == tFloat;
 }
 
 
@@ -127,7 +128,7 @@ string DrvInfo::queryMetaString(const string & name)
 }
 
 
-int DrvInfo::queryMetaInt(const string & name, int def)
+NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
 {
     Value * v = queryMeta(name);
     if (!v) return def;
@@ -135,12 +136,26 @@ int DrvInfo::queryMetaInt(const string & name, int def)
     if (v->type == tString) {
         /* Backwards compatibility with before we had support for
            integer meta fields. */
-        int n;
+        NixInt n;
         if (string2Int(v->string.s, n)) return n;
     }
     return def;
 }
 
+NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
+{
+    Value * v = queryMeta(name);
+    if (!v) return def;
+    if (v->type == tFloat) return v->fpoint;
+    if (v->type == tString) {
+        /* Backwards compatibility with before we had support for
+           float meta fields. */
+        NixFloat n;
+        if (string2Float(v->string.s, n)) return n;
+    }
+    return def;
+}
+
 
 bool DrvInfo::queryMetaBool(const string & name, bool def)
 {
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 98f762494aa5..365c66c8d710 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -47,7 +47,8 @@ public:
     StringSet queryMetaNames();
     Value * queryMeta(const string & name);
     string queryMetaString(const string & name);
-    int queryMetaInt(const string & name, int def);
+    NixInt queryMetaInt(const string & name, NixInt def);
+    NixFloat queryMetaFloat(const string & name, NixFloat def);
     bool queryMetaBool(const string & name, bool def);
     void setMeta(const string & name, Value * v);
 
diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc
index 0898b560911b..7ef2b2c56227 100644
--- a/src/libexpr/json-to-value.cc
+++ b/src/libexpr/json-to-value.cc
@@ -105,17 +105,22 @@ static void parseJSON(EvalState & state, const char * & s, Value & v)
         mkString(v, parseJSONString(s));
     }
 
-    else if (isdigit(*s) || *s == '-') {
-        bool neg = false;
-        if (*s == '-') {
-            neg = true;
-            if (!*++s) throw JSONParseError("unexpected end of JSON number");
+    else if (isdigit(*s) || *s == '-' || *s == '.' ) {
+        // Buffer into a string first, then use built-in C++ conversions
+        std::string tmp_number;
+        ValueType number_type = tInt;
+
+        while (isdigit(*s) || *s == '-' || *s == '.' || *s == 'e' || *s == 'E') {
+            if (*s == '.' || *s == 'e' || *s == 'E')
+                number_type = tFloat;
+            tmp_number.append(*s++, 1);
+        }
+
+        if (number_type == tFloat) {
+            mkFloat(v, stod(tmp_number));
+        } else {
+            mkInt(v, stoi(tmp_number));
         }
-        NixInt n = 0;
-        // FIXME: detect overflow
-        while (isdigit(*s)) n = n * 10 + (*s++ - '0');
-        if (*s == '.' || *s == 'e') throw JSONParseError("floating point JSON numbers are not supported");
-        mkInt(v, neg ? -n : n);
     }
 
     else if (strncmp(s, "true", 4) == 0) {
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index 1f2957ec71ad..8f9d686a1c89 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -85,6 +85,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
 
 ID          [a-zA-Z\_][a-zA-Z0-9\_\'\-]*
 INT         [0-9]+
+FLOAT       {INT}[0-9]+\.[0-9]+[eE]-?{INT}
 PATH        [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+
 HPATH       \~(\/[a-zA-Z0-9\.\_\-\+]+)+
 SPATH       \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
@@ -123,6 +124,12 @@ or          { return OR_KW; }
                   throw ParseError(format("invalid integer ‘%1%’") % yytext);
               return INT;
             }
+{FLOAT}     { errno = 0;
+              yylval->n = strtod(yytext, 0);
+              if (errno != 0)
+                  throw ParseError(format("invalid float ‘%1%’") % yytext);
+              return FLOAT;
+            }
 
 \$\{        { PUSH_STATE(INITIAL); return DOLLAR_CURLY; }
 \{          { PUSH_STATE(INITIAL); return '{'; }
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 5bc8e4202a49..b2c9f0528ca9 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -68,6 +68,11 @@ void ExprInt::show(std::ostream & str)
     str << n;
 }
 
+void ExprFloat::show(std::ostream & str)
+{
+    str << nf;
+}
+
 void ExprString::show(std::ostream & str)
 {
     showString(str, s);
@@ -226,6 +231,10 @@ void ExprInt::bindVars(const StaticEnv & env)
 {
 }
 
+void ExprFloat::bindVars(const StaticEnv & env)
+{
+}
+
 void ExprString::bindVars(const StaticEnv & env)
 {
 }
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index ef07d4557fe8..5e7bc40c85c9 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -98,6 +98,15 @@ struct ExprInt : Expr
     Value * maybeThunk(EvalState & state, Env & env);
 };
 
+struct ExprFloat : Expr
+{
+    NixFloat nf;
+    Value v;
+    ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); };
+    COMMON_METHODS
+    Value * maybeThunk(EvalState & state, Env & env);
+};
+
 struct ExprString : Expr
 {
     Symbol s;
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index d34882f361cf..32c66c783328 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -244,6 +244,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
   nix::Formals * formals;
   nix::Formal * formal;
   nix::NixInt n;
+  nix::NixFloat nf;
   const char * id; // !!! -> Symbol
   char * path;
   char * uri;
@@ -264,6 +265,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %token <id> ID ATTRPATH
 %token <e> STR IND_STR
 %token <n> INT
+%token <nf> FLOAT
 %token <path> PATH HPATH SPATH
 %token <uri> URI
 %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
@@ -366,6 +368,7 @@ expr_simple
           $$ = new ExprVar(CUR_POS, data->symbols.create($1));
   }
   | INT { $$ = new ExprInt($1); }
+  | FLOAT { $$ = new ExprFloat($1); }
   | '"' string_parts '"' { $$ = $2; }
   | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
       $$ = stripIndentation(CUR_POS, data->symbols, *$2);
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 5b9ecc018f0e..ebc5ae143143 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -197,6 +197,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
         case tExternal:
             t = args[0]->external->typeOf();
             break;
+        case tFloat: t = "float"; break;
         default: abort();
     }
     mkString(v, state.symbols.create(t));
@@ -226,6 +227,12 @@ static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value
     mkBool(v, args[0]->type == tInt);
 }
 
+/* Determine whether the argument is a float. */
+static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceValue(*args[0]);
+    mkBool(v, args[0]->type == tFloat);
+}
 
 /* Determine whether the argument is a string. */
 static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v)
@@ -247,11 +254,17 @@ struct CompareValues
 {
     bool operator () (const Value * v1, const Value * v2) const
     {
+        if (v1->type == tFloat && v2->type == tInt)
+            return v1->fpoint < v2->integer;
+        if (v1->type == tInt && v2->type == tFloat)
+            return v1->integer < v2->fpoint;
         if (v1->type != v2->type)
             throw EvalError("cannot compare values of different types");
         switch (v1->type) {
             case tInt:
                 return v1->integer < v2->integer;
+            case tFloat:
+                return v1->fpoint < v2->fpoint;
             case tString:
                 return strcmp(v1->string.s, v2->string.s) < 0;
             case tPath:
@@ -1424,27 +1437,40 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value
 
 static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
 }
 
 
 static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
 }
 
 
 static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
 }
 
 
 static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    NixInt i2 = state.forceInt(*args[1], pos);
-    if (i2 == 0) throw EvalError(format("division by zero, at %1%") % pos);
-    mkInt(v, state.forceInt(*args[0], pos) / i2);
+    NixFloat f2 = state.forceFloat(*args[1], pos);
+    if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos);
+
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) / state.forceInt(*args[1], pos));
 }
 
 
@@ -1736,7 +1762,7 @@ void EvalState::createBaseEnv()
        language feature gets added.  It's not necessary to increase it
        when primops get added, because you can just use `builtins ?
        primOp' to check. */
-    mkInt(v, 3);
+    mkInt(v, 4);
     addConstant("__langVersion", v);
 
     // Miscellaneous
@@ -1753,6 +1779,7 @@ void EvalState::createBaseEnv()
     addPrimOp("__isFunction", 1, prim_isFunction);
     addPrimOp("__isString", 1, prim_isString);
     addPrimOp("__isInt", 1, prim_isInt);
+    addPrimOp("__isFloat", 1, prim_isFloat);
     addPrimOp("__isBool", 1, prim_isBool);
     addPrimOp("__genericClosure", 1, prim_genericClosure);
     addPrimOp("abort", 1, prim_abort);
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index b0cf85e21f18..47ee324a6e4f 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -84,6 +84,10 @@ void printValueAsJSON(EvalState & state, bool strict,
             v.external->printValueAsJSON(state, strict, str, context);
             break;
 
+        case tFloat:
+            str << v.fpoint;
+            break;
+
         default:
             throw TypeError(format("cannot convert %1% to JSON") % showType(v));
     }
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index cb52ce1e67bd..00b1918a82aa 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -148,6 +148,10 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
             v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen);
             break;
 
+        case tFloat:
+            doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
+            break;
+
         default:
             doc.writeEmptyElement("unevaluated");
     }
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index e6d1502cb607..88424106cdeb 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -22,6 +22,7 @@ typedef enum {
     tPrimOp,
     tPrimOpApp,
     tExternal,
+    tFloat
 } ValueType;
 
 
@@ -38,6 +39,7 @@ class XMLWriter;
 
 
 typedef long NixInt;
+typedef double NixFloat;
 
 /* External values must descend from ExternalValueBase, so that
  * type-agnostic nix functions (e.g. showType) can be implemented
@@ -141,6 +143,7 @@ struct Value
             Value * left, * right;
         } primOpApp;
         ExternalValueBase * external;
+        NixFloat fpoint;
     };
 
     bool isList() const
@@ -181,6 +184,14 @@ static inline void mkInt(Value & v, NixInt n)
 }
 
 
+static inline void mkFloat(Value & v, NixFloat n)
+{
+    clearValue(v);
+    v.type = tFloat;
+    v.fpoint = n;
+}
+
+
 static inline void mkBool(Value & v, bool b)
 {
     clearValue(v);