Merge remote-tracking branch 'origin/master' into flakes

This commit is contained in:
Eelco Dolstra 2019-05-29 12:36:44 +02:00
commit 6636808e90
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
16 changed files with 486 additions and 214 deletions

View file

@ -14,7 +14,8 @@
<varlistentry><term><envar>IN_NIX_SHELL</envar></term> <varlistentry><term><envar>IN_NIX_SHELL</envar></term>
<listitem><para>Indicator that tells if the current environment was set up by <listitem><para>Indicator that tells if the current environment was set up by
<command>nix-shell</command>.</para></listitem> <command>nix-shell</command>. Since Nix 2.0 the values are
<literal>"pure"</literal> and <literal>"impure"</literal></para></listitem>
</varlistentry> </varlistentry>

View file

@ -217,7 +217,25 @@ but can also be written as:
ellipsis(<literal>...</literal>) as you can access attribute names as ellipsis(<literal>...</literal>) as you can access attribute names as
<literal>a</literal>, using <literal>args.a</literal>, which was given as an <literal>a</literal>, using <literal>args.a</literal>, which was given as an
additional attribute to the function. additional attribute to the function.
</para></listitem> </para>
<warning>
<para>
The <literal>args@</literal> expression is bound to the argument passed to the function which
means that attributes with defaults that aren't explicitly specified in the function call
won't cause an evaluation error, but won't exist in <literal>args</literal>.
</para>
<para>
For instance
<programlisting>
let
function = args@{ a ? 23, ... }: args;
in
function {}
</programlisting>
will evaluate to an empty attribute set.
</para>
</warning></listitem>
</itemizedlist> </itemizedlist>

View file

@ -2,12 +2,6 @@
if [ -n "${__ETC_PROFILE_NIX_SOURCED:-}" ]; then return; fi if [ -n "${__ETC_PROFILE_NIX_SOURCED:-}" ]; then return; fi
__ETC_PROFILE_NIX_SOURCED=1 __ETC_PROFILE_NIX_SOURCED=1
# Set up secure multi-user builds: non-root users build through the
# Nix daemon.
if [ "$USER" != root -o ! -w @localstatedir@/nix/db ]; then
export NIX_REMOTE=daemon
fi
export NIX_USER_PROFILE_DIR="@localstatedir@/nix/profiles/per-user/$USER" export NIX_USER_PROFILE_DIR="@localstatedir@/nix/profiles/per-user/$USER"
export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile" export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile"

View file

@ -57,7 +57,7 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
# Set up environment. # Set up environment.
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix # This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_USER_PROFILE_DIR" export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile"
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch
@ -79,5 +79,5 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
fi fi
export PATH="$NIX_LINK/bin:$__savedpath" export PATH="$NIX_LINK/bin:$__savedpath"
unset __savedpath NIX_LINK NIX_USER_PROFILE_DIR NIX_PROFILES unset __savedpath NIX_LINK NIX_USER_PROFILE_DIR
fi fi

View file

@ -2,7 +2,7 @@
, nixpkgs ? builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03.tar.gz , nixpkgs ? builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03.tar.gz
}: }:
with import nixpkgs { system = builtins.currentSystem or "x86_64-linux"; }; with import (builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03.tar.gz) { system = builtins.currentSystem or "x86_64-linux"; };
with import ./release-common.nix { inherit pkgs; }; with import ./release-common.nix { inherit pkgs; };

View file

