docs and defs

This commit is contained in:
trimill 2025-02-05 21:33:07 -05:00
parent e7716d038e
commit 2f771c55ac
8 changed files with 290 additions and 81 deletions

28
Cargo.lock generated
View file

@ -31,9 +31,9 @@ checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -55,9 +55,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.26" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -65,9 +65,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.26" version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@ -75,9 +75,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.24" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -410,9 +410,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.43" version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -457,9 +457,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -519,9 +519,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"

View file

@ -10,7 +10,6 @@
- [Arithmetic](./lang/arithmetic.md) - [Arithmetic](./lang/arithmetic.md)
- [Bitwise operators](./lang/bitwise.md) - [Bitwise operators](./lang/bitwise.md)
- [Operators reference](./lang/operators.md)
- [Variables and scope](./lang/variables.md) - [Variables and scope](./lang/variables.md)
- [Functions](./lang/functions.md) - [Functions](./lang/functions.md)
- [Partial functions and pipes](./lang/partial.md) - [Partial functions and pipes](./lang/partial.md)
@ -21,5 +20,8 @@
- [Iterators](./lang/iterators.md) - [Iterators](./lang/iterators.md)
- [Ranges](./lang/ranges.md) - [Ranges](./lang/ranges.md)
- [Exceptions](./lang/exceptions.md) - [Exceptions](./lang/exceptions.md)
- [Language reference](./lang/reference.md)
- [Grammar](./lang/grammar.md)
- [Operators reference](./lang/operators.md)

95
docs/src/lang/grammar.md Normal file
View file

@ -0,0 +1,95 @@
# Grammar
Some additional restrictions, such as the precedence of operators or the kinds
of expressions that are valid to assign to, are not represented here. See the
[operators reference](operators.md) for a description of operator precedence.
```grammar
program := block EOF
# LINESEP: either a semicolon or newline
block := LINESEP* (expr LINESEP+)* expr?
expr := assign
| return assign?
| break assign?
| continue
assign := pipeline | pipeline assign_op assign
assign_op := "=" | "++=" | "&=" | "#|=" | "#^=" | "#&=" | "<<=" | ">>=" | "+="
| "-=" | "*=" | "/=" | "//=" | "%=" | "^="
pipeline := lambda | pipeline "|" lambda
lambda := prec_expr
| "\" ident_list "->" lambda
| "&" lambda
# See the operators reference
prec_expr := access
| prec_expr infix_op prec_expr
| prec_expr postfix_op
| prefix_op prec_expr
infix_op := "==" | "!=" | ">" | ">=" | "<" | "<=" | "++" | "&" | ".." | "..="
| "#|" | "#^" | "#&" | "<<" | ">>" | "+" | "-" | "*" | "/" | "//"
| "%" | "^" | "and" | "or"
prefix_op := "-" | "!" | "~" | "*.." | "*..="
postfix_op := "..*"
access := term access_op*
access_op := "(" expr_list ")"
| "[" expr "]"
| "." IDENT
| "->" IDENT "(" expr_list ")"
var := term
| "global" IDENT
| "var" IDENT
| fn_decl
fn_decl := "fn" IDENT? "(" ident_list ")" fn_body
fn_body := "do" block "end"
| "=" expr
term := IDENT
| "(" expr ")"
| "[" list_items "]"
| "{" table_items "}"
| "$"
| "do" block "end"
| "if" if_stmt_chain
| "while" expr "do" block "end"
| "for" IDENT "in" expr "do" block "end"
| "try" block catch_block* "end"
| INTEGER
| FLOAT
| IMAGINARY
| STRING
| SYMBOL
| "true"
| "false"
| "nil"
| "*..*"
if_stmt_chain := expr "then" block if_stmt_end
if_stmt_end := "elif" if_stmt_chain
| "else" block "end"
| "end"
catch_block := "catch" ("*" | symbol_list) ("in" IDENT)? "do" block
symbol_list := (SYMBOL ",")* SYMBOL?
ident_list := (IDENT ",")* IDENT?
expr_list := (expr ",")* expr?
list_items := (list_item ",")* list_item?
list_item := ".."? expr
table_items := (table_item ",")* table_item?
table_item := ".." expr
| term "=" expr
```

View file

