From 1f119040654235922662c2ae35d90e9f3ada540a Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 21 Sep 2023 05:19:06 -0400 Subject: [PATCH 1/7] Begin tree sitter implementation --- Cargo.lock | 23 +++++++++++++++++++++++ Cargo.toml | 5 +++++ src/interface.rs | 34 ++++++++++------------------------ 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a74d40c..4580e1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,6 +1069,7 @@ name = "dust-lang" version = "0.1.1" dependencies = [ "ansi_term", + "cc", "chrono", "clap", "comfy-table", @@ -1091,6 +1092,8 @@ dependencies = [ "toml", "toml_edit", "trash", + "tree-sitter", + "tree-sitter-rust", ] [[package]] @@ -4022,6 +4025,26 @@ dependencies = [ "windows 0.44.0", ] +[[package]] +name = "tree-sitter" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-rust" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0832309b0b2b6d33760ce5c0e818cb47e1d72b468516bfe4134408926fa7594" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "try-lock" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 601df4f..8641ccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,12 @@ name = "gui" name = "dust_lib" path = "src/lib.rs" +[build-dependencies] +cc = "*" + [dependencies] +tree-sitter = "0.20.10" +tree-sitter-rust = "0.20.3" rand = "0.8.5" chrono = "0.4.26" trash = "3.0.3" diff --git a/src/interface.rs b/src/interface.rs index 77f7193..679d792 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -2,6 +2,8 @@ use crate::{token, tree, Result, Value, VariableMap}; +use tree_sitter::{Language, Parser}; + /// Evaluate the given expression string. /// /// # Examples @@ -30,32 +32,16 @@ pub fn eval(string: &str) -> Result { /// assert_eq!(eval_with_context("one + two + three", &mut context), Ok(Value::from(6))); /// ``` pub fn eval_with_context(input: &str, context: &mut VariableMap) -> Result { - let without_comments = input - .lines() - .map(|line| { - let split = line.split_once('#'); + let mut parser = Parser::new(); - if let Some((code, _comment)) = split { - code - } else { - line - } - }) - .collect::(); + parser + .set_language(tree_sitter_rust::language()) + .expect("Error loading Rust grammar"); - let split = without_comments.split_once("->"); + let tree = parser.parse(input, None).unwrap(); + let root_node = tree.root_node().to_sexp(); - if let Some((left, right)) = split { - let left_result = tree::tokens_to_operator_tree(token::tokenize(left)?)? - .eval_with_context_mut(context)?; + println!("{root_node:?}"); - context.set_value("input", left_result)?; - - let right_result = eval_with_context(right, context)?; - - Ok(right_result) - } else { - tree::tokens_to_operator_tree(token::tokenize(&without_comments)?)? - .eval_with_context_mut(context) - } + Ok(Value::Empty) } From 19f600cd241dea73543b9932f4ec466e87015743 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 27 Sep 2023 22:52:03 -0400 Subject: [PATCH 2/7] Add tree sitter submodule --- .gitignore | 1 + .gitmodules | 6 ++++++ tree_sitter_dust | 1 + 3 files changed, 8 insertions(+) create mode 100644 .gitmodules create mode 160000 tree_sitter_dust diff --git a/.gitignore b/.gitignore index 2f7896d..aa8afe6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +node_modules/ target/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f18c3ff --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "tree-sitter-dust"] + path = tree-sitter-dust + url = ./tree-sitter-dust +[submodule "tree_sitter_dust"] + path = tree_sitter_dust + url = ssh://git@git.jeffa.io:22022/jeff/tree-sitter-dust.git diff --git a/tree_sitter_dust b/tree_sitter_dust new file mode 160000 index 0000000..dd857a8 --- /dev/null +++ b/tree_sitter_dust @@ -0,0 +1 @@ +Subproject commit dd857a82911c2a1062e9e563964b68b77c9c4e26 From fca650229b0604032c760bb9b35462b2233cc846 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 5 Oct 2023 11:39:19 -0400 Subject: [PATCH 3/7] Begin rewriting README --- .gitmodules | 3 - README.md | 166 ++++++++++++++++++----------------------------- tree-sitter-dust | 1 + 3 files changed, 64 insertions(+), 106 deletions(-) create mode 160000 tree-sitter-dust diff --git a/.gitmodules b/.gitmodules index f18c3ff..d469035 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "tree-sitter-dust"] path = tree-sitter-dust - url = ./tree-sitter-dust -[submodule "tree_sitter_dust"] - path = tree_sitter_dust url = ssh://git@git.jeffa.io:22022/jeff/tree-sitter-dust.git diff --git a/README.md b/README.md index 72b5fde..ce8b45e 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,26 @@ Dust is a data-oriented programming language and interactive shell. Dust can be A basic dust program: ```dust -output "Hello world!" +output <"Hello world!"> ``` Dust can do two (or more) things at the same time with effortless concurrency: ```dust -run( - 'output "will this one finish first?"', - 'output "or will this one?"' -) +run < + function { output 'will this one finish first?' } + function { output 'or will this one?' } +> ``` Dust can do amazing things with data. To load CSV data, isolate a column and render it as a line plot in a GUI window: ```dust read_file("examples/assets/faithful.csv") - -> from_csv(input) - -> rows(input) - -> transform(input, 'input.1') - -> plot(input) + -> from_csv + -> rows + -> transform <{input.1}> + -> plot ``` @@ -35,23 +35,23 @@ read_file("examples/assets/faithful.csv") - [Contributing](#contributing) - [The Dust Programming Language](#the-dust-programming-language) - [Variables and Data Types](#variables-and-data-types) - - [Tools](#tools) + - [Integers and Floats](#integers-and-floats) - [Lists](#lists) - [Maps](#maps) - [Tables](#tables) - [The Yield Operator](#the-yield-operator) - [Functions](#functions) - - [Time](#time) + - [Empty Values](#empty-values) ## Features -- Data visualization: GUI (not TUI) plots, graphs and charts are available from directly within dust. No external tools are needed. -- Powerful tooling: Built-in commands reduce complex tasks to plain, simple code. You can even partition disks or install software. +- Simplicity: Dust is designed to be easy to learn and powerful to use, without compromising either. +- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness. +- Data format: Dust is data-oriented, so first and foremost it makes a great language for defining data. - Pipelines: Like a pipe in bash, dust features the yield `->` operator. - Format conversion: Effortlessly convert between dust and formats like JSON, CSV and TOML. - Structured data: Dust can represent data with more than just strings. Lists, maps and tables are easy to make and manage. -- Developer tools: Dust has a complete tree sitter grammar, allowing syntax highlighting and completion in most code editors. ## Usage @@ -60,10 +60,10 @@ Dust is an experimental project under active development. At this stage, feature To get help with the shell you can use the "help" tool. ```dust -help() # Returns a table will all tool info. -help("random") # Returns a table with info on tools in the specified group. +help # Returns a table will all tool info. +help <"random"> # Returns a table with info on tools in the specified group. # The above is simply a shorthand for this: -help() -> where(input, 'tool == "random"') +help -> where(input, function { tool == "random" }) ``` ## Installation @@ -94,94 +94,74 @@ Variables have two parts: a key and a value. The key is always a text string. Th - map - table - function -- time -- empty Here are some examples of variables in dust. ```dust -string = "The answer is 42."; -integer = 42; -float = 42.42; -list = (1, 2, string, integer, float); -map.key = "value"; -empty = (); +string = "The answer is 42." +integer = 42 +float = 42.42 +list = (1, 2, string, integer, float) +map.key = 'value' ``` -### Tools +### Integers and Floats -**Tools** are dust's built-in functions. Some of them can reconfigure your whole system while others do very little. They may accept different inputs, or none at all. For example, commands in the `random` group can be run without input, but the `random_integer` command can optionally take two numbers as in inclusive range. - -```dust -die_roll = random_integer(1, 6); -d20_roll = random_integer(1, 20); -coin_flip = random_boolean(); -``` - -```dust -message = "I hate dust."; -replace(message, "hate", "love") -``` +Integer and floating point values are dust's numeric types. Any whole number (i.e. without a decimal) is an integer. Floats are declared by adding a single decimal to or number. If you divide integers or do any kind of math with a float, you will create a float value. ### Lists -Lists are sequential collections. They can be built by grouping values with parentheses and separating them with commas. Values can be indexed by their position to access their contents. Lists are used to represent rows in tables and most commands take a list as an argument. Their contents can be indexed using dot notation with an integer. +Lists are sequential collections. They can be built by grouping values with square brackets. Commas are optional. Values can be indexed by their position to access their contents. Their contents can be indexed using dot notation with an integer. Dust lists are zero-indexed. ```dust -list = (true, 41, "Ok"); +list = [true 41 "Ok"] -assert_equal(list.0, true); +assert_equal -the_answer = list.1 + 1; +the_answer = list.1 + 1 -assert_equal(the_answer, 42); +assert_equal ``` ### Maps -Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. Under the hood, all of dust's runtime variables are stored in a map, so, as with variables, the key is always a string. +Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. Under the hood, all of dust's runtime variables are stored in a map, so, as with variables, the key is always a string. A map is created with a pair of curly braces and its entries and just variables declared inside those braces. Map contents can be accessed using dot notation and a value's key. ```dust -reminder.message = "Buy milk"; -reminder.tags = ("groceries", "home"); +reminder = { + message = "Buy milk" + tags = ("groceries", "home") +} -json = to_json(reminder); -append(json, "info.txt"); +output ``` ### Tables -Tables are strict collections, each row must have a value for each column. Empty cells must be explicitly set to an empty value. +Tables are strict collections, each row must have a value for each column. If a values is "missing" it should be set to an appropriate value for that type. For example a string can be empty and a number can be set to zero. Dust table declarations consist of a list of column names, which are identifiers enclosed in pointed braces. The column names are followed by a pair of curly braces filled with list values. Each list will become a row in the new table. ```dust -animals = create_table ( - ("name", "species", "age"), - ( - ("rover", "cat", 14), - ("spot", "snake", 9), - ("bob", "giraffe", 2) - ) -); +animals = table { + ["rover" "cat" 14] + ["spot" "snake" 9] + ["bob" "giraffe" 2] +} ``` Querying a table is similar to SQL. - + ```dust -names = select(animals, "name"); -youngins = where(animals, 'age < 5'); +names = select name from animals +youngins = select species from animals where age <= 10 ``` The commands `create_table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row. ```dust -insert( - animals, - ( - ("eliza", "ostrich", 4), - ("pat", "white rhino", 7), - ("jim", "walrus", 9) - ) -); +insert into animals + ["eliza" "ostrich" 4] + ["pat" "white rhino" 7] + ["jim" "walrus" 9] assert_equal(count(animals.all), 6); @@ -207,52 +187,32 @@ from_json(json) ### Functions -Functions are first-class values in dust, so they are assigned to variables like any other value. The function body is wrapped in single parentheses. To call a function, it's just like calling a command: simply pass it an argument or use an empty set of parentheses to pass an empty value. - -In the function bod, the **`input` variable** represents whatever value is passed to the function when called. +Functions are first-class values in dust, so they are assigned to variables like any other value. The function body is wrapped in single parentheses. To create a function, use the "function" keyword. The function's arguments are identifiers inside of a set of pointed braces and the function body is enclosed in curly braces. To call a fuction, invoke its variable name and use a set of pointed braces to pass arguments (unless it has no arguments). ```dust -say_hi = 'output "hi"'; -add_one = 'input + 1'; +say_hi = function <> { + output <"hi"> +} -say_hi(); -assert_equal(add_one(3), 4); +add_one = function { + number + 1 +} + +say_hi +assert_equal ``` This function simply passes the input to the shell's standard output. ```dust -print = 'output(input)'; +print = function { + output +} ``` -Because functions are stored in variables, we can use collections like maps to -organize them. +### Empty Values -```dust -math.add = 'input.0 + input.1'; -math.subtract = 'input.0 - input.1'; - -assert_equal(math.add(2, 2), 4); -assert_equal(math.subtract(100, 1), 99); -``` - -### Time - -Dust can record, parse and convert time values. Dust can parse TOML datetime -values or can create time values using commands. - -```dust -dob = from_toml("1979-05-27T07:32:00-08:00") - -output "Date of birth = " + local(dob); -``` - -```dust -time = now(); - -output "Universal time is " + utc(time); -output "Local time is " + local(time); -``` +Dust does not have a null type. Instead, it uses the "empty" type to represent a lack of any other value. There is no syntax to create this value: it is only used by the interpreter. Note that Dust does have the NaN value, which is a floating point value that must exist in order for floats to work as intended. Integers will never be NaN and no value will ever be null or undefined. [dnf]: https://dnf.readthedocs.io/en/latest/index.html [evalexpr]: https://github.com/ISibboI/evalexpr diff --git a/tree-sitter-dust b/tree-sitter-dust new file mode 160000 index 0000000..916b59b --- /dev/null +++ b/tree-sitter-dust @@ -0,0 +1 @@ +Subproject commit 916b59b4b6ff3a9ece271a292932202200df04b8 From e55e2962c4a882b67c3c2b8de4894e9aba764123 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 5 Oct 2023 11:40:48 -0400 Subject: [PATCH 4/7] Remove mistake --- tree_sitter_dust | 1 - 1 file changed, 1 deletion(-) delete mode 160000 tree_sitter_dust diff --git a/tree_sitter_dust b/tree_sitter_dust deleted file mode 160000 index dd857a8..0000000 --- a/tree_sitter_dust +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dd857a82911c2a1062e9e563964b68b77c9c4e26 From 0dbaadf8c6340386e4913d9763b3d092207f77ea Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 5 Oct 2023 12:04:02 -0400 Subject: [PATCH 5/7] Update README --- README.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ce8b45e..00dd820 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dust -Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a tool create or manage data. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax. +Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a tool create or manage data. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax. Dust's grammar is formally defined in code and its minimalism is in large part due to its tree sitter parser, which is lightning-fast and accurate. A basic dust program: @@ -23,7 +23,7 @@ Dust can do amazing things with data. To load CSV data, isolate a column and ren read_file("examples/assets/faithful.csv") -> from_csv -> rows - -> transform <{input.1}> + -> transform <{item.1}> -> plot ``` @@ -32,9 +32,10 @@ read_file("examples/assets/faithful.csv") - [Features](#features) - [Usage](#usage) - [Installation](#installation) + - [Implementation](#implementation) - [Contributing](#contributing) - [The Dust Programming Language](#the-dust-programming-language) - - [Variables and Data Types](#variables-and-data-types) + - [Declaring Variables](#declaring-variables) - [Integers and Floats](#integers-and-floats) - [Lists](#lists) - [Maps](#maps) @@ -72,17 +73,21 @@ You must have the default rust toolchain installed and up-to-date. Install [rust To build from source, clone the repository and run `cargo run` to start the shell. To see other command line options, use `cargo run -- --help`. +## Implementation + +Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust module. Tree sitter generates a parser, written in C, from a set of rules defined in JavaScript. Dust itself is a rust binary that calls the C parser using FFI. Dust does not use Javascript at runtime. + +Tree Sitter generates a concrete syntax tree, which the Rust code maps to an abstract syntax tree by traversing each node once. Tree sitter is fast enough to be updated on every keystroke which is perfect for a data-oriented language like Dust because it allows only the relevant sections to be re-evaluated and the result displayed instantly. + ## Contributing -Please submit any thoughts or suggestions for this project. To contribute a new command, see the library documentation. Implementation tests are written in dust and are run by a corresponding rust test so dust tests will be run when `cargo test` is called. +Please submit any thoughts or suggestions for this project. For instructions on the internal API, see the library documentation. Implementation tests are written in dust and are run by a corresponding rust test so dust tests will be run when `cargo test` is called. ## The Dust Programming Language -Dust is a hard fork of [evalexpr]; a simple expression language. Dust's core language features maintain this simplicity. But it can manage large, complex sets of data and perform complicated tasks through commands. It should not take long for a new user to learn the language, especially with the assistance of the shell. +It should not take long for a new user to learn the language, especially with the assistance of the shell. If your editor supports tree sitter, you can use [tree-sitter-dust] for syntax highlighting and completion support. Aside from this guide, the best way to learn dust is to read the examples and tests to get a better idea of what dust can do. -If your editor supports tree sitter, you can use [tree-sitter-dust] for syntax highlighting and completion support. Aside from this guide, the best way to learn dust is to read the examples and tests to get a better idea of what dust can do. - -### Variables and Data Types +### Declaring Variables Variables have two parts: a key and a value. The key is always a text string. The value can be any of the following data types: @@ -101,10 +106,14 @@ Here are some examples of variables in dust. string = "The answer is 42." integer = 42 float = 42.42 -list = (1, 2, string, integer, float) -map.key = 'value' +list = (1 2 string integer float) +map = { + key = `value` +} ``` +Note that strings can be wrapped with any kind of quote: single, double or backticks. Numbers are always integers by default. And commas are optional in lists. + ### Integers and Floats Integer and floating point values are dust's numeric types. Any whole number (i.e. without a decimal) is an integer. Floats are declared by adding a single decimal to or number. If you divide integers or do any kind of math with a float, you will create a float value. @@ -173,7 +182,7 @@ sorted = sort(animals); Like a pipe in bash, zsh or fish, the yield operator evaluates the expression on the left and passes it as input to the expression on the right. That input is always assigned to the **`input` variable** for that context. These expressions may simply contain a value or they can call a command or function that returns a value. ```dust -"Hello dust!" -> output(input) +"Hello dust!" -> output ``` This can be useful when working on the command line but to make a script easier to read or to avoid fetching the same resource multiple times, we can also declare variables. You should use `->` and variables together to write efficient, elegant scripts. From 5c8686f16f6b6627eb214189dc3b8c10e74d242a Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 5 Oct 2023 12:08:04 -0400 Subject: [PATCH 6/7] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00dd820..5993a3c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dust -Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a tool create or manage data. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax. Dust's grammar is formally defined in code and its minimalism is in large part due to its tree sitter parser, which is lightning-fast and accurate. +Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a tool create or manage data. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax. Dust's grammar is formally defined in code and its minimalism is in large part due to its tree sitter parser, which is lightning-fast, accurate and thoroughly tested. A basic dust program: From 3125302e6030fa61376180dbc00d635a21ea2937 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 5 Oct 2023 15:27:08 -0400 Subject: [PATCH 7/7] Write README and example; Clean up --- README.md | 74 ++++++++++++++++++++----------------------- examples/fibonacci.ds | 11 +++++++ 2 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 examples/fibonacci.ds diff --git a/README.md b/README.md index 5993a3c..2bc341f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Dust -Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a tool create or manage data. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax. Dust's grammar is formally defined in code and its minimalism is in large part due to its tree sitter parser, which is lightning-fast, accurate and thoroughly tested. +Dust is a data-oriented programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a data format. Dust is expression-based, has first-class functions, lexical scope and lightweight syntax. Dust's grammar is formally defined in code and its minimalism is in large part due to its tree sitter parser, which is lightning-fast, accurate and thoroughly tested. A basic dust program: ```dust -output <"Hello world!"> +output { "Hello world!" } ``` Dust can do two (or more) things at the same time with effortless concurrency: @@ -27,6 +27,19 @@ read_file("examples/assets/faithful.csv") -> plot ``` +Dust is also a minimal, obvious data format. It is easier to write than JSON and easier to read than TOML and YAML. However, because it is a programming language, it is able to self-reference, perform calculations or load external data. + +```dust +foo = "bar" +numbers = [1 2 3 4] +truths = { + dust = "the best thing ever" + favorite_number = numbers.3 + another_number = numbers.0 + numbers.1 +} +old_faithful_data = read_file { "faithful.csv" } +``` + - [Dust](#dust) - [Features](#features) @@ -40,7 +53,6 @@ read_file("examples/assets/faithful.csv") - [Lists](#lists) - [Maps](#maps) - [Tables](#tables) - - [The Yield Operator](#the-yield-operator) - [Functions](#functions) - [Empty Values](#empty-values) @@ -62,9 +74,9 @@ To get help with the shell you can use the "help" tool. ```dust help # Returns a table will all tool info. -help <"random"> # Returns a table with info on tools in the specified group. +help {"random"} # Returns a table with info on tools in the specified group. # The above is simply a shorthand for this: -help -> where(input, function { tool == "random" }) +help -> where { input, function { tool == "random" } } ``` ## Installation @@ -89,7 +101,7 @@ It should not take long for a new user to learn the language, especially with th ### Declaring Variables -Variables have two parts: a key and a value. The key is always a text string. The value can be any of the following data types: +Variables have two parts: a key and a value. The key is always a string. The value can be any of the following data types: - string - integer @@ -106,7 +118,7 @@ Here are some examples of variables in dust. string = "The answer is 42." integer = 42 float = 42.42 -list = (1 2 string integer float) +list = [1 2 string integer float] # Commas are optional when writing lists. map = { key = `value` } @@ -125,11 +137,11 @@ Lists are sequential collections. They can be built by grouping values with squa ```dust list = [true 41 "Ok"] -assert_equal +assert_equal { list.0 true } the_answer = list.1 + 1 -assert_equal +assert_equal { the_answer, 42 } ``` ### Maps @@ -139,18 +151,18 @@ Maps are flexible collections with arbitrary key-value pairs, similar to JSON ob ```dust reminder = { message = "Buy milk" - tags = ("groceries", "home") + tags = ["groceries", "home"] } -output +output { reminder.message } ``` ### Tables -Tables are strict collections, each row must have a value for each column. If a values is "missing" it should be set to an appropriate value for that type. For example a string can be empty and a number can be set to zero. Dust table declarations consist of a list of column names, which are identifiers enclosed in pointed braces. The column names are followed by a pair of curly braces filled with list values. Each list will become a row in the new table. +Tables are strict collections, each row must have a value for each column. If a value is "missing" it should be set to an appropriate value for that type. For example, a string can be empty and a number can be set to zero. Dust table declarations consist of a list of column names, which are identifiers enclosed in pointed braces. The column names are followed by a pair of curly braces filled with list values. Each list will become a row in the new table. ```dust -animals = table { +animals = table { ["rover" "cat" 14] ["spot" "snake" 9] ["bob" "giraffe" 2] @@ -164,58 +176,40 @@ names = select name from animals youngins = select species from animals where age <= 10 ``` -The commands `create_table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row. +The keywords `table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row. ```dust -insert into animals +insert into animals { ["eliza" "ostrich" 4] ["pat" "white rhino" 7] ["jim" "walrus" 9] +} -assert_equal(count(animals.all), 6); - -sorted = sort(animals); -``` - -### The Yield Operator - -Like a pipe in bash, zsh or fish, the yield operator evaluates the expression on the left and passes it as input to the expression on the right. That input is always assigned to the **`input` variable** for that context. These expressions may simply contain a value or they can call a command or function that returns a value. - -```dust -"Hello dust!" -> output -``` - -This can be useful when working on the command line but to make a script easier to read or to avoid fetching the same resource multiple times, we can also declare variables. You should use `->` and variables together to write efficient, elegant scripts. - -```dust -json = download("https://api.sampleapis.com/futurama/characters"); -from_json(json) - -> select(input, "name"); - -> input.4 +assert_equal { count { animals }, 6 }; ``` ### Functions -Functions are first-class values in dust, so they are assigned to variables like any other value. The function body is wrapped in single parentheses. To create a function, use the "function" keyword. The function's arguments are identifiers inside of a set of pointed braces and the function body is enclosed in curly braces. To call a fuction, invoke its variable name and use a set of pointed braces to pass arguments (unless it has no arguments). +Functions are first-class values in dust, so they are assigned to variables like any other value. The function body is wrapped in single parentheses. To create a function, use the "function" keyword. The function's arguments are identifiers inside of a set of pointed braces and the function body is enclosed in curly braces. To call a fuction, invoke its variable name and use a set of curly braces to pass arguments (or leave them empty to pass nothing). You don't need commas when listing arguments and you don't need to add whitespace inside the function body but doing so may make your code easier to read. Use your best judgement, the parser will disambiguate any valid syntax. ```dust say_hi = function <> { - output <"hi"> + output {"hi"} } add_one = function { number + 1 } -say_hi -assert_equal +say_hi {} +assert_equal { add_one{3}, 4 } ``` This function simply passes the input to the shell's standard output. ```dust print = function { - output + output { input } } ``` diff --git a/examples/fibonacci.ds b/examples/fibonacci.ds new file mode 100644 index 0000000..ca86507 --- /dev/null +++ b/examples/fibonacci.ds @@ -0,0 +1,11 @@ +fibonacci = function { + if number <= 0 then 0 + else if number == 1 then 1 + else + first = fibonacci { number - 2 } + second = fibonacci { number - 1 } + + first + second +} + +