@ -4,8 +4,8 @@
* @date May 2013 * @date May 2013
*/ */
#ifndef _CPPTOML_H_ #ifndef CPPTOML_H
#define _CPPTOML_H_ #define CPPTOML_H
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
@ -84,11 +84,12 @@ class option
return &value_; return &value_;
} }
const T& value_or(const T& alternative) const template <class U>
T value_or(U&& alternative) const
{ {
if (!empty_) if (!empty_)
return value_; return value_;
return alternative; return static_cast<T>(std::forward<U>(alternative));
} }
private: private:
@ -295,13 +296,12 @@ struct valid_value_or_string_convertible
}; };
template <class T> template <class T>
struct value_traits<T, typename std:: struct value_traits<T, typename std::enable_if<
enable_if<valid_value_or_string_convertible<T>:: valid_value_or_string_convertible<T>::value>::type>
value>::type>
{ {
using value_type = typename std:: using value_type = typename std::conditional<
conditional<valid_value<typename std::decay<T>::type>::value, valid_value<typename std::decay<T>::type>::value,
typename std::decay<T>::type, std::string>::type; typename std::decay<T>::type, std::string>::type;
using type = value<value_type>; using type = value<value_type>;
@ -312,12 +312,11 @@ struct value_traits<T, typename std::
}; };
template <class T> template <class T>
struct value_traits<T, struct value_traits<
typename std:: T,
enable_if<!valid_value_or_string_convertible<T>::value typename std::enable_if<
&& std::is_floating_point< !valid_value_or_string_convertible<T>::value
typename std::decay<T>::type>::value>:: && std::is_floating_point<typename std::decay<T>::type>::value>::type>
type>
{ {
using value_type = typename std::decay<T>::type; using value_type = typename std::decay<T>::type;
@ -330,11 +329,11 @@ struct value_traits<T,
}; };
template <class T> template <class T>
struct value_traits<T, struct value_traits<
typename std:: T, typename std::enable_if<
enable_if<!valid_value_or_string_convertible<T>::value !valid_value_or_string_convertible<T>::value
&& std::is_signed<typename std::decay<T>:: && !std::is_floating_point<typename std::decay<T>::type>::value
type>::value>::type> && std::is_signed<typename std::decay<T>::type>::value>::type>
{ {
using value_type = int64_t; using value_type = int64_t;
@ -356,11 +355,10 @@ struct value_traits<T,
}; };
template <class T> template <class T>
struct value_traits<T, struct value_traits<
typename std:: T, typename std::enable_if<
enable_if<!valid_value_or_string_convertible<T>::value !valid_value_or_string_convertible<T>::value
&& std::is_unsigned<typename std::decay<T>:: && std::is_unsigned<typename std::decay<T>::type>::value>::type>
type>::value>::type>
{ {
using value_type = int64_t; using value_type = int64_t;
@ -395,10 +393,15 @@ struct array_of_trait<array>
template <class T> template <class T>
inline std::shared_ptr<typename value_traits<T>::type> make_value(T&& val); inline std::shared_ptr<typename value_traits<T>::type> make_value(T&& val);
inline std::shared_ptr<array> make_array(); inline std::shared_ptr<array> make_array();
namespace detail
{
template <class T> template <class T>
inline std::shared_ptr<T> make_element(); inline std::shared_ptr<T> make_element();
}
inline std::shared_ptr<table> make_table(); inline std::shared_ptr<table> make_table();
inline std::shared_ptr<table_array> make_table_array(); inline std::shared_ptr<table_array> make_table_array(bool is_inline = false);
#if defined(CPPTOML_NO_RTTI) #if defined(CPPTOML_NO_RTTI)
/// Base type used to store underlying data type explicitly if RTTI is disabled /// Base type used to store underlying data type explicitly if RTTI is disabled
@ -576,7 +579,7 @@ class base : public std::enable_shared_from_this<base>
#if defined(CPPTOML_NO_RTTI) #if defined(CPPTOML_NO_RTTI)
base_type type() const base_type type() const
{ {
return type_; return type_;
} }
protected: protected:
@ -698,7 +701,7 @@ inline std::shared_ptr<value<double>> base::as()
if (type() == base_type::INT) if (type() == base_type::INT)
{ {
auto v = std::static_pointer_cast<value<int64_t>>(shared_from_this()); auto v = std::static_pointer_cast<value<int64_t>>(shared_from_this());
return make_value<double>(static_cast<double>(v->get()));; return make_value<double>(static_cast<double>(v->get()));
} }
#else #else
if (auto v = std::dynamic_pointer_cast<value<double>>(shared_from_this())) if (auto v = std::dynamic_pointer_cast<value<double>>(shared_from_this()))
@ -731,7 +734,8 @@ inline std::shared_ptr<const value<double>> base::as() const
{ {
#if defined(CPPTOML_NO_RTTI) #if defined(CPPTOML_NO_RTTI)
if (type() == base_type::FLOAT) if (type() == base_type::FLOAT)
return std::static_pointer_cast<const value<double>>(shared_from_this()); return std::static_pointer_cast<const value<double>>(
shared_from_this());
if (type() == base_type::INT) if (type() == base_type::INT)
{ {
@ -1027,11 +1031,14 @@ inline std::shared_ptr<array> make_array()
return std::make_shared<make_shared_enabler>(); return std::make_shared<make_shared_enabler>();
} }
namespace detail
{
template <> template <>
inline std::shared_ptr<array> make_element<array>() inline std::shared_ptr<array> make_element<array>()
{ {
return make_array(); return make_array();
} }
} // namespace detail
/** /**
* Obtains a option<vector<T>>. The option will be empty if the array * Obtains a option<vector<T>>. The option will be empty if the array
@ -1060,7 +1067,7 @@ class table;
class table_array : public base class table_array : public base
{ {
friend class table; friend class table;
friend std::shared_ptr<table_array> make_table_array(); friend std::shared_ptr<table_array> make_table_array(bool);
public: public:
std::shared_ptr<base> clone() const override; std::shared_ptr<base> clone() const override;
@ -1152,14 +1159,25 @@ class table_array : public base
array_.reserve(n); array_.reserve(n);
} }
/**
* Whether or not the table array is declared inline. This mostly
* matters for parsing, where statically defined arrays cannot be
* appended to using the array-of-table syntax.
*/
bool is_inline() const
{
return is_inline_;
}
private: private:
#if defined(CPPTOML_NO_RTTI) #if defined(CPPTOML_NO_RTTI)
table_array() : base(base_type::TABLE_ARRAY) table_array(bool is_inline = false)
: base(base_type::TABLE_ARRAY), is_inline_(is_inline)
{ {
// nothing // nothing
} }
#else #else
table_array() table_array(bool is_inline = false) : is_inline_(is_inline)
{ {
// nothing // nothing
} }
@ -1169,26 +1187,30 @@ class table_array : public base
table_array& operator=(const table_array& rhs) = delete; table_array& operator=(const table_array& rhs) = delete;
std::vector<std::shared_ptr<table>> array_; std::vector<std::shared_ptr<table>> array_;
const bool is_inline_ = false;
}; };
inline std::shared_ptr<table_array> make_table_array() inline std::shared_ptr<table_array> make_table_array(bool is_inline)
{ {
struct make_shared_enabler : public table_array struct make_shared_enabler : public table_array
{ {
make_shared_enabler() make_shared_enabler(bool mse_is_inline) : table_array(mse_is_inline)
{ {
// nothing // nothing
} }
}; };
return std::make_shared<make_shared_enabler>(); return std::make_shared<make_shared_enabler>(is_inline);
} }
namespace detail
{
template <> template <>
inline std::shared_ptr<table_array> make_element<table_array>() inline std::shared_ptr<table_array> make_element<table_array>()
{ {
return make_table_array(); return make_table_array(true);
} }
} // namespace detail
// The below are overloads for fetching specific value types out of a value // The below are overloads for fetching specific value types out of a value
// where special casting behavior (like bounds checking) is desired // where special casting behavior (like bounds checking) is desired
@ -1679,11 +1701,14 @@ std::shared_ptr<table> make_table()
return std::make_shared<make_shared_enabler>(); return std::make_shared<make_shared_enabler>();
} }
namespace detail
{
template <> template <>
inline std::shared_ptr<table> make_element<table>() inline std::shared_ptr<table> make_element<table>()
{ {
return make_table(); return make_table();
} }
} // namespace detail
template <class T> template <class T>
std::shared_ptr<base> value<T>::clone() const std::shared_ptr<base> value<T>::clone() const
@ -1702,7 +1727,7 @@ inline std::shared_ptr<base> array::clone() const
inline std::shared_ptr<base> table_array::clone() const inline std::shared_ptr<base> table_array::clone() const
{ {
auto result = make_table_array(); auto result = make_table_array(is_inline());
result->reserve(array_.size()); result->reserve(array_.size());
for (const auto& ptr : array_) for (const auto& ptr : array_)
result->array_.push_back(ptr->clone()->as_table()); result->array_.push_back(ptr->clone()->as_table());
@ -1738,6 +1763,11 @@ inline bool is_number(char c)
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
} }
inline bool is_hex(char c)
{
return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
/** /**
* Helper object for consuming expected characters. * Helper object for consuming expected characters.
*/ */
@ -1766,6 +1796,13 @@ class consumer
[&](char c) { (*this)(c); }); [&](char c) { (*this)(c); });
} }
void eat_or(char a, char b)
{
if (it_ == end_ || (*it_ != a && *it_ != b))
on_error_();
++it_;
}
int eat_digits(int len) int eat_digits(int len)
{ {
int val = 0; int val = 0;
@ -1830,7 +1867,7 @@ inline std::istream& getline(std::istream& input, std::string& line)
line.push_back(static_cast<char>(c)); line.push_back(static_cast<char>(c));
} }
} }
} } // namespace detail
/** /**
* The parser class. * The parser class.
@ -1914,21 +1951,25 @@ class parser
std::string full_table_name; std::string full_table_name;
bool inserted = false; bool inserted = false;
while (it != end && *it != ']')
{
auto part = parse_key(it, end,
[](char c) { return c == '.' || c == ']'; });
auto key_end = [](char c) { return c == ']'; };
auto key_part_handler = [&](const std::string& part) {
if (part.empty()) if (part.empty())
throw_parse_exception("Empty component of table name"); throw_parse_exception("Empty component of table name");
if (!full_table_name.empty()) if (!full_table_name.empty())
full_table_name += "."; full_table_name += '.';
full_table_name += part; full_table_name += part;
if (curr_table->contains(part)) if (curr_table->contains(part))
{ {
#if !defined(__PGI)
auto b = curr_table->get(part); auto b = curr_table->get(part);
#else
// Workaround for PGI compiler
std::shared_ptr<base> b = curr_table->get(part);
#endif
if (b->is_table()) if (b->is_table())
curr_table = static_cast<table*>(b.get()); curr_table = static_cast<table*>(b.get());
else if (b->is_table_array()) else if (b->is_table_array())
@ -1946,16 +1987,23 @@ class parser
curr_table->insert(part, make_table()); curr_table->insert(part, make_table());
curr_table = static_cast<table*>(curr_table->get(part).get()); curr_table = static_cast<table*>(curr_table->get(part).get());
} }
consume_whitespace(it, end); };
if (it != end && *it == '.')
++it; key_part_handler(parse_key(it, end, key_end, key_part_handler));
consume_whitespace(it, end);
}
if (it == end) if (it == end)
throw_parse_exception( throw_parse_exception(
"Unterminated table declaration; did you forget a ']'?"); "Unterminated table declaration; did you forget a ']'?");
if (*it != ']')
{
std::string errmsg{"Unexpected character in table definition: "};
errmsg += '"';
errmsg += *it;
errmsg += '"';
throw_parse_exception(errmsg);
}
// table already existed // table already existed
if (!inserted) if (!inserted)
{ {
@ -1969,8 +2017,9 @@ class parser
// since it has already been defined. If there aren't any // since it has already been defined. If there aren't any
// values, then it was implicitly created by something like // values, then it was implicitly created by something like
// [a.b] // [a.b]
if (curr_table->empty() || std::any_of(curr_table->begin(), if (curr_table->empty()
curr_table->end(), is_value)) || std::any_of(curr_table->begin(), curr_table->end(),
is_value))
{ {
throw_parse_exception("Redefinition of table " throw_parse_exception("Redefinition of table "
+ full_table_name); + full_table_name);
@ -1989,36 +2038,45 @@ class parser
if (it == end || *it == ']') if (it == end || *it == ']')
throw_parse_exception("Table array name cannot be empty"); throw_parse_exception("Table array name cannot be empty");
std::string full_ta_name; auto key_end = [](char c) { return c == ']'; };
while (it != end && *it != ']')
{
auto part = parse_key(it, end,
[](char c) { return c == '.' || c == ']'; });
std::string full_ta_name;
auto key_part_handler = [&](const std::string& part) {
if (part.empty()) if (part.empty())
throw_parse_exception("Empty component of table array name"); throw_parse_exception("Empty component of table array name");
if (!full_ta_name.empty()) if (!full_ta_name.empty())
full_ta_name += "."; full_ta_name += '.';
full_ta_name += part; full_ta_name += part;
consume_whitespace(it, end);
if (it != end && *it == '.')
++it;
consume_whitespace(it, end);
if (curr_table->contains(part)) if (curr_table->contains(part))
{ {
#if !defined(__PGI)
auto b = curr_table->get(part); auto b = curr_table->get(part);
#else
// Workaround for PGI compiler
std::shared_ptr<base> b = curr_table->get(part);
#endif
// if this is the end of the table array name, add an // if this is the end of the table array name, add an
// element to the table array that we just looked up // element to the table array that we just looked up,
// provided it was not declared inline
if (it != end && *it == ']') if (it != end && *it == ']')
{ {
if (!b->is_table_array()) if (!b->is_table_array())
{
throw_parse_exception("Key " + full_ta_name throw_parse_exception("Key " + full_ta_name
+ " is not a table array"); + " is not a table array");
}
auto v = b->as_table_array(); auto v = b->as_table_array();
if (v->is_inline())
{
throw_parse_exception("Static array " + full_ta_name
+ " cannot be appended to");
}
v->get().push_back(make_table()); v->get().push_back(make_table());
curr_table = v->get().back().get(); curr_table = v->get().back().get();
} }
@ -2059,15 +2117,16 @@ class parser
= static_cast<table*>(curr_table->get(part).get()); = static_cast<table*>(curr_table->get(part).get());
} }
} }
} };
key_part_handler(parse_key(it, end, key_end, key_part_handler));
// consume the last "]]" // consume the last "]]"
if (it == end) auto eat = make_consumer(it, end, [this]() {
throw_parse_exception("Unterminated table array name"); throw_parse_exception("Unterminated table array name");
++it; });
if (it == end) eat(']');
throw_parse_exception("Unterminated table array name"); eat(']');
++it;
consume_whitespace(it, end); consume_whitespace(it, end);
eol_or_comment(it, end); eol_or_comment(it, end);
@ -2076,7 +2135,35 @@ class parser
void parse_key_value(std::string::iterator& it, std::string::iterator& end, void parse_key_value(std::string::iterator& it, std::string::iterator& end,
table* curr_table) table* curr_table)
{ {
auto key = parse_key(it, end, [](char c) { return c == '='; }); auto key_end = [](char c) { return c == '='; };
auto key_part_handler = [&](const std::string& part) {
// two cases: this key part exists already, in which case it must
// be a table, or it doesn't exist in which case we must create
// an implicitly defined table
if (curr_table->contains(part))
{
auto val = curr_table->get(part);
if (val->is_table())
{
curr_table = static_cast<table*>(val.get());
}
else
{
throw_parse_exception("Key " + part
+ " already exists as a value");
}
}
else
{
auto newtable = make_table();
curr_table->insert(part, newtable);
curr_table = newtable.get();
}
};
auto key = parse_key(it, end, key_end, key_part_handler);
if (curr_table->contains(key)) if (curr_table->contains(key))
throw_parse_exception("Key " + key + " already present"); throw_parse_exception("Key " + key + " already present");
if (it == end || *it != '=') if (it == end || *it != '=')
@ -2087,18 +2174,57 @@ class parser
consume_whitespace(it, end); consume_whitespace(it, end);
} }
template <class Function> template <class KeyEndFinder, class KeyPartHandler>
std::string parse_key(std::string::iterator& it, std::string
const std::string::iterator& end, Function&& fun) parse_key(std::string::iterator& it, const std::string::iterator& end,
KeyEndFinder&& key_end, KeyPartHandler&& key_part_handler)
{
// parse the key as a series of one or more simple-keys joined with '.'
while (it != end && !key_end(*it))
{
auto part = parse_simple_key(it, end);
consume_whitespace(it, end);
if (it == end || key_end(*it))
{
return part;
}
if (*it != '.')
{
std::string errmsg{"Unexpected character in key: "};
errmsg += '"';
errmsg += *it;
errmsg += '"';
throw_parse_exception(errmsg);
}
key_part_handler(part);
// consume the dot
++it;
}
throw_parse_exception("Unexpected end of key");
}
std::string parse_simple_key(std::string::iterator& it,
const std::string::iterator& end)
{ {
consume_whitespace(it, end); consume_whitespace(it, end);
if (*it == '"')
if (it == end)
throw_parse_exception("Unexpected end of key (blank key?)");
if (*it == '"' || *it == '\'')
{ {
return parse_quoted_key(it, end); return string_literal(it, end, *it);
} }
else else
{ {
auto bke = std::find_if(it, end, std::forward<Function>(fun)); auto bke = std::find_if(it, end, [](char c) {
return c == '.' || c == '=' || c == ']';
});
return parse_bare_key(it, bke); return parse_bare_key(it, bke);
} }
} }
@ -2142,12 +2268,6 @@ class parser
return key; return key;
} }
std::string parse_quoted_key(std::string::iterator& it,
const std::string::iterator& end)
{
return string_literal(it, end, '"');
}
enum class parse_type enum class parse_type
{ {
STRING = 1, STRING = 1,
@ -2193,7 +2313,7 @@ class parser
parse_type determine_value_type(const std::string::iterator& it, parse_type determine_value_type(const std::string::iterator& it,
const std::string::iterator& end) const std::string::iterator& end)
{ {
if(it == end) if (it == end)
{ {
throw_parse_exception("Failed to parse value type"); throw_parse_exception("Failed to parse value type");
} }
@ -2209,7 +2329,11 @@ class parser
{ {
return *dtype; return *dtype;
} }
else if (is_number(*it) || *it == '-' || *it == '+') else if (is_number(*it) || *it == '-' || *it == '+'
|| (*it == 'i' && it + 1 != end && it[1] == 'n'
&& it + 2 != end && it[2] == 'f')
|| (*it == 'n' && it + 1 != end && it[1] == 'a'
&& it + 2 != end && it[2] == 'n'))
{ {
return determine_number_type(it, end); return determine_number_type(it, end);
} }
@ -2235,6 +2359,13 @@ class parser
auto check_it = it; auto check_it = it;
if (*check_it == '-' || *check_it == '+') if (*check_it == '-' || *check_it == '+')
++check_it; ++check_it;
if (check_it == end)
throw_parse_exception("Malformed number");
if (*check_it == 'i' || *check_it == 'n')
return parse_type::FLOAT;
while (check_it != end && is_number(*check_it)) while (check_it != end && is_number(*check_it))
++check_it; ++check_it;
if (check_it != end && *check_it == '.') if (check_it != end && *check_it == '.')
@ -2283,57 +2414,56 @@ class parser
bool consuming = false; bool consuming = false;
std::shared_ptr<value<std::string>> ret; std::shared_ptr<value<std::string>> ret;
auto handle_line auto handle_line = [&](std::string::iterator& local_it,
= [&](std::string::iterator& local_it, std::string::iterator& local_end) {
std::string::iterator& local_end) { if (consuming)
if (consuming) {
{ local_it = std::find_if_not(local_it, local_end, is_ws);
local_it = std::find_if_not(local_it, local_end, is_ws);
// whole line is whitespace // whole line is whitespace
if (local_it == local_end) if (local_it == local_end)
return; return;
} }
consuming = false; consuming = false;
while (local_it != local_end) while (local_it != local_end)
{ {
// handle escaped characters // handle escaped characters
if (delim == '"' && *local_it == '\\') if (delim == '"' && *local_it == '\\')
{ {
auto check = local_it; auto check = local_it;
// check if this is an actual escape sequence or a // check if this is an actual escape sequence or a
// whitespace escaping backslash // whitespace escaping backslash
++check; ++check;
consume_whitespace(check, local_end); consume_whitespace(check, local_end);
if (check == local_end) if (check == local_end)
{ {
consuming = true; consuming = true;
break; break;
} }
ss << parse_escape_code(local_it, local_end); ss << parse_escape_code(local_it, local_end);
continue; continue;
} }
// if we can end the string // if we can end the string
if (std::distance(local_it, local_end) >= 3) if (std::distance(local_it, local_end) >= 3)
{ {
auto check = local_it; auto check = local_it;
// check for """ // check for """
if (*check++ == delim && *check++ == delim if (*check++ == delim && *check++ == delim
&& *check++ == delim) && *check++ == delim)
{ {
local_it = check; local_it = check;
ret = make_value<std::string>(ss.str()); ret = make_value<std::string>(ss.str());
break; break;
} }
} }
ss << *local_it++; ss << *local_it++;
} }
}; };
// handle the remainder of the current line // handle the remainder of the current line
handle_line(it, end); handle_line(it, end);
@ -2514,17 +2644,13 @@ class parser
return value; return value;
} }
bool is_hex(char c)
{
return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
uint32_t hex_to_digit(char c) uint32_t hex_to_digit(char c)
{ {
if (is_number(c)) if (is_number(c))
return static_cast<uint32_t>(c - '0'); return static_cast<uint32_t>(c - '0');
return 10 + static_cast<uint32_t>( return 10
c - ((c >= 'a' && c <= 'f') ? 'a' : 'A')); + static_cast<uint32_t>(c
- ((c >= 'a' && c <= 'f') ? 'a' : 'A'));
} }
std::shared_ptr<base> parse_number(std::string::iterator& it, std::shared_ptr<base> parse_number(std::string::iterator& it,
@ -2538,25 +2664,6 @@ class parser
++check_it; ++check_it;
}; };
eat_sign();
auto eat_numbers = [&]() {
auto beg = check_it;
while (check_it != end && is_number(*check_it))
{
++check_it;
if (check_it != end && *check_it == '_')
{
++check_it;
if (check_it == end || !is_number(*check_it))
throw_parse_exception("Malformed number");
}
}
if (check_it == beg)
throw_parse_exception("Malformed number");
};
auto check_no_leading_zero = [&]() { auto check_no_leading_zero = [&]() {
if (check_it != end && *check_it == '0' && check_it + 1 != check_end if (check_it != end && *check_it == '0' && check_it + 1 != check_end
&& check_it[1] != '.') && check_it[1] != '.')
@ -2565,7 +2672,80 @@ class parser
} }
}; };
auto eat_digits = [&](bool (*check_char)(char)) {
auto beg = check_it;
while (check_it != end && check_char(*check_it))
{
++check_it;
if (check_it != end && *check_it == '_')
{
++check_it;
if (check_it == end || !check_char(*check_it))
throw_parse_exception("Malformed number");
}
}
if (check_it == beg)
throw_parse_exception("Malformed number");
};
auto eat_hex = [&]() { eat_digits(&is_hex); };
auto eat_numbers = [&]() { eat_digits(&is_number); };
if (check_it != end && *check_it == '0' && check_it + 1 != check_end
&& (check_it[1] == 'x' || check_it[1] == 'o' || check_it[1] == 'b'))
{
++check_it;
char base = *check_it;
++check_it;
if (base == 'x')
{
eat_hex();
return parse_int(it, check_it, 16);
}
else if (base == 'o')
{
auto start = check_it;
eat_numbers();
auto val = parse_int(start, check_it, 8, "0");
it = start;
return val;
}
else // if (base == 'b')
{
auto start = check_it;
eat_numbers();
auto val = parse_int(start, check_it, 2);
it = start;
return val;
}
}
eat_sign();
check_no_leading_zero(); check_no_leading_zero();
if (check_it != end && check_it + 1 != end && check_it + 2 != end)
{
if (check_it[0] == 'i' && check_it[1] == 'n' && check_it[2] == 'f')
{
auto val = std::numeric_limits<double>::infinity();
if (*it == '-')
val = -val;
it = check_it + 3;
return make_value(val);
}
else if (check_it[0] == 'n' && check_it[1] == 'a'
&& check_it[2] == 'n')
{
auto val = std::numeric_limits<double>::quiet_NaN();
if (*it == '-')
val = -val;
it = check_it + 3;
return make_value(val);
}
}
eat_numbers(); eat_numbers();
if (check_it != end if (check_it != end
@ -2604,14 +2784,17 @@ class parser
} }
std::shared_ptr<value<int64_t>> parse_int(std::string::iterator& it, std::shared_ptr<value<int64_t>> parse_int(std::string::iterator& it,
const std::string::iterator& end) const std::string::iterator& end,
int base = 10,
const char* prefix = "")
{ {
std::string v{it, end}; std::string v{it, end};
v = prefix + v;
v.erase(std::remove(v.begin(), v.end(), '_'), v.end()); v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
it = end; it = end;
try try
{ {
return make_value<int64_t>(std::stoll(v)); return make_value<int64_t>(std::stoll(v, nullptr, base));
} }
catch (const std::invalid_argument& ex) catch (const std::invalid_argument& ex)
{ {
@ -2674,18 +2857,33 @@ class parser
std::string::iterator find_end_of_number(std::string::iterator it, std::string::iterator find_end_of_number(std::string::iterator it,
std::string::iterator end) std::string::iterator end)
{ {
return std::find_if(it, end, [](char c) { auto ret = std::find_if(it, end, [](char c) {
return !is_number(c) && c != '_' && c != '.' && c != 'e' && c != 'E' return !is_number(c) && c != '_' && c != '.' && c != 'e' && c != 'E'
&& c != '-' && c != '+'; && c != '-' && c != '+' && c != 'x' && c != 'o' && c != 'b';
}); });
if (ret != end && ret + 1 != end && ret + 2 != end)
{
if ((ret[0] == 'i' && ret[1] == 'n' && ret[2] == 'f')
|| (ret[0] == 'n' && ret[1] == 'a' && ret[2] == 'n'))
{
ret = ret + 3;
}
}
return ret;
} }
std::string::iterator find_end_of_date(std::string::iterator it, std::string::iterator find_end_of_date(std::string::iterator it,
std::string::iterator end) std::string::iterator end)
{ {
return std::find_if(it, end, [](char c) { auto end_of_date = std::find_if(it, end, [](char c) {
return !is_number(c) && c != 'T' && c != 'Z' && c != ':' && c != '-' return !is_number(c) && c != '-';
&& c != '+' && c != '.'; });
if (end_of_date != end && *end_of_date == ' ' && end_of_date + 1 != end
&& is_number(end_of_date[1]))
end_of_date++;
return std::find_if(end_of_date, end, [](char c) {
return !is_number(c) && c != 'T' && c != 'Z' && c != ':'
&& c != '-' && c != '+' && c != '.';
}); });
} }
@ -2754,7 +2952,7 @@ class parser
if (it == date_end) if (it == date_end)
return make_value(ldate); return make_value(ldate);
eat('T'); eat.eat_or('T', ' ');
local_datetime ldt; local_datetime ldt;
static_cast<local_date&>(ldt) = ldate; static_cast<local_date&>(ldt) = ldate;
@ -2850,9 +3048,9 @@ class parser
auto arr = make_array(); auto arr = make_array();
while (it != end && *it != ']') while (it != end && *it != ']')
{ {
auto value = parse_value(it, end); auto val = parse_value(it, end);
if (auto v = value->as<Value>()) if (auto v = val->as<Value>())
arr->get().push_back(value); arr->get().push_back(val);
else else
throw_parse_exception("Arrays must be homogeneous"); throw_parse_exception("Arrays must be homogeneous");
skip_whitespace_and_comments(it, end); skip_whitespace_and_comments(it, end);
@ -2871,7 +3069,7 @@ class parser
std::string::iterator& it, std::string::iterator& it,
std::string::iterator& end) std::string::iterator& end)
{ {
auto arr = make_element<Object>(); auto arr = detail::make_element<Object>();
while (it != end && *it != ']') while (it != end && *it != ']')
{ {
@ -2881,7 +3079,7 @@ class parser
arr->get().push_back(((*this).*fun)(it, end)); arr->get().push_back(((*this).*fun)(it, end));
skip_whitespace_and_comments(it, end); skip_whitespace_and_comments(it, end);
if (*it != ',') if (it == end || *it != ',')
break; break;
++it; ++it;
@ -2906,8 +3104,11 @@ class parser
throw_parse_exception("Unterminated inline table"); throw_parse_exception("Unterminated inline table");
consume_whitespace(it, end); consume_whitespace(it, end);
parse_key_value(it, end, tbl.get()); if (it != end && *it != '}')
consume_whitespace(it, end); {
parse_key_value(it, end, tbl.get());
consume_whitespace(it, end);
}
} while (*it == ','); } while (*it == ',');
if (it == end || *it != '}') if (it == end || *it != '}')
@ -2987,7 +3188,8 @@ class parser
if (it[4] != '-' || it[7] != '-') if (it[4] != '-' || it[7] != '-')
return {}; return {};
if (len >= 19 && it[10] == 'T' && is_time(it + 11, date_end)) if (len >= 19 && (it[10] == 'T' || it[10] == ' ')
&& is_time(it + 11, date_end))
{ {
// datetime type // datetime type
auto time_end = find_end_of_time(it + 11, date_end); auto time_end = find_end_of_time(it + 11, date_end);
@ -3243,7 +3445,7 @@ class toml_writer
{ {
res += "\\\\"; res += "\\\\";
} }
else if ((const uint32_t)*it <= 0x001f) else if (static_cast<uint32_t>(*it) <= UINT32_C(0x001f))
{ {
res += "\\u"; res += "\\u";
std::stringstream ss; std::stringstream ss;
@ -3274,12 +3476,21 @@ class toml_writer
*/ */
void write(const value<double>& v) void write(const value<double>& v)
{ {
std::ios::fmtflags flags{stream_.flags()}; std::stringstream ss;
ss << std::showpoint
<< std::setprecision(std::numeric_limits<double>::max_digits10)
<< v.get();
stream_ << std::showpoint; auto double_str = ss.str();
write(v.get()); auto pos = double_str.find("e0");
if (pos != std::string::npos)
double_str.replace(pos, 2, "e");
pos = double_str.find("e-0");
if (pos != std::string::npos)
double_str.replace(pos, 3, "e-");
stream_.flags(flags); stream_ << double_str;
has_naked_endline_ = false;
} }
/** /**
@ -3287,9 +3498,9 @@ class toml_writer
* offset_datetime. * offset_datetime.
*/ */
template <class T> template <class T>
typename std::enable_if<is_one_of<T, int64_t, local_date, local_time, typename std::enable_if<
local_datetime, is_one_of<T, int64_t, local_date, local_time, local_datetime,
offset_datetime>::value>::type offset_datetime>::value>::type
write(const value<T>& v) write(const value<T>& v)
{ {
write(v.get()); write(v.get());
@ -3453,5 +3664,5 @@ inline std::ostream& operator<<(std::ostream& stream, const array& a)
a.accept(writer); a.accept(writer);
return stream; return stream;
} }
} } // namespace cpptoml
#endif #endif // CPPTOML_H

View file

@ -81,6 +81,8 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
AttrPath::iterator i; AttrPath::iterator i;
// All attrpaths have at least one attr // All attrpaths have at least one attr
assert(!attrPath.empty()); assert(!attrPath.empty());
// Checking attrPath validity.
// ===========================
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) { for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
if (i->symbol.set()) { if (i->symbol.set()) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
@ -102,11 +104,29 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
attrs = nested; attrs = nested;
} }
} }
// Expr insertion.
// ==========================
if (i->symbol.set()) { if (i->symbol.set()) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) { if (j != attrs->attrs.end()) {
dupAttr(attrPath, pos, j->second.pos); // This attr path is already defined. However, if both
// e and the expr pointed by the attr path are two attribute sets,
// we want to merge them.
// Otherwise, throw an error.
auto ae = dynamic_cast<ExprAttrs *>(e);
auto jAttrs = dynamic_cast<ExprAttrs *>(j->second.e);
if (jAttrs && ae) {
for (auto & ad : ae->attrs) {
auto j2 = jAttrs->attrs.find(ad.first);
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
dupAttr(ad.first, j2->second.pos, ad.second.pos);
jAttrs->attrs[ad.first] = ad.second;
}
} else {
dupAttr(attrPath, pos, j->second.pos);
}
} else { } else {
// This attr path is not defined. Let's create it.
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos); attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos);
e->setName(i->symbol); e->setName(i->symbol);
} }

View file

@ -352,11 +352,18 @@ public:
if (running || done || expected || failed) { if (running || done || expected || failed) {
if (running) if (running)
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt, if (expected != 0)
running / unit, done / unit, expected / unit); s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
running / unit, done / unit, expected / unit);
else
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL,
running / unit, done / unit);
else if (expected != done) else if (expected != done)
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt, if (expected != 0)
done / unit, expected / unit); s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
done / unit, expected / unit);
else
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL, done / unit);
else else
s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit); s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit);
s = fmt(itemFmt, s); s = fmt(itemFmt, s);

View file

@ -1 +1 @@
[ { clients = { data = [ [ "gamma" "delta" ] [ 1 2 ] ]; hosts = [ "alpha" "omega" ]; }; database = { connection_max = 5000; enabled = true; ports = [ 8001 8001 8002 ]; server = "192.168.1.1"; }; owner = { name = "Tom Preston-Werner"; }; servers = { alpha = { dc = "eqdc10"; ip = "10.0.0.1"; }; beta = { dc = "eqdc10"; ip = "10.0.0.2"; }; }; title = "TOML Example"; } { "'key2'" = "value"; "1234" = "value"; "127.0.0.1" = "value"; a = { b = { c = { }; }; }; arr1 = [ 1 2 3 ]; arr2 = [ "red" "yellow" "green" ]; arr3 = [ [ 1 2 ] [ 3 4 5 ] ]; arr4 = [ "all" "strings" "are the same" "type" ]; arr5 = [ [ 1 2 ] [ "a" "b" "c" ] ]; arr7 = [ 1 2 3 ]; arr8 = [ 1 2 ]; bare-key = "value"; bare_key = "value"; bool1 = true; bool2 = false; "character encoding" = "value"; d = { e = { f = { }; }; }; flt1 = 1; flt2 = 3.1415; flt3 = -0.01; flt4 = 5e+22; flt5 = 1e+06; flt6 = -0.02; flt7 = 6.626e-34; flt8 = 9.22462e+06; g = { h = { i = { }; }; }; int1 = 99; int2 = 42; int3 = 0; int4 = -17; int5 = 1000; int6 = 5349221; int7 = 12345; j = { "ʞ" = { "'l'" = { }; }; }; key = "value"; name = "Orange"; products = [ { name = "Hammer"; sku = 738594937; } { } { color = "gray"; name = "Nail"; sku = 284758393; } ]; str = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."; table-1 = { key1 = "some string"; key2 = 123; }; table-2 = { key1 = "another string"; key2 = 456; }; x = { y = { z = { w = { name = { first = "Tom"; last = "Preston-Werner"; }; point = { x = 1; y = 2; }; }; }; }; }; "ʎǝʞ" = "value"; } { metadata = { "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"; "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"; "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"; "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"; }; package = [ { dependencies = [ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" ]; name = "aho-corasick"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.6.4"; } { name = "ansi_term"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.9.0"; } { dependencies = [ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" ]; name = "atty"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.2.10"; } ]; } ] [ { clients = { data = [ [ "gamma" "delta" ] [ 1 2 ] ]; hosts = [ "alpha" "omega" ]; }; database = { connection_max = 5000; enabled = true; ports = [ 8001 8001 8002 ]; server = "192.168.1.1"; }; owner = { name = "Tom Preston-Werner"; }; servers = { alpha = { dc = "eqdc10"; ip = "10.0.0.1"; }; beta = { dc = "eqdc10"; ip = "10.0.0.2"; }; }; title = "TOML Example"; } { "1234" = "value"; "127.0.0.1" = "value"; a = { b = { c = { }; }; }; arr1 = [ 1 2 3 ]; arr2 = [ "red" "yellow" "green" ]; arr3 = [ [ 1 2 ] [ 3 4 5 ] ]; arr4 = [ "all" "strings" "are the same" "type" ]; arr5 = [ [ 1 2 ] [ "a" "b" "c" ] ]; arr7 = [ 1 2 3 ]; arr8 = [ 1 2 ]; bare-key = "value"; bare_key = "value"; bin1 = 214; bool1 = true; bool2 = false; "character encoding" = "value"; d = { e = { f = { }; }; }; dog = { "tater.man" = { type = { name = "pug"; }; }; }; flt1 = 1; flt2 = 3.1415; flt3 = -0.01; flt4 = 5e+22; flt5 = 1e+06; flt6 = -0.02; flt7 = 6.626e-34; flt8 = 9.22462e+06; g = { h = { i = { }; }; }; hex1 = 3735928559; hex2 = 3735928559; hex3 = 3735928559; int1 = 99; int2 = 42; int3 = 0; int4 = -17; int5 = 1000; int6 = 5349221; int7 = 12345; j = { "ʞ" = { l = { }; }; }; key = "value"; key2 = "value"; name = "Orange"; oct1 = 342391; oct2 = 493; physical = { color = "orange"; shape = "round"; }; products = [ { name = "Hammer"; sku = 738594937; } { } { color = "gray"; name = "Nail"; sku = 284758393; } ]; "quoted \"value\"" = "value"; site = { "google.com" = true; }; str = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."; table-1 = { key1 = "some string"; key2 = 123; }; table-2 = { key1 = "another string"; key2 = 456; }; x = { y = { z = { w = { animal = { type = { name = "pug"; }; }; name = { first = "Tom"; last = "Preston-Werner"; }; point = { x = 1; y = 2; }; }; }; }; }; "ʎǝʞ" = "value"; } { metadata = { "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"; "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"; "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"; "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"; }; package = [ { dependencies = [ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" ]; name = "aho-corasick"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.6.4"; } { name = "ansi_term"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.9.0"; } { dependencies = [ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" ]; name = "atty"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.2.10"; } ]; } ]

