fix a few bugs with the new precedence rules
This commit is contained in:
parent
29a80854a4
commit
9808d3227f
3 changed files with 113 additions and 59 deletions
|
@ -432,22 +432,23 @@ unsafe extern "C" fn haku_compile_brush(
|
|||
);
|
||||
debug!("compiling: {closure_spec:?}");
|
||||
|
||||
// debug!("bytecode: {:?}", chunk.bytecode);
|
||||
// {
|
||||
// let mut cursor = 0_usize;
|
||||
// for info in &chunk.span_info {
|
||||
// let slice = &chunk.bytecode[cursor..cursor + info.len as usize];
|
||||
// debug!(
|
||||
// "{:?} | 0x{:x} {:?} | {:?}",
|
||||
// info.span,
|
||||
// cursor,
|
||||
// slice,
|
||||
// info.span.slice(src.code),
|
||||
// );
|
||||
// cursor += info.len as usize;
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
debug!("bytecode: {:?}", chunk.bytecode);
|
||||
{
|
||||
let mut cursor = 0_usize;
|
||||
for info in &chunk.span_info {
|
||||
let slice = &chunk.bytecode[cursor..cursor + info.len as usize];
|
||||
debug!(
|
||||
"{:?} | 0x{:x} {:?} | {:?}",
|
||||
info.span,
|
||||
cursor,
|
||||
slice,
|
||||
info.span.slice(src.code),
|
||||
);
|
||||
cursor += info.len as usize;
|
||||
}
|
||||
}
|
||||
// */
|
||||
instance.compile_result2 = Some(CompileResult {
|
||||
defs_string: instance.defs.serialize_defs(),
|
||||
tags_string: instance.defs.serialize_tags(),
|
||||
|
|
|
@ -329,7 +329,7 @@ fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
|||
| TokenKind::GreaterEqual => 2,
|
||||
TokenKind::Plus | TokenKind::Minus | TokenKind::Star | TokenKind::Slash => 3,
|
||||
// 4: reserve for `.`
|
||||
_ if PREFIX_TOKENS.contains(kind) => 5,
|
||||
_ if is_prefix_token((kind, spaces)) => 5,
|
||||
_ => return None, // not an infix operator
|
||||
};
|
||||
Some(match kind {
|
||||
|
@ -341,11 +341,11 @@ fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
|||
|
||||
// For unary -, we treat it as having Tight spacing rather than Call, else it would
|
||||
// be allowed to begin function calls.
|
||||
TokenKind::Minus if spaces.pair() == (true, false) => Tightness::Tight(index),
|
||||
TokenKind::Minus if !spaces.are_balanced() => Tightness::Tight(index),
|
||||
|
||||
// For calls, there is a special intermediate level, such that they can sit between
|
||||
// loose operators and tight operators.
|
||||
_ if PREFIX_TOKENS.contains(kind) => Tightness::Call,
|
||||
_ if is_prefix_token((kind, spaces)) => Tightness::Call,
|
||||
|
||||
// For everything else, the usual rules apply.
|
||||
_ => match spaces.pair() {
|
||||
|
@ -366,7 +366,7 @@ fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
|||
|
||||
// When we're inside a call, subsequent arguments must not be slurped into the current
|
||||
// expression, as it would result in calls being parsed as (vec (1 (-1))), which is not correct.
|
||||
if left_tightness == Tightness::Call {
|
||||
if left_tightness == Tightness::Call && right.0 == TokenKind::Minus && !right.1.are_balanced() {
|
||||
return Tighter::Left;
|
||||
}
|
||||
|
||||
|
@ -579,13 +579,15 @@ fn if_expr(p: &mut Parser) -> Closed {
|
|||
p.close(o, NodeKind::If)
|
||||
}
|
||||
|
||||
// TODO: There is a lot of special casing around `-` being both a prefix and an infix token.
|
||||
// Maybe there's a way to simplify it?
|
||||
|
||||
// NOTE: This must be synchronised with the match expression in prefix().
|
||||
const PREFIX_TOKENS: TokenKindSet = TokenKindSet::new(&[
|
||||
TokenKind::Ident,
|
||||
TokenKind::Tag,
|
||||
TokenKind::Number,
|
||||
TokenKind::Color,
|
||||
TokenKind::Minus,
|
||||
TokenKind::Not,
|
||||
TokenKind::LParen,
|
||||
TokenKind::Backslash,
|
||||
|
@ -593,6 +595,10 @@ const PREFIX_TOKENS: TokenKindSet = TokenKindSet::new(&[
|
|||
TokenKind::LBrack,
|
||||
]);
|
||||
|
||||
fn is_prefix_token((kind, spaces): (TokenKind, Spaces)) -> bool {
|
||||
PREFIX_TOKENS.contains(kind) || (kind == TokenKind::Minus && !spaces.are_balanced())
|
||||
}
|
||||
|
||||
fn prefix(p: &mut Parser) -> Closed {
|
||||
let (kind, spaces) = p.peek_with_spaces();
|
||||
match kind {
|
||||
|
@ -602,7 +608,7 @@ fn prefix(p: &mut Parser) -> Closed {
|
|||
TokenKind::Color => one(p, NodeKind::Color),
|
||||
TokenKind::LBrack => list(p),
|
||||
|
||||
TokenKind::Minus if !spaces.right() => unary(p),
|
||||
TokenKind::Minus if spaces.pair() == (true, false) => unary(p),
|
||||
TokenKind::Not => unary(p),
|
||||
TokenKind::LParen => paren(p),
|
||||
TokenKind::Backslash => lambda(p),
|
||||
|
@ -619,9 +625,9 @@ fn prefix(p: &mut Parser) -> Closed {
|
|||
|
||||
_ => {
|
||||
assert!(
|
||||
!PREFIX_TOKENS.contains(p.peek()),
|
||||
"{:?} found in PREFIX_TOKENS",
|
||||
p.peek()
|
||||
!is_prefix_token(p.peek_with_spaces()),
|
||||
"{:?} is not a prefix token",
|
||||
p.peek_with_spaces()
|
||||
);
|
||||
|
||||
let span = p.span();
|
||||
|
@ -651,7 +657,7 @@ fn infix(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
|||
|
||||
TokenKind::Equal => infix_let(p, op),
|
||||
|
||||
_ if PREFIX_TOKENS.contains(op.0) => infix_call(p, op),
|
||||
_ if is_prefix_token(op) => infix_call(p, op),
|
||||
|
||||
_ => panic!("unhandled infix operator {op:?}"),
|
||||
}
|
||||
|
@ -671,7 +677,7 @@ fn infix_binary(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
|||
}
|
||||
|
||||
fn infix_call(p: &mut Parser, mut arg: (TokenKind, Spaces)) -> NodeKind {
|
||||
while PREFIX_TOKENS.contains(p.peek()) {
|
||||
while is_prefix_token(p.peek_with_spaces()) {
|
||||
precedence_parse(p, arg);
|
||||
arg = p.peek_with_spaces();
|
||||
}
|
||||
|
|
111
docs/rkgk.dj
111
docs/rkgk.dj
|
@ -87,7 +87,7 @@ If you want to draw multiple scribbles, you can wrap them into a list, which we
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke 8 #F00 (d To + vec 4 0)
|
||||
stroke 8 #00F (d To + vec (-4) 0)
|
||||
stroke 8 #00F (d To + vec -4 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -109,25 +109,15 @@ withDotter \d ->
|
|||
[
|
||||
[
|
||||
stroke 8 #F00 (d To + vec 4 0)
|
||||
stroke 8 #00F (d To + vec (-4) 0)
|
||||
stroke 8 #00F (d To + vec -4 0)
|
||||
]
|
||||
[
|
||||
stroke 8 #FF0 (d To + vec 0 4)
|
||||
stroke 8 #0FF (d To + vec 0 (-4))
|
||||
stroke 8 #0FF (d To + vec 0 -4)
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
::: aside
|
||||
|
||||
Another weird thing: when negating a number, you have to put it in parentheses.
|
||||
|
||||
This is because haku does not see your spaces---`vec -4`, `vec - 4`, and `vec-4` all mean the same thing!
|
||||
In this case, it will always choose the 2nd interpretation---vec minus four.
|
||||
So to make it interpret our minus four as, well, _minus four_, we need to enclose it in parentheses.
|
||||
|
||||
:::
|
||||
|
||||
This might seem useless, but it's a really useful property in computer programs.
|
||||
It essentially means you can snap pieces together like Lego bricks!
|
||||
|
||||
|
@ -186,7 +176,7 @@ haku vectors however are a little more constrained, because they always contain
|
|||
We call these four numbers X, Y, Z, and W respectively.
|
||||
|
||||
Four is a useful number of dimensions to have, because it lets us do 3D math---which technically isn't built into haku, but if you want it, it's there.
|
||||
For most practical purposes, we'll only be using the first _two_ of the four dimensions though---X and Y.
|
||||
For most practical purposes, we'll only be using the first _two_ of the four dimensions though---X and Y.
|
||||
This is because the wall is a 2D space---it's a flat surface with no depth.
|
||||
|
||||
It's important to know though that vectors don't mean much _by themselves_---rakugaki just chooses them to represent points on the wall, but in a flat 2D space, all points need to be relative to some _origin_---the vector `(0, 0)`.
|
||||
|
@ -205,7 +195,7 @@ withDotter \d ->
|
|||
stroke 8 #000 (d To + vec 10 0) -- moved 10 pixels rightwards
|
||||
```
|
||||
|
||||
Also note how the `d To` expression is parenthesized.
|
||||
Also note how the `d To + vec 10 0` expression is parenthesized.
|
||||
This is because otherwise, its individual parts would be interpreted as separate arguments to `stroke`, which is not what we want!
|
||||
|
||||
Anyways, with all that, we let haku mix all the ingredients together, and get a black dot under the cursor.
|
||||
|
@ -249,16 +239,72 @@ haku also supports other kinds of shapes: circles and rectangles.
|
|||
```haku
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke 8 #F00 (circle (d To + vec (-16) 0) 16)
|
||||
stroke 8 #00F (rect (d To + vec 0 (-16)) 32 32)
|
||||
stroke 8 #F00 (circle (d To + vec -16 0) 16)
|
||||
stroke 8 #00F (rect (d To + vec 0 -16) (vec 32 32))
|
||||
]
|
||||
```
|
||||
|
||||
- `circle`s are made up of an X position, Y position, and radius.
|
||||
|
||||
|
||||
- `rect`s are made up of the (X and Y) position of their top-left corner, and a size (width and height).\
|
||||
Our example produces a square, because the rectangle's width and height are equal!
|
||||
|
||||
|
||||
## Math in haku
|
||||
|
||||
While haku is based entirely in pure math, it is important to note that haku is _not_ math notation!
|
||||
It is a textual programming language, and has different rules concerning order of operations than math.
|
||||
|
||||
::: aside
|
||||
|
||||
If you've programmed in any other language, you might find those rules alien.
|
||||
But I promise you, they make sense in the context of the rest of the language!
|
||||
|
||||
:::
|
||||
|
||||
In traditional math notation, the conventional order of operations is:
|
||||
|
||||
1. Parentheses
|
||||
2. Exponentiation
|
||||
3. Multiplication and division
|
||||
4. Addition and subtraction
|
||||
|
||||
haku does not have an exponentiation operator.
|
||||
That purpose is served by the function `pow`.
|
||||
It does however have parentheses, multiplication, division, addition, and subtraction.
|
||||
|
||||
Unlike in math notation, addition, subtraction, multiplication, and division, are _all_ calculated from left to right---multiplication and division does not take precedence over addition and subtraction.
|
||||
So for the expression `2 + 2 * 2`, the result is `8`, and not `6`!
|
||||
|
||||
Since this can be inconvenient at times, there is a way to work around that.
|
||||
haku has a distinction between _tight_ and _loose_ operators, where tight operators always take precedence over loose ones in the order of operations.
|
||||
|
||||
Remove the spaces around the `*` multiplication operator, like `2 + 2*2`, and the result is now `6` again---because we made `*` tight!
|
||||
|
||||
This is convenient when representing fractions.
|
||||
If you want a constant like half-π, the way to write it is `1/2*pi`---and order of operations will never mess you up, as long as you keep it tight without spaces!
|
||||
|
||||
The same thing happens with functions.
|
||||
For example, if you wanted to calculate the sine of `1/2*pi*x`, as long as you write that as `sin 1/2*pi*x`, with the whole argument without spaces, you won't have to wrap it in parentheses.
|
||||
|
||||
Inside a single whole tight or loose expression, there is still an order of operations.
|
||||
In fact, here's the full order of operations in haku for reference:
|
||||
|
||||
1. Tight
|
||||
|
||||
1. Arithmetic: `+`, `-`, `*`, `/`
|
||||
1. Comparisons: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
||||
|
||||
1. Function calls
|
||||
1. Loose
|
||||
|
||||
1. Arithmetic
|
||||
1. Comparisons
|
||||
1. Variables: `:`, `=`
|
||||
|
||||
Naturally, you can still use parentheses when the loose-tight distinction is not enough.
|
||||
|
||||
|
||||
## Programming in haku
|
||||
|
||||
So far we've been using haku solely to describe data.
|
||||
|
@ -270,7 +316,7 @@ Remember that example from before?
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke 8 #F00 (d To + vec 4 0)
|
||||
stroke 8 #00F (d To + vec (-4) 0)
|
||||
stroke 8 #00F (d To + vec -4 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -281,7 +327,7 @@ If we wanted to change the size of the points, we'd need to first update the str
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke 4 #F00 (d To + vec 4 0)
|
||||
stroke 4 #00F (d To + vec (-4) 0)
|
||||
stroke 4 #00F (d To + vec -4 0)
|
||||
---
|
||||
]
|
||||
```
|
||||
|
@ -294,8 +340,8 @@ So we also have to update their positions.
|
|||
[
|
||||
stroke 4 #F00 (d To + vec 2 0)
|
||||
---
|
||||
stroke 4 #00F (d To + vec (-2) 0)
|
||||
--
|
||||
stroke 4 #00F (d To + vec -2 0)
|
||||
--
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -322,7 +368,7 @@ thickness: 4
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (d To + vec 2 0)
|
||||
stroke thickness #00F (d To + vec (-2) 0)
|
||||
stroke thickness #00F (d To + vec -2 0)
|
||||
---------
|
||||
]
|
||||
```
|
||||
|
@ -355,7 +401,7 @@ xOffset: 2
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (d To + vec xOffset 0)
|
||||
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||
stroke thickness #00F (d To + vec -xOffset 0)
|
||||
---------
|
||||
]
|
||||
```
|
||||
|
@ -371,7 +417,7 @@ Uppercase names are special values we call _tags_.
|
|||
|
||||
Tags are values which represent names.
|
||||
For example, the `To` in `d To` is a tag.
|
||||
It represents the name of the piece of data we're extracting from `d`.
|
||||
It is the name of the piece of data we're extracting from `d`.
|
||||
|
||||
There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood.
|
||||
|
||||
|
@ -388,7 +434,7 @@ xOffset: 2
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (d To + vec xOffset 0)
|
||||
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||
stroke thickness #00F (d To + vec -xOffset 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -402,7 +448,7 @@ xOffset: thickness / 2
|
|||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (d To + vec xOffset 0)
|
||||
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||
stroke thickness #00F (d To + vec -xOffset 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -564,6 +610,7 @@ Seriously, 64 is my limit.
|
|||
|
||||
I wonder if there's any way we could automate this?
|
||||
|
||||
|
||||
### The Ouroboros
|
||||
|
||||
You know the drill by now.
|
||||
|
@ -587,7 +634,7 @@ splat: \d, radius ->
|
|||
airbrush: \d, size ->
|
||||
[
|
||||
splat d size
|
||||
airbrush d (size - 8)
|
||||
airbrush d size-8
|
||||
]
|
||||
|
||||
withDotter \d ->
|
||||
|
@ -649,7 +696,7 @@ airbrush: \d, size ->
|
|||
if (size > 0)
|
||||
[
|
||||
splat d size
|
||||
airbrush d (size - 8)
|
||||
airbrush d size-8
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
@ -675,8 +722,8 @@ airbrush: \d, size ->
|
|||
if (size > 0)
|
||||
[
|
||||
splat d size
|
||||
airbrush d (size - 1)
|
||||
---
|
||||
airbrush d size-1
|
||||
---
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
@ -696,7 +743,7 @@ airbrush: \d, size ->
|
|||
if (size > 0)
|
||||
[
|
||||
splat d size
|
||||
airbrush d (size - 1)
|
||||
airbrush d size-1
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue