From 14ebde52893263930cdcde1406cc91cc5c42556f Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Tue, 5 Jan 2016 00:40:40 +0100 Subject: [PATCH 1/7] First hit at providing support for floats in the language. --- src/libexpr/eval.cc | 56 ++++++++++++++++++++++++++++++++++-- src/libexpr/eval.hh | 1 + src/libexpr/get-drvs.cc | 21 ++++++++++++-- src/libexpr/get-drvs.hh | 3 +- src/libexpr/json-to-value.cc | 25 +++++++++------- src/libexpr/lexer.l | 7 +++++ src/libexpr/nixexpr.cc | 9 ++++++ src/libexpr/nixexpr.hh | 9 ++++++ src/libexpr/parser.y | 3 ++ src/libexpr/primops.cc | 41 +++++++++++++++++++++----- src/libexpr/value-to-json.cc | 4 +++ src/libexpr/value-to-xml.cc | 4 +++ src/libexpr/value.hh | 11 +++++++ src/libmain/shared.hh | 24 ++++++++++++++++ src/libutil/util.hh | 8 ++++++ src/nix-env/nix-env.cc | 4 +++ 16 files changed, 207 insertions(+), 23 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index df1600bc1..e5a108a43 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -128,6 +128,8 @@ static void printValue(std::ostream & str, std::set & 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 eb55f6d4d..13353a43f 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 1002ee628..996c2c5f4 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 98f762494..365c66c8d 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 0898b5609..7ef2b2c56 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 1f2957ec7..8f9d686a1 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 5bc8e4202..b2c9f0528 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 ef07d4557..5e7bc40c8 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 d34882f36..32c66c783 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 ATTRPATH %token STR IND_STR %token INT +%token FLOAT %token PATH HPATH SPATH %token 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 5b9ecc018..ebc5ae143 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 b0cf85e21..47ee324a6 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 cb52ce1e6..00b1918a8 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 e6d1502cb..88424106c 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); diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index 65b288e1f..a350f496d 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -66,6 +66,30 @@ template N getIntArg(const string & opt, return n * multiplier; } +template N getFloatArg(const string & opt, + Strings::iterator & i, const Strings::iterator & end, bool allowUnit) +{ + ++i; + if (i == end) throw UsageError(format("‘%1%’ requires an argument") % opt); + string s = *i; + N multiplier = 1; + if (allowUnit && !s.empty()) { + char u = std::toupper(*s.rbegin()); + if (std::isalpha(u)) { + if (u == 'K') multiplier = 1ULL << 10; + else if (u == 'M') multiplier = 1ULL << 20; + else if (u == 'G') multiplier = 1ULL << 30; + else if (u == 'T') multiplier = 1ULL << 40; + else throw UsageError(format("invalid unit specifier ‘%1%’") % u); + s.resize(s.size() - 1); + } + } + N n; + if (!string2Float(s, n)) + throw UsageError(format("‘%1%’ requires a float argument") % opt); + return n * multiplier; +} + /* Show the manual page for the specified program. */ void showManPage(const string & name); diff --git a/src/libutil/util.hh b/src/libutil/util.hh index f4026a0a8..7ebfd7cee 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -358,6 +358,14 @@ template bool string2Int(const string & s, N & n) return str && str.get() == EOF; } +/* Parse a string into a float. */ +template bool string2Float(const string & s, N & n) +{ + std::istringstream str(s); + str >> n; + return str && str.get() == EOF; +} + /* Return true iff `s' ends in `suffix'. */ bool hasSuffix(const string & s, const string & suffix); diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 02a9f25a7..dcfb8f353 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -1127,6 +1127,10 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) attrs2["type"] = "int"; attrs2["value"] = (format("%1%") % v->integer).str(); xml.writeEmptyElement("meta", attrs2); + } else if (v->type == tFloat) { + attrs2["type"] = "float"; + attrs2["value"] = (format("%1%") % v->fpoint).str(); + xml.writeEmptyElement("meta", attrs2); } else if (v->type == tBool) { attrs2["type"] = "bool"; attrs2["value"] = v->boolean ? "true" : "false"; From 494fc5acbb104371a80ca17bd9f1e35c3859ed27 Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Tue, 5 Jan 2016 00:53:22 +0100 Subject: [PATCH 2/7] Try a simplified version of float lexing that didn't work. The last one I tried was botchered anyway ... --- src/libexpr/lexer.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 8f9d686a1..158ca9152 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -85,7 +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} +FLOAT {INT}\.{INT} PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+ HPATH \~(\/[a-zA-Z0-9\.\_\-\+]+)+ SPATH \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\> From f872262e0818ae1000826cfca3ad17619f50b60c Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Tue, 5 Jan 2016 09:46:37 +0100 Subject: [PATCH 3/7] Fix up float parsing. --- src/libexpr/eval.cc | 1 + src/libexpr/lexer.l | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index e5a108a43..b951daaca 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -130,6 +130,7 @@ static void printValue(std::ostream & str, std::set & active, con break; case tFloat: str << v.fpoint; + break; default: throw Error("invalid value"); } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 158ca9152..5fdcb25dd 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -85,7 +85,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s) ID [a-zA-Z\_][a-zA-Z0-9\_\'\-]* INT [0-9]+ -FLOAT {INT}\.{INT} +FLOAT (([1-9][0-9]*\.?[0-9]*)|(\.[0-9]+))([Ee][+-]?[0-9]+)? PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+ HPATH \~(\/[a-zA-Z0-9\.\_\-\+]+)+ SPATH \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\> @@ -125,7 +125,7 @@ or { return OR_KW; } return INT; } {FLOAT} { errno = 0; - yylval->n = strtod(yytext, 0); + yylval->nf = strtod(yytext, 0); if (errno != 0) throw ParseError(format("invalid float ‘%1%’") % yytext); return FLOAT; From a12a43046b0d1b967f0ca31d0db7bff218250274 Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Tue, 5 Jan 2016 09:54:49 +0100 Subject: [PATCH 4/7] Edge condition: parser did not pick up floats starting exactly with 0. --- src/libexpr/lexer.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 5fdcb25dd..7a6e48215 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -85,7 +85,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s) ID [a-zA-Z\_][a-zA-Z0-9\_\'\-]* INT [0-9]+ -FLOAT (([1-9][0-9]*\.?[0-9]*)|(\.[0-9]+))([Ee][+-]?[0-9]+)? +FLOAT (([1-9][0-9]*\.[0-9]*)|(0?\.[0-9]+))([Ee][+-]?[0-9]+)? PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+ HPATH \~(\/[a-zA-Z0-9\.\_\-\+]+)+ SPATH \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\> From 934642155c036ce6880e57854f095f2863ab80f1 Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Wed, 6 Jan 2016 08:25:58 +0100 Subject: [PATCH 5/7] @eelco's feedback: downgrade to regular float for size, remove unused function. --- src/libexpr/value.hh | 2 +- src/libmain/shared.hh | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 88424106c..62bdd9281 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -39,7 +39,7 @@ class XMLWriter; typedef long NixInt; -typedef double NixFloat; +typedef float NixFloat; /* External values must descend from ExternalValueBase, so that * type-agnostic nix functions (e.g. showType) can be implemented diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index a350f496d..32183d6a6 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -66,29 +66,6 @@ template N getIntArg(const string & opt, return n * multiplier; } -template N getFloatArg(const string & opt, - Strings::iterator & i, const Strings::iterator & end, bool allowUnit) -{ - ++i; - if (i == end) throw UsageError(format("‘%1%’ requires an argument") % opt); - string s = *i; - N multiplier = 1; - if (allowUnit && !s.empty()) { - char u = std::toupper(*s.rbegin()); - if (std::isalpha(u)) { - if (u == 'K') multiplier = 1ULL << 10; - else if (u == 'M') multiplier = 1ULL << 20; - else if (u == 'G') multiplier = 1ULL << 30; - else if (u == 'T') multiplier = 1ULL << 40; - else throw UsageError(format("invalid unit specifier ‘%1%’") % u); - s.resize(s.size() - 1); - } - } - N n; - if (!string2Float(s, n)) - throw UsageError(format("‘%1%’ requires a float argument") % opt); - return n * multiplier; -} /* Show the manual page for the specified program. */ void showManPage(const string & name); From b4bda4765af09acb86a4dd71105059491e7dcc30 Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Wed, 6 Jan 2016 08:41:53 +0100 Subject: [PATCH 6/7] Update documentation for floats. --- doc/manual/expressions/builtins.xml | 18 +++++++++--------- doc/manual/expressions/derivations.xml | 2 +- doc/manual/expressions/language-values.xml | 9 +++++++-- doc/manual/release-notes/rl-1.11.xml | 7 +++++++ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/doc/manual/expressions/builtins.xml b/doc/manual/expressions/builtins.xml index 3b664479d..13cc2221e 100644 --- a/doc/manual/expressions/builtins.xml +++ b/doc/manual/expressions/builtins.xml @@ -32,7 +32,7 @@ available as builtins.derivation. builtins.add e1 e2 - Return the sum of the integers + Return the sum of the numbers e1 and e2. @@ -204,7 +204,7 @@ if builtins ? getEnv then builtins.getEnv "PATH" else "" builtins.div e1 e2 - Return the quotient of the integers + Return the quotient of the numbers e1 and e2. @@ -335,7 +335,7 @@ stdenv.mkDerivation { - builtins.foldl’ + builtins.foldl’ op nul list Reduce a list by applying a binary operator, from @@ -602,12 +602,12 @@ x: x + 456 builtins.lessThan e1 e2 - Return true if the integer - e1 is less than the integer + Return true if the number + e1 is less than the number e2, and false otherwise. Evaluation aborts if either e1 or e2 - does not evaluate to an integer. + does not evaluate to a number. @@ -658,7 +658,7 @@ map (x: "foo" + x) [ "bar" "bla" "abc" ] builtins.mul e1 e2 - Return the product of the integers + Return the product of the numbers e1 and e2. @@ -815,7 +815,7 @@ builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ] builtins.sub e1 e2 - Return the difference between the integers + Return the difference between the numbers e1 and e2. @@ -942,7 +942,7 @@ in foo builtins.toJSON e Return a string containing a JSON representation - of e. Strings, integers, booleans, + of e. Strings, integers, floats, booleans, nulls and lists are mapped to their JSON equivalents. Sets (except derivations) are represented as objects. Derivations are translated to a JSON string containing the derivation’s output diff --git a/doc/manual/expressions/derivations.xml b/doc/manual/expressions/derivations.xml index 90e2786fa..f2a73dccf 100644 --- a/doc/manual/expressions/derivations.xml +++ b/doc/manual/expressions/derivations.xml @@ -43,7 +43,7 @@ of which specify the inputs of the build. - Strings and integers are just passed + Strings and numbers are just passed verbatim. A path (e.g., diff --git a/doc/manual/expressions/language-values.xml b/doc/manual/expressions/language-values.xml index 0bf6632d6..f1174ecb5 100644 --- a/doc/manual/expressions/language-values.xml +++ b/doc/manual/expressions/language-values.xml @@ -140,8 +140,13 @@ stdenv.mkDerivation { - Integers, e.g., - 123. + Numbers, which can be integers (like + 123) or floating point (like + 123.43 or .27e13). + + Numbers are type-compatible: pure integer operations will always + return integers, whereas any operation involving at least one floating point + number will have a floating point number as a result. Paths, e.g., /bin/sh or ./builder.sh. diff --git a/doc/manual/release-notes/rl-1.11.xml b/doc/manual/release-notes/rl-1.11.xml index aa9a3e101..fb6e4abfe 100644 --- a/doc/manual/release-notes/rl-1.11.xml +++ b/doc/manual/release-notes/rl-1.11.xml @@ -10,6 +10,13 @@ features: + + The Nix language now supports floating point numbers. They are + based on regular C++ float and compatible with + existing integers and number-related operations. Export and import to and + from JSON and XML works, too. + + All "chroot"-containing strings got renamed to "sandbox". In particular, some nix options got renamed, but the old names From 5cdcaf5e8edd6679f667502eec421ac4e725d4ef Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Wed, 6 Jan 2016 10:03:24 +0100 Subject: [PATCH 7/7] Adapt tests to show that floats work properly. --- tests/lang/eval-okay-fromjson.nix | 6 +++++- tests/lang/eval-okay-tojson.exp | 2 +- tests/lang/eval-okay-tojson.nix | 1 + tests/lang/eval-okay-types.exp | 2 +- tests/lang/eval-okay-types.nix | 10 ++++++++++ tests/lang/eval-okay-xml.exp.xml | 3 +++ tests/lang/eval-okay-xml.nix | 2 ++ 7 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/lang/eval-okay-fromjson.nix b/tests/lang/eval-okay-fromjson.nix index 5ed0c1c43..2a9ad0cab 100644 --- a/tests/lang/eval-okay-fromjson.nix +++ b/tests/lang/eval-okay-fromjson.nix @@ -12,7 +12,9 @@ builtins.fromJSON "Width": 100 }, "Animated" : false, - "IDs": [116, 943, 234, 38793, true ,false,null, -100] + "IDs": [116, 943, 234, 38793, true ,false,null, -100], + "Latitude": 37.7668, + "Longitude": -122.3959, } } '' @@ -28,5 +30,7 @@ builtins.fromJSON }; Animated = false; IDs = [ 116 943 234 38793 true false null (0-100) ]; + Latitude = 37.7668; + Longitude = -122.3959; }; } diff --git a/tests/lang/eval-okay-tojson.exp b/tests/lang/eval-okay-tojson.exp index e8164af2b..33588493f 100644 --- a/tests/lang/eval-okay-tojson.exp +++ b/tests/lang/eval-okay-tojson.exp @@ -1 +1 @@ -"{\"a\":123,\"b\":-456,\"c\":\"foo\",\"d\":\"foo\\n\\\"bar\\\"\",\"e\":true,\"f\":false,\"g\":[1,2,3],\"h\":[\"a\",[\"b\",{\"foo\\nbar\":{}}]],\"i\":3}" +"{\"a\":123,\"b\":-456,\"c\":\"foo\",\"d\":\"foo\\n\\\"bar\\\"\",\"e\":true,\"f\":false,\"g\":[1,2,3],\"h\":[\"a\",[\"b\",{\"foo\\nbar\":{}}]],\"i\":3,\"j\":1.44}" diff --git a/tests/lang/eval-okay-tojson.nix b/tests/lang/eval-okay-tojson.nix index 0d4e55b3d..c046ba4ae 100644 --- a/tests/lang/eval-okay-tojson.nix +++ b/tests/lang/eval-okay-tojson.nix @@ -8,4 +8,5 @@ builtins.toJSON g = [ 1 2 3 ]; h = [ "a" [ "b" { "foo\nbar" = {}; } ] ]; i = 1 + 2; + j = 1.44; } diff --git a/tests/lang/eval-okay-types.exp b/tests/lang/eval-okay-types.exp index 82487f710..9a8ea0bcb 100644 --- a/tests/lang/eval-okay-types.exp +++ b/tests/lang/eval-okay-types.exp @@ -1 +1 @@ -[ true false true false true false true false true false true false "int" "bool" "string" "null" "set" "list" "lambda" "lambda" "lambda" "lambda" ] +[ true false true false true false true false true true true true true true true true true true true false true false "int" "bool" "string" "null" "set" "list" "lambda" "lambda" "lambda" "lambda" ] diff --git a/tests/lang/eval-okay-types.nix b/tests/lang/eval-okay-types.nix index 8cb225e24..a34775f5e 100644 --- a/tests/lang/eval-okay-types.nix +++ b/tests/lang/eval-okay-types.nix @@ -8,6 +8,16 @@ with builtins; (isString [ "x" ]) (isInt (1 + 2)) (isInt { x = 123; }) + (isInt (1 / 2)) + (isInt (1 + 1)) + (isInt (1 / 2)) + (isInt (1 * 2)) + (isInt (1 - 2)) + (isFloat (1.2)) + (isFloat (1 + 1.0)) + (isFloat (1 / 2.0)) + (isFloat (1 * 2.0)) + (isFloat (1 - 2.0)) (isBool (true && false)) (isBool null) (isAttrs { x = 123; }) diff --git a/tests/lang/eval-okay-xml.exp.xml b/tests/lang/eval-okay-xml.exp.xml index f124f939e..92b75e0b8 100644 --- a/tests/lang/eval-okay-xml.exp.xml +++ b/tests/lang/eval-okay-xml.exp.xml @@ -45,5 +45,8 @@ + + + diff --git a/tests/lang/eval-okay-xml.nix b/tests/lang/eval-okay-xml.nix index b9389bfae..9ee9f8a0b 100644 --- a/tests/lang/eval-okay-xml.nix +++ b/tests/lang/eval-okay-xml.nix @@ -2,6 +2,8 @@ rec { x = 123; + y = 567.890; + a = "foo"; b = "bar";