View file

@ -46,15 +46,15 @@
"character encoding" = "value" "character encoding" = "value"
"ʎǝʞ" = "value" "ʎǝʞ" = "value"
'key2' = "value" 'key2' = "value"
#'quoted "value"' = "value" 'quoted "value"' = "value"
name = "Orange" name = "Orange"
# FIXME: cpptoml doesn't handle dotted keys properly yet. physical.color = "orange"
#physical.color = "orange" physical.shape = "round"
#physical.shape = "round" site."google.com" = true
#site."google.com" = true
# This is legal according to the spec, but cpptoml doesn't handle it.
#a.b.c = 1 #a.b.c = 1
#a.d = 2 #a.d = 2
@ -68,16 +68,14 @@
int6 = 5_349_221 int6 = 5_349_221
int7 = 1_2_3_4_5 int7 = 1_2_3_4_5
# FIXME: cpptoml doesn't support these yet: hex1 = 0xDEADBEEF
hex2 = 0xdeadbeef
hex3 = 0xdead_beef
#hex1 = 0xDEADBEEF oct1 = 0o01234567
#hex2 = 0xdeadbeef oct2 = 0o755
#hex3 = 0xdead_beef
#oct1 = 0o01234567 bin1 = 0b11010110
#oct2 = 0o755
#bin1 = 0b11010110
flt1 = +1.0 flt1 = +1.0
flt2 = 3.1415 flt2 = 3.1415
@ -126,8 +124,8 @@
key1 = "another string" key1 = "another string"
key2 = 456 key2 = 456
#[dog."tater.man"] [dog."tater.man"]
#type.name = "pug" type.name = "pug"
[a.b.c] [a.b.c]
[ d.e.f ] [ d.e.f ]
@ -137,7 +135,7 @@
name = { first = "Tom", last = "Preston-Werner" } name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 } point = { x = 1, y = 2 }
#animal = { type.name = "pug" } animal = { type.name = "pug" }
[[products]] [[products]]
name = "Hammer" name = "Hammer"

View file

@ -0,0 +1,4 @@
{
x.z = 3;
x = { y = 3; z = 3; };
}

View file

@ -0,0 +1,4 @@
{
x.y.y = 3;
x = { y.y= 3; z = 3; };
}

View file

@ -0,0 +1,4 @@
{
x = { y = 3; z = 3; };
x.q = 3;
}

View file

@ -0,0 +1,4 @@
{
x.q = 3;
x = { y = 3; z = 3; };
}

View file

@ -0,0 +1,7 @@
{
services.ssh.enable = true;
services.ssh = { port = 123; };
services = {
httpd.enable = true;
};
}