@ -4,7 +4,8 @@ Lists in Talc are written as comma-separated items between square brackets.
Lists may be *heterogeneous* (contain items of different types). Similarly to Lists may be *heterogeneous* (contain items of different types). Similarly to
strings, lists may be concatenated using `++`. Lists can be indexed by writing strings, lists may be concatenated using `++`. Lists can be indexed by writing
the index in square brackets after the list. The first element of the list is the index in square brackets after the list. The first element of the list is
at index zero. at index zero. The `&` operator will add a single element to the end of a list,
returning a new list.
```talc ```talc
>> numbers = [1, 5, 6, 4, 2, 3] >> numbers = [1, 5, 6, 4, 2, 3]
@ -13,6 +14,15 @@ at index zero.
4 4
>> numbers ++ [8, 9] >> numbers ++ [8, 9]
[1, 5, 6, 4, 2, 3, 8, 9] [1, 5, 6, 4, 2, 3, 8, 9]
>> numbers & 7
[1, 5, 6, 4, 2, 3, 7]
```
Lists are heterogeneous, so they may contain values of different types.
```talc
>> things = [1, 2.0, "three", 4/1, :five]
[1, 2.0, "three", 4/1, :five]
``` ```
Lists are *mutable* and their elements can be assigned to or modified. Lists are *mutable* and their elements can be assigned to or modified.
@ -46,3 +56,69 @@ ranges further in their own chapter.
>> squares[*..-3] >> squares[*..-3]
[0, 1, 4, 9, 16] [0, 1, 4, 9, 16]
``` ```
## Interpolation
Using the `..` operator, a list can be created by combining the elements from
multiple different collections
```talc
>> even = [2, 4, 6, 8]
[2, 4, 6, 8]
>> odd = [1, 3, 5, 7, ..even]
[1, 3, 5, 7, 2, 4, 6, 8]
>> n = [1, ..[2, 3, 4], 5, ..[6, ..[7, 8]], ..[9]]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
```
Any iterator or iterable value can be used
```talc
>> squares = [..(0..10 | map(\x -> x*x))]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```
## Destructuring
A list can be destructured in an assignment expression as follows:
```talc
>> vals = [6, 2, 8]
[6, 2, 8]
>> [a, b, c] = vals
[6, 2, 8]
>> a
6
>> b
2
>> c
8
```
If the number of elements in the list does not match the number in the
destructure, an error is thrown.
A single interpolation may also be used to collect all remaining values. The
interpolation may occur in the beginning, middle, or end of the assignment.
```talc
>> ns = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
>> [a, b, ..rest] = ns
[1, 2, 3, 4, 5]
>> a
1
>> b
2
>> rest
[3, 4, 5]
>> [a, ..middle, z] = ns
[1, 2, 3, 4, 5]
>> a
1
>> middle
[2, 3, 4]
>> rest
5
```

View file

@ -0,0 +1,4 @@
# Language reference
These pages present a more thorough (and less guided) description of the
Talc language.

View file

@ -105,3 +105,35 @@ hljs.registerLanguage('talc', function() {
] ]
} }
}) })
hljs.registerLanguage('grammar', function() {
const STR_RE = "'[^']*'|\"[^\"]*\"";
const TERMINAL_RE = "\\b[A-Z_]+\\b"
const OP_RE = ":=|\\||\\+|\\*|\\?|\\(|\\)"
return {
name: "Grammar",
contains: [
{
className: 'string',
begin: STR_RE,
relevance: 0
},
{
className: 'variable',
begin: TERMINAL_RE,
relevance: 0
},
{
className: 'literal',
begin: OP_RE,
relevance: 0
},
hljs.COMMENT(
'#', // begin
'\n', // end
{}
)
]
}
})

View file

