mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
DOCS: Update docs for Modules.
Also since We have the reference site now not longer embed the reference in the library documentation.
This commit is contained in:
parent
fa96c7c0ef
commit
64e3b793ae
@ -246,4 +246,47 @@ let my_dbconf = mymacro("couchdb.example.org", "9090");
|
||||
let my_dbhost = dbconf.host;
|
||||
```
|
||||
|
||||
Modules
|
||||
-------
|
||||
|
||||
UCG has another form of reusable execution that is a little more composable than macros
|
||||
are. Modules allow you to parameterize a set of statements and build the statements
|
||||
later. Modules are an expression. They can be bound to a value and then reused later.
|
||||
Modules do not close over their environment by they can import other ucg files into
|
||||
the module using import statements.
|
||||
|
||||
Module expressions start with the module keyword followed by a tuple representing their
|
||||
parameters with any associated default values. The body of the module is separated from
|
||||
the parameter tuple by the `=>` symbol and is delimited by `{` and `}` respectively.
|
||||
|
||||
The body of the module can contain any valid ucg statement.
|
||||
|
||||
```
|
||||
let top_mod = module {
|
||||
deep_value = "None",
|
||||
} => {
|
||||
import "shared.ucg" as shared_macros;
|
||||
|
||||
let embedded_def = module {
|
||||
deep_value = "None",
|
||||
} => {
|
||||
let value = mod.deep_value;
|
||||
};
|
||||
|
||||
let embedded = embedded_def{deep_value = mod.deep_value};
|
||||
};
|
||||
```
|
||||
|
||||
You instantiate a module via the copy expression. The resulting module instance can
|
||||
reference the bindings in the module similarly to selecting a tuple field or a binding
|
||||
from an imported file.
|
||||
|
||||
```
|
||||
let embedded_default_params = top_mod{};
|
||||
embedded_default_params.embedded.value == "None";
|
||||
|
||||
let embedded_with_params = embedded_mod{deep_value = "Some"};
|
||||
embedded_with_params.embedded.value == "Some";
|
||||
```
|
||||
|
||||
Next: <a href="/reference/statements">Statements</a>
|
@ -22,7 +22,14 @@ assert |
|
||||
|
||||
let embedded_mod = module {
|
||||
deep_value = "None",
|
||||
env = "None",
|
||||
} => {
|
||||
let env = select mod.env, "qa", {
|
||||
None = "qa",
|
||||
prod = "prod",
|
||||
qa = "qa",
|
||||
uat = "uat",
|
||||
};
|
||||
let embedded_def = module {
|
||||
deep_value = "None",
|
||||
} => {
|
||||
@ -38,6 +45,10 @@ assert |
|
||||
embedded_default_params.embedded.value == "None";
|
||||
|;
|
||||
|
||||
assert |
|
||||
embedded_default_params.env == "qa";
|
||||
|;
|
||||
|
||||
let embedded_with_params = embedded_mod{deep_value = "Some"};
|
||||
|
||||
assert |
|
||||
|
465
src/lib.rs
465
src/lib.rs
@ -15,470 +15,7 @@
|
||||
|
||||
//! # ucg, A universal configuration grammar.
|
||||
//!
|
||||
//! Ucg defines a common grammar for describing a collection of configuration values.
|
||||
//! ucg allows you to specify configuration values with a syntax that that is immutable,
|
||||
//! composable, with copy-on-write semantics, and safe.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```ucg
|
||||
//! // named bindings
|
||||
//! let host = "mysql.internal.net";
|
||||
//! let port = 8080
|
||||
//!
|
||||
//! // format strings
|
||||
//! let connstr = "mysql://@:@" % (host, port);
|
||||
//!
|
||||
//! // tuples
|
||||
//! let dbconf = {
|
||||
//! connstr = connstr,
|
||||
//! database = "mydb",
|
||||
//! // lists
|
||||
//! tables = ["posts", "comments", "users"],
|
||||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! ## Syntax
|
||||
//!
|
||||
//! A valid ucg file is composed of a series of statements. Statements start with an
|
||||
//! optional keyword and terminate with a semicolon.
|
||||
//!
|
||||
//! ### Reserved words
|
||||
//!
|
||||
//! The following words are reserved in ucg and can't be used as named bindings.
|
||||
//!
|
||||
//! * self
|
||||
//! * assert
|
||||
//! * true
|
||||
//! * false
|
||||
//! * let
|
||||
//! * import
|
||||
//! * as
|
||||
//! * select
|
||||
//! * macro
|
||||
//! * env
|
||||
//! * map
|
||||
//! * filter
|
||||
//! * NULL
|
||||
//! * out
|
||||
//!
|
||||
//! ## Types
|
||||
//!
|
||||
//! ### Primitive types
|
||||
//!
|
||||
//! ucg has a relatively simple syntax with just a few primitive types, Null, Boolean, Int, Float, and String.
|
||||
//!
|
||||
//! ### Boolean
|
||||
//!
|
||||
//! A Boolean is either `true` or `false`.
|
||||
//!
|
||||
//! ```uct
|
||||
//! true;
|
||||
//! false;
|
||||
//! ```
|
||||
//!
|
||||
//! #### Int
|
||||
//!
|
||||
//! An Int is any integer number.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! 1; // a single Integer
|
||||
//! ```
|
||||
//!
|
||||
//! #### Float
|
||||
//!
|
||||
//! A Float is any number with a decimal point.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! 1.0; // A typical float.
|
||||
//! 1. // You can leave off the 0 after the decimal point.
|
||||
//! .1 // the leading 0 is also optional.
|
||||
//! ```
|
||||
//!
|
||||
//! #### String
|
||||
//!
|
||||
//! A String is any quoted text. Backslashes within a string escape the next preceding
|
||||
//! character.
|
||||
//!
|
||||
//! ``` ucg
|
||||
//! "foo"; // a simple string
|
||||
//! "I'm a \"fine\" looking string"; // escaped quotes in a string.
|
||||
//! ```
|
||||
//!
|
||||
//! #### NULL or the Empty type.
|
||||
//!
|
||||
//! A NULL is an empty type. It represents no value.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let empty = NULL;
|
||||
//! ```
|
||||
//!
|
||||
//! ### Complex types
|
||||
//!
|
||||
//! ucg has two complex data types, Lists and Tuples.
|
||||
//!
|
||||
//! #### Lists
|
||||
//!
|
||||
//! Lists are surrounded with square brackets `[ ]` and have comma separated elements. Trailing
|
||||
//! commas are permitted in lists.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! [1, 2, 3]; // A simple list of numbers.
|
||||
//!
|
||||
//! [[1, 2], [3, 4]] // A deep list with embedded lists inside.
|
||||
//! ```
|
||||
//!
|
||||
//! Lists are 0 indexed and you can index into them using the dotted selector syntax.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let mylist = [0, 1, 2, 3];
|
||||
//!
|
||||
//! let zero = mylist.0;
|
||||
//! ```
|
||||
//!
|
||||
//! #### Tuple
|
||||
//!
|
||||
//! Tuple's are an ordered collection of name, value pairs. They are bounded by curly braces `{ }`
|
||||
//! and contain name = value pairs separated by commas. Trailing commas are permitted. The name must
|
||||
//! be either a bareword without quotes or a quoted string.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let mytuple = {
|
||||
//! field1 = "value1",
|
||||
//! field2 = "value2",
|
||||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! Tuples can be indexed using dotted selector syntax.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let field = mytuple.fields1;
|
||||
//! ```
|
||||
//!
|
||||
//! ### Expressions
|
||||
//!
|
||||
//! #### Selectors
|
||||
//!
|
||||
//! Selectors are references to a bound value in ucg. They can index arbitrarily deep into either tuples or lists.
|
||||
//! The head of a selector can be a tuple or list or symbol. Optionally a selector can also be followed by either a
|
||||
//! bareword or string to index a tuple field or an integer to index a list position.
|
||||
//!
|
||||
//! The simplest selector is just a reference to a bound value by name.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let mytuple = {
|
||||
//! field1 = "a string",
|
||||
//! field2 = [{
|
||||
//! subfield1 = 1,
|
||||
//! }];
|
||||
//! };
|
||||
//!
|
||||
//! mytuple.field2.0; // descend into a deeply nested tuple and array.
|
||||
//! ```
|
||||
//!
|
||||
//! ##### The Environment Selector
|
||||
//!
|
||||
//! The `env` selector is a reserved selector that always contains a tuple with any environment
|
||||
//! variables in it.
|
||||
//!
|
||||
//! Attempting to reference an enviroment selector field that does not exist is a compile error.
|
||||
//!
|
||||
//! #### Binary operators
|
||||
//!
|
||||
//! ##### Numeric operators
|
||||
//!
|
||||
//! ucg supports the following numeric operators, `+`, `-`, `*`, `/` Each one is type safe and infers the types
|
||||
//! from the values they operate on. The operators expect both the left and right operands to be of the same
|
||||
//! type.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! 1 + 1; // result is 2
|
||||
//! ```
|
||||
//!
|
||||
//! ##### Concatenation
|
||||
//!
|
||||
//! ucg supports concatenation using the `+` operator. It is typesafe expecting both sides to be of the same type.
|
||||
//! You can concatenate strings or lists but not tuples.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! "foo " + "bar" // result is "foo bar"
|
||||
//! [1,2] + [3,4]; // result is [1,2,3,4]
|
||||
//! ```
|
||||
//!
|
||||
//! ##### Comparison
|
||||
//!
|
||||
//! ucg supports comparison using the `==`, `!=`, `>`, `<`, `>=`, `<=` operators. They are type safe and expect both
|
||||
//! sides to be of the same type.
|
||||
//!
|
||||
//! The `>`, `<`, `>=`, and `>=` operators are only supported on numeric types (i.e. int, and float).
|
||||
//!
|
||||
//! ```ucg
|
||||
//! 1 > 2; // result is false
|
||||
//! 2 < 3; // result is true
|
||||
//! 10 > "9"; // This is a compile error.
|
||||
//! (1+2) == 3
|
||||
//! ```
|
||||
//!
|
||||
//! The equality operators `==` and `!=` are supported for all types and will perform deep equal comparisons on complex
|
||||
//! types.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let tpl1 = {
|
||||
//! foo = "bar",
|
||||
//! one = 1
|
||||
//! };
|
||||
//! let tpl2 = tpl1{}; // copy the tpl1 tuple
|
||||
//! tpl1 == tpl2; // returns true
|
||||
//! let tpl3 = tpl1{duck="quack"};
|
||||
//! tpl1 == tpl3; // returns false
|
||||
//! ```
|
||||
//!
|
||||
//! Note that tuple fields are ordered so a tuple will only be equal if the fields are both in the same order and
|
||||
//! have the same values in them.
|
||||
//!
|
||||
//! ##### Operator Precedence
|
||||
//!
|
||||
//! UCG binary operators follow the typical operator precedence for math. `*` and `/` are higher precendence than
|
||||
//! `+` and `-` which are higher precedence than any of the comparison operators.
|
||||
//!
|
||||
//! #### Copy expressions
|
||||
//!
|
||||
//! ucg Tuples support a form of reuse with copy on write semantics. You can copy a tuple and selectively overwrite fields or add new
|
||||
//! fields to it with the copy expression. To perform a copy first reference the tuple by a bound name and then use `{ field = value, ... }`
|
||||
//! syntax to copy with overridden fields or add completely new fields. When replacing a preexisting field with a new value you cannot
|
||||
//! change the type of the field. This allows you to define a typed shape for a tuple with default values and then provide new values for
|
||||
//! some or all of the fields while still enforcing the same types for those fields. Adding completely new fields has no such restriction.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let base = {
|
||||
//! field1 = "value1",
|
||||
//! field2 = 100,
|
||||
//! field3 = 5.6,
|
||||
//! };
|
||||
//!
|
||||
//! let overridden = base{
|
||||
//! field1 = "new value"
|
||||
//! };
|
||||
//!
|
||||
//! let expanded = base{
|
||||
//! field2 = 200,
|
||||
//! field3 = "look ma a new field",
|
||||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! The following will cause an error because the overriden field's value does not match the original.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let bad = base{
|
||||
//! field1 = 300, // Error!!! must be a string.
|
||||
//! };
|
||||
//!
|
||||
//! ```
|
||||
//! When you are copying a tuple you can use the special symbol `self` to refer to the tuple you are copying.
|
||||
//! This allows you to override fields in nested tuples with an concise syntax. Self always refers
|
||||
//! to the innermost tuple you are copying.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let nestedtpl = {
|
||||
//! field1 = "value1",
|
||||
//! inner = {
|
||||
//! field2 = 2
|
||||
//! inner = {
|
||||
//! field3 = "three",
|
||||
//! },
|
||||
//! },
|
||||
//! };
|
||||
//!
|
||||
//! let copiedtpl = nestedtpl{
|
||||
//! inner = self.inner{
|
||||
//! inner = self.inner{
|
||||
//! field4 = 4,
|
||||
//! },
|
||||
//! },
|
||||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! #### Conditional data
|
||||
//!
|
||||
//! ucg supports a limited form of conditional data selection of using the select expression. A select expression
|
||||
//! starts with the `select` keyword and is followed by a an expression resolving to a string naming the field to
|
||||
//! select, an expression resolving to the default value, and a tuple to select the field from. If the field selected
|
||||
//! is not in the tuple then the default value will be used.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let want = "baz";
|
||||
//!
|
||||
//! // field default
|
||||
//! select want, "quux", {
|
||||
//! baz = "foo",
|
||||
//! fuzz = "bang",
|
||||
//! }; // result will be "foo"
|
||||
//!
|
||||
//! // field default
|
||||
//! select "quack", "quux", {
|
||||
//! baz = "foo",
|
||||
//! fuzz = "bang",
|
||||
//! }; // result will be "quux"
|
||||
//! ```
|
||||
//!
|
||||
//! #### Macros
|
||||
//!
|
||||
//! Macros look like functions but they are resolved at compile time and configurations don't execute so they never appear in output.
|
||||
//! Macros do not close over their environment so they can only reference values defined in their arguments. They can't refer to bindings
|
||||
//! or other macros defined elsewhere. They are useful for constructing tuples of a certain shape or otherwise promoting data reuse.
|
||||
//! You define a macro with the `macro` keyword followed by the arguments in parentheses, a `=>`, and then a tuple.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let mymacro = macro (arg1, arg2) => {
|
||||
//! host = arg1,
|
||||
//! port = arg2,
|
||||
//! connstr = "couchdb://@:@" % (arg1, arg2),
|
||||
//! }
|
||||
//!
|
||||
//! let my_dbconf = mymacro("couchdb.example.org", "9090");
|
||||
//!
|
||||
//! let my_dbhost = dbconf.host;
|
||||
//! ```
|
||||
//!
|
||||
//! macros always resolve to a tuple. If you want to get a single value out you can use selector syntax to retrieve it.
|
||||
//!
|
||||
//! ##### List macros
|
||||
//!
|
||||
//! ucg supports a couple of ways to use macros for mapping or filtering a list to a new list.
|
||||
//!
|
||||
//! A map expression starts with the map keyword followed by the name of a macro that takes exactly
|
||||
//! one argument, a `.`, and the name of the output field for the macro. ucg will apply the macro
|
||||
//! to each element of the list and then take the output field from the resulting tuple and append
|
||||
//! it to the resulting list. If the output field does not exist in the macro output tuple it will
|
||||
//! be a compile error.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let list = [1, 2, 3, 4];
|
||||
//! let mapper = macro(item) => { result = item + 1 };
|
||||
//!
|
||||
//! // results in: [2, 3, 4, 5]
|
||||
//! let mapped = map mapper.result list;
|
||||
//! ```
|
||||
//
|
||||
//! A filter expression starts with the filter keyword followed by the name of a macro with exactly
|
||||
//! one argument, a `.`, and the name of the output field for the macro. The filter will apply the
|
||||
//! macro to each element of the list and if the output field is a value that is not NULL then the
|
||||
//! list element is appended to the output list. If the output field returns a NULL Value or Boolean
|
||||
//! false then the element is not appended to the output list. If the output field does not exist in
|
||||
//! the macro it will be a compile error.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let list = ["foo", "bar", "foo", "bar"];
|
||||
//! let filtrator = macro(item) => {
|
||||
//! ok = select item NULL {
|
||||
//! foo = 1
|
||||
//! }
|
||||
//! };
|
||||
//!
|
||||
//! // results in: ["foo", "foo"]
|
||||
//! let filtered = filter filtrator.ok list;
|
||||
//! ```
|
||||
//!
|
||||
//! ### Statements
|
||||
//!
|
||||
//! There are several kinds of statements in a ucg file. expression statements, let statements, import statements,
|
||||
//! assert statements, and out statements. All ucg statements must be terminated by a semicolon.
|
||||
//!
|
||||
//! #### Expression statements
|
||||
//!
|
||||
//! The simplest and least useful is the expression statement. It is any valid expression followed by a semicolon.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! 1;
|
||||
//! 4 / 2;
|
||||
//! "foo";
|
||||
//! "foo" + "bar";
|
||||
//! ```
|
||||
//!
|
||||
//! Despite the fact that these are valid the results are thrown away and can essentially be considered a noop. If we
|
||||
//! ever create a repl for ucg statements they may prove more useful.
|
||||
//!
|
||||
//! #### Named value statements
|
||||
//!
|
||||
//! There are two statements that can introduce a named value for a given ucg file. Let statnements and import statements.
|
||||
//! Any collisions in binding names inside a file are treated as compile errors. Bindings are immutable and once bound they
|
||||
//! can't be modified.
|
||||
//!
|
||||
//! * Let statements
|
||||
//!
|
||||
//! The let statement binds the result of any valid expression to a name. It starts with the `let` keyword and is followed by
|
||||
//! the name of the binding, an `=`, and a valid ucg expression.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let name = "foo";
|
||||
//! ```
|
||||
//!
|
||||
//! * Import statement
|
||||
//!
|
||||
//! The import statement imports the contents of another ucg file into the current file with a name. The imported files named
|
||||
//! values are exposed as a tuple in the referencing file. It starts with the `import` keyword and is followed by a quoted path
|
||||
//! to the ucg file, the keyword `as`, and a name for the imported values.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! import "dbconfigs.ucg" as dbconfigs;
|
||||
//!
|
||||
//! let mysqlconf = dbconfigs.mysql;
|
||||
//! ```
|
||||
//!
|
||||
//! #### Statements to generate output.
|
||||
//!
|
||||
//! Some statements in ucg exist to generate an output. Either a compiled configuration or the results of test assertions.
|
||||
//!
|
||||
//! * Assert statement
|
||||
//!
|
||||
//! The assert statement defines an expression that must evaluate to either true or false. Assert statements are noops except
|
||||
//! during a validation compile. They give you a way to assert certains properties about your data and can be used as a form
|
||||
//! of unit testing for your configurations. It starts with the assert keyword followed by a valid block of ucg statements
|
||||
//! delimited by `|` characters. The final statement in the in the block must evaluate to a boolean expression.
|
||||
//!
|
||||
//! ```ucg
|
||||
//! assert |
|
||||
//! host == "www.example.com";
|
||||
//! |;
|
||||
//! assert |
|
||||
//! select qa, 443, {
|
||||
//! qa = 80,
|
||||
//! prod = 443,
|
||||
//! } == 443;
|
||||
//! |;
|
||||
//! ```
|
||||
//!
|
||||
//! When _test.ucg files are run in a validation run then ucg will output a log of all the assertions
|
||||
//! to stdout. Giving you a simple test harness for your ucg configs.
|
||||
//!
|
||||
//! * Out statement
|
||||
//!
|
||||
//! The out statement defines the output for a UCG file. It identifies the output converter type and an
|
||||
//! expression that will be output. The output converter type is expected to be one of the registered converters
|
||||
//! (e.g. json, exec) and the artifact file will take the same name as the ucg file with the extension replaced by
|
||||
//! the defined extension for that converter.
|
||||
//!
|
||||
//! For a file named api_config.ucg with the following contents:
|
||||
//!
|
||||
//! ```ucg
|
||||
//! let myconf = {
|
||||
//! api_url = "https://example.org/api/v1/",
|
||||
//! api_token = env.API_TOKEN,
|
||||
//! };
|
||||
//! out json myconf;
|
||||
//! ```
|
||||
//!
|
||||
//! ucg will output the myconf tuple as json to a file called api_config.json
|
||||
//!
|
||||
//! ### Converters
|
||||
//!
|
||||
//! Converters convert the ucg intermediate format into an output artifact. Converters define two different things.
|
||||
//! A conversion for UCG's Intermediate Representation Val's and a file extension for output artifacts.
|
||||
//!
|
||||
//! * json: `json`, Serialized json datastructure.
|
||||
//! * flags: `txt`, command line flags suitable for appending to a command line as arguments.
|
||||
//! * env: `env`, a list of environment variables posix shell style.
|
||||
//! * exec: `sh`, a bash script that will `exec` an executable with arguments and environment variables set.
|
||||
//! Language reference is at https://ucg.marzhillstudios.com/reference
|
||||
|
||||
// The following is necessary to allow the macros in tokenizer and parse modules
|
||||
// to succeed.
|
||||
|
Loading…
x
Reference in New Issue
Block a user