Compare commits

...

2 commits

Author SHA1 Message Date
fde5579479 [fine] Some design notes
Just like... write some stuff down.
2024-01-21 09:23:45 -08:00
741a729f8d [fine] Working on parser resilience 2024-01-21 09:23:20 -08:00
6 changed files with 170 additions and 1 deletions

41
fine/design.md Normal file
View file

@ -0,0 +1,41 @@
# Design Notes for the Fine Language
This language is being designed as I go, because the main thing I'm
interested in is building something that's fun and productive for me
personally. That means, rather than being super careful, I'm just
building the thing that pleases me at any given moment.
Here are some notes. The notes are for me in the future, in case I'm
wondering why the language is one way instead of another way.
## The `new` keyword
I really like rust's "just use a type name with curly braces to
construct new values". It's really clean! Unfortunately it leads to an
ambiguity in the syntax that I don't like:
``` rust
if something { ...
```
In the code above, after I have parsed `something` and I see `{`, am I:
- Parsing an object construction expression for the type `something`?
- Parsing `something` as a boolean value reference and `{` as the
start of the block?
Naively you would expect the latter, but if I scan ahead a little more:
``` rust
if something { foo: true }.foo { }
```
Rust does not allow `struct` literals in the condition of the `if`,
which is correct, but that's more work than I want to do here. There's
just a lot of context flowing around about whether or not I can parse
a structure literal in any particular situation.
The `new` keyword is a compromise: we know that the context
immediately following the `new` keyword is always a type expression,
so we know that e.g. `<` or whatever means "generic type parameter"
and not "less than".

View file

@ -395,6 +395,15 @@ impl<'a> CParser<'a> {
// eprintln!("{}: {}: {}", self.current.start, self.current, msg);
// }
fn at_any(&self, kinds: &[TokenKind]) -> bool {
for kind in kinds {
if self.at(*kind) {
return true;
}
}
return false;
}
fn at(&self, kind: TokenKind) -> bool {
self.peek() == kind
}
@ -586,6 +595,8 @@ fn field_decl(p: &mut CParser) {
p.end(m, TreeKind::FieldDecl);
}
const PARAM_LIST_RECOVERY: &[TokenKind] = &[TokenKind::Arrow, TokenKind::LeftBrace, TokenKind::Fun];
fn param_list(p: &mut CParser) {
let m = p.start();
@ -594,7 +605,10 @@ fn param_list(p: &mut CParser) {
if p.at(TokenKind::Identifier) {
parameter(p);
} else {
break;
if p.at_any(PARAM_LIST_RECOVERY) {
break;
}
p.advance_with_error("expected parameter");
}
}
p.expect(TokenKind::RightParen, "expect ')' to end a parameter list");

View file

@ -0,0 +1,4 @@
These tests all produce errors in their parse, but the point is that
the trees are kinda as best as we can get.
See e.g. https://matklad.github.io/2023/05/21/resilient-ll-parsing-tutorial.html

View file

@ -0,0 +1,53 @@
fun f1(x: f64,
fun f2(x: f64,, z: f64) {}
fun f3() {}
// @concrete:
// | File
// | FunctionDecl
// | Fun:'"fun"'
// | Identifier:'"f1"'
// | ParamList
// | LeftParen:'"("'
// | Parameter
// | Identifier:'"x"'
// | Colon:'":"'
// | TypeExpression
// | Identifier:'"f64"'
// | Comma:'","'
// | Error:'"Error at 'fun': expect ')' to end a parameter list"'
// | FunctionDecl
// | Fun:'"fun"'
// | Identifier:'"f2"'
// | ParamList
// | LeftParen:'"("'
// | Parameter
// | Identifier:'"x"'
// | Colon:'":"'
// | TypeExpression
// | Identifier:'"f64"'
// | Comma:'","'
// | Error
// | Error:'"Error at ',': expected parameter"'
// | Comma:'","'
// | Parameter
// | Identifier:'"z"'
// | Colon:'":"'
// | TypeExpression
// | Identifier:'"f64"'
// | RightParen:'")"'
// | Block
// | LeftBrace:'"{"'
// | RightBrace:'"}"'
// | FunctionDecl
// | Fun:'"fun"'
// | Identifier:'"f3"'
// | ParamList
// | LeftParen:'"("'
// | RightParen:'")"'
// | Block
// | LeftBrace:'"{"'
// | RightBrace:'"}"'
//

View file

@ -0,0 +1,57 @@
fun fib_rec(f1: f64,
fun fib(n: f64) -> f64 {
fib_rec(1, 1, n)
}
// @concrete:
// | File
// | FunctionDecl
// | Fun:'"fun"'
// | Identifier:'"fib_rec"'
// | ParamList
// | LeftParen:'"("'
// | Parameter
// | Identifier:'"f1"'
// | Colon:'":"'
// | TypeExpression
// | Identifier:'"f64"'
// | Comma:'","'
// | Error:'"Error at 'fun': expect ')' to end a parameter list"'
// | FunctionDecl
// | Fun:'"fun"'
// | Identifier:'"fib"'
// | ParamList
// | LeftParen:'"("'
// | Parameter
// | Identifier:'"n"'
// | Colon:'":"'
// | TypeExpression
// | Identifier:'"f64"'
// | RightParen:'")"'
// | ReturnType
// | Arrow:'"->"'
// | TypeExpression
// | Identifier:'"f64"'
// | Block
// | LeftBrace:'"{"'
// | ExpressionStatement
// | CallExpression
// | Identifier
// | Identifier:'"fib_rec"'
// | ArgumentList
// | LeftParen:'"("'
// | Argument
// | LiteralExpression
// | Number:'"1"'
// | Comma:'","'
// | Argument
// | LiteralExpression
// | Number:'"1"'
// | Comma:'","'
// | Argument
// | Identifier
// | Identifier:'"n"'
// | RightParen:'")"'
// | RightBrace:'"}"'
//