@ -272,8 +272,15 @@ impl<'s> Parser<'s> {
let ext = self.parse_expr()?; let ext = self.parse_expr()?;
items.push(TableItem::Interpolate(b(ext))); items.push(TableItem::Interpolate(b(ext)));
} }
k if k.expr_first() => { T::True
let key = self.parse_term_not_ident()?; | T::False
| T::Nil
| T::Dollar
| T::Integer
| T::String
| T::Symbol
| T::LParen => {
let key = self.parse_term()?;
expect!(self, T::Equal); expect!(self, T::Equal);
let value = self.parse_expr()?; let value = self.parse_expr()?;
items.push(TableItem::Pair(b(key), b(value))); items.push(TableItem::Pair(b(key), b(value)));
@ -399,9 +406,10 @@ impl<'s> Parser<'s> {
} }
} }
fn parse_term_not_ident(&mut self) -> Result<Expr> { fn parse_term(&mut self) -> Result<Expr> {
let tok = self.next()?; let tok = self.next()?;
match tok.kind { match tok.kind {
T::Identifier => Ok(E::Ident(Symbol::get(tok.content)).span(tok.span)),
T::LParen => { T::LParen => {
let e = self.parse_expr()?; let e = self.parse_expr()?;
expect!(self, T::RParen); expect!(self, T::RParen);
@ -485,26 +493,46 @@ impl<'s> Parser<'s> {
} }
} }
fn parse_term(&mut self) -> Result<Expr> { fn parse_fn_decl(&mut self) -> Result<Expr> {
let Some(tok) = try_next!(self, T::Identifier | T::Var | T::Global) else { let tok_fn = expect!(self, T::Fn);
return self.parse_term_not_ident() let name = try_next!(self, T::Identifier).map(|t| Symbol::get(t.content));
}; expect!(self, T::LParen);
match tok.kind { let args = self.parse_ident_list()?;
T::Identifier => Ok(E::Ident(Symbol::get(tok.content)).span(tok.span)), expect!(self, T::RParen);
match expect!(self, T::Do | T::Equal).kind {
T::Do => {
let content = self.parse_block()?;
let end = expect!(self, T::End);
Ok(E::FnDef(name, args, b(content)).span(tok_fn.span + end.span))
}
T::Equal => {
let content = self.parse_expr()?;
let span = tok_fn.span + content.span;
Ok(E::FnDef(name, args, b(content)).span(span))
}
_ => unreachable!("parse_fn_decl: guaranteed by try_next!"),
}
}
fn parse_var(&mut self) -> Result<Expr> {
match self.peek()?.kind {
T::Global => { T::Global => {
let tok = self.next()?;
let ident = expect!(self, T::Identifier); let ident = expect!(self, T::Identifier);
Ok(E::Global(Symbol::get(ident.content)).span(tok.span + ident.span)) Ok(E::Global(Symbol::get(ident.content)).span(tok.span + ident.span))
} }
T::Var => { T::Var => {
let tok = self.next()?;
let ident = expect!(self, T::Identifier); let ident = expect!(self, T::Identifier);
Ok(E::Var(Symbol::get(ident.content)).span(tok.span + ident.span)) Ok(E::Var(Symbol::get(ident.content)).span(tok.span + ident.span))
} }
_ => unreachable!("guarenteed by try_next"), T::Fn => self.parse_fn_decl(),
_ => self.parse_term(),
} }
} }
fn parse_access(&mut self) -> Result<Expr> { fn parse_access(&mut self) -> Result<Expr> {
let mut lhs = self.parse_term()?; let mut lhs = self.parse_var()?;
loop { loop {
let tok = try_next!(self, T::LParen | T::LBrack | T::Arrow | T::Dot); let tok = try_next!(self, T::LParen | T::LBrack | T::Arrow | T::Dot);
match tok.map(|t| t.kind) { match tok.map(|t| t.kind) {
@ -651,7 +679,7 @@ impl<'s> Parser<'s> {
if let Some(op) = self.peek()?.kind.assign_op() { if let Some(op) = self.peek()?.kind.assign_op() {
let lval = LValue::from_expr(lhs)?; let lval = LValue::from_expr(lhs)?;
self.next()?; self.next()?;
let rhs = self.parse_decl()?; let rhs = self.parse_assign()?;
let rhs_span = rhs.span; let rhs_span = rhs.span;
Ok(E::Assign(op, b(lval), b(rhs)).span(lhs_span + rhs_span)) Ok(E::Assign(op, b(lval), b(rhs)).span(lhs_span + rhs_span))
} else { } else {
@ -659,41 +687,12 @@ impl<'s> Parser<'s> {
} }
} }
fn parse_fn_decl(&mut self) -> Result<Expr> {
let tok_fn = expect!(self, T::Fn);
let name = try_next!(self, T::Identifier).map(|t| Symbol::get(t.content));
expect!(self, T::LParen);
let args = self.parse_ident_list()?;
expect!(self, T::RParen);
match expect!(self, T::Do | T::Equal).kind {
T::Do => {
let content = self.parse_block()?;
let end = expect!(self, T::End);
Ok(E::FnDef(name, args, b(content)).span(tok_fn.span + end.span))
}
T::Equal => {
let content = self.parse_expr()?;
let span = tok_fn.span + content.span;
Ok(E::FnDef(name, args, b(content)).span(span))
}
_ => unreachable!("parse_fn_decl: guaranteed by try_next!"),
}
}
fn parse_decl(&mut self) -> Result<Expr> {
match self.peek()?.kind {
T::Fn => self.parse_fn_decl(),
_ => self.parse_assign(),
}
}
fn parse_expr(&mut self) -> Result<Expr> { fn parse_expr(&mut self) -> Result<Expr> {
let tok = try_next!(self, T::Return | T::Break | T::Continue); match self.peek()?.kind {
if let Some(tok) = tok {
match tok.kind {
T::Return => { T::Return => {
let tok = self.next()?;
if self.peek()?.kind.expr_first() { if self.peek()?.kind.expr_first() {
let expr = self.parse_decl()?; let expr = self.parse_assign()?;
let span = expr.span; let span = expr.span;
Ok(E::Return(Some(b(expr))).span(tok.span + span)) Ok(E::Return(Some(b(expr))).span(tok.span + span))
} else { } else {
@ -701,19 +700,20 @@ impl<'s> Parser<'s> {
} }
} }
T::Break => { T::Break => {
let tok = self.next()?;
if self.peek()?.kind.expr_first() { if self.peek()?.kind.expr_first() {
let expr = self.parse_decl()?; let expr = self.parse_assign()?;
let span = expr.span; let span = expr.span;
Ok(E::Break(Some(b(expr))).span(tok.span + span)) Ok(E::Break(Some(b(expr))).span(tok.span + span))
} else { } else {
Ok(E::Break(None).span(tok.span)) Ok(E::Break(None).span(tok.span))
} }
} }
T::Continue => Ok(E::Continue.span(tok.span)), T::Continue => {
_ => unreachable!("parse_expr: guaranteed by try_next!"), let tok = self.next()?;
Ok(E::Continue.span(tok.span))
} }
} else { _ => self.parse_assign(),
self.parse_decl()
} }
} }