Compare commits
No commits in common. "bc2df73487ea13d588313023fa3b979a8df73d03" and "8fcf71661e79abc7e343bb95e73f3b767b1b318a" have entirely different histories.
bc2df73487
...
8fcf71661e
8 changed files with 149 additions and 251 deletions
|
|
@ -635,7 +635,7 @@ fn def_prepass<'a>(c: &mut Compiler<'a>, src: &Source<'a>, toplevel: NodeId) ->
|
||||||
while let Some(binary) = walk.node_of(NodeKind::Binary) {
|
while let Some(binary) = walk.node_of(NodeKind::Binary) {
|
||||||
let mut binary_walk = src.ast.walk(binary);
|
let mut binary_walk = src.ast.walk(binary);
|
||||||
if let (Some(ident), Some(op)) = (binary_walk.node(), binary_walk.get(NodeKind::Op)) {
|
if let (Some(ident), Some(op)) = (binary_walk.node(), binary_walk.get(NodeKind::Op)) {
|
||||||
if src.ast.span(op).slice(src.code) == ":" {
|
if src.ast.span(op).slice(src.code) == "=" {
|
||||||
let name = src.ast.span(ident).slice(src.code);
|
let name = src.ast.span(ident).slice(src.code);
|
||||||
match c.defs.add_def(name) {
|
match c.defs.add_def(name) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -667,7 +667,7 @@ fn compile_toplevel_expr<'a>(
|
||||||
) -> CompileResult<ToplevelExpr> {
|
) -> CompileResult<ToplevelExpr> {
|
||||||
if src.ast.kind(node_id) == NodeKind::Binary {
|
if src.ast.kind(node_id) == NodeKind::Binary {
|
||||||
if let Some(op) = src.ast.walk(node_id).get(NodeKind::Op) {
|
if let Some(op) = src.ast.walk(node_id).get(NodeKind::Op) {
|
||||||
if src.ast.span(op).slice(src.code) == ":" {
|
if src.ast.span(op).slice(src.code) == "=" {
|
||||||
compile_def(c, src, node_id)?;
|
compile_def(c, src, node_id)?;
|
||||||
return Ok(ToplevelExpr::Def);
|
return Ok(ToplevelExpr::Def);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ fn ident(l: &mut Lexer<'_>) -> TokenKind {
|
||||||
"or" => TokenKind::Or,
|
"or" => TokenKind::Or,
|
||||||
"if" => TokenKind::If,
|
"if" => TokenKind::If,
|
||||||
"else" => TokenKind::Else,
|
"else" => TokenKind::Else,
|
||||||
|
"let" => TokenKind::Let,
|
||||||
_ => TokenKind::Ident,
|
_ => TokenKind::Ident,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +210,6 @@ fn token(l: &mut Lexer<'_>) -> (TokenKind, Span) {
|
||||||
'[' => one(l, TokenKind::LBrack),
|
'[' => one(l, TokenKind::LBrack),
|
||||||
']' => one(l, TokenKind::RBrack),
|
']' => one(l, TokenKind::RBrack),
|
||||||
',' => one(l, TokenKind::Comma),
|
',' => one(l, TokenKind::Comma),
|
||||||
':' => one(l, TokenKind::Colon),
|
|
||||||
'\\' => one(l, TokenKind::Backslash),
|
'\\' => one(l, TokenKind::Backslash),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
||||||
|
|
@ -306,7 +306,7 @@ enum Tighter {
|
||||||
fn tighter(left: TokenKind, right: TokenKind) -> Tighter {
|
fn tighter(left: TokenKind, right: TokenKind) -> Tighter {
|
||||||
fn tightness(kind: TokenKind) -> Option<usize> {
|
fn tightness(kind: TokenKind) -> Option<usize> {
|
||||||
match kind {
|
match kind {
|
||||||
TokenKind::Equal | TokenKind::Colon => Some(0),
|
TokenKind::Equal => Some(0),
|
||||||
TokenKind::EqualEqual
|
TokenKind::EqualEqual
|
||||||
| TokenKind::NotEqual
|
| TokenKind::NotEqual
|
||||||
| TokenKind::Less
|
| TokenKind::Less
|
||||||
|
|
@ -590,6 +590,7 @@ const PREFIX_TOKENS: TokenKindSet = TokenKindSet::new(&[
|
||||||
TokenKind::LParen,
|
TokenKind::LParen,
|
||||||
TokenKind::Backslash,
|
TokenKind::Backslash,
|
||||||
TokenKind::If,
|
TokenKind::If,
|
||||||
|
TokenKind::Let,
|
||||||
TokenKind::LBrack,
|
TokenKind::LBrack,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -605,6 +606,7 @@ fn prefix(p: &mut Parser) -> Closed {
|
||||||
TokenKind::LParen => paren(p),
|
TokenKind::LParen => paren(p),
|
||||||
TokenKind::Backslash => lambda(p),
|
TokenKind::Backslash => lambda(p),
|
||||||
TokenKind::If => if_expr(p),
|
TokenKind::If => if_expr(p),
|
||||||
|
TokenKind::Let => let_expr(p),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -635,9 +637,7 @@ fn infix(p: &mut Parser, op: TokenKind) -> NodeKind {
|
||||||
| TokenKind::LessEqual
|
| TokenKind::LessEqual
|
||||||
| TokenKind::Greater
|
| TokenKind::Greater
|
||||||
| TokenKind::GreaterEqual
|
| TokenKind::GreaterEqual
|
||||||
| TokenKind::Colon => infix_binary(p, op),
|
| TokenKind::Equal => infix_binary(p, op),
|
||||||
|
|
||||||
TokenKind::Equal => infix_let(p, op),
|
|
||||||
|
|
||||||
_ if PREFIX_TOKENS.contains(op) => infix_call(p),
|
_ if PREFIX_TOKENS.contains(op) => infix_call(p),
|
||||||
|
|
||||||
|
|
@ -666,31 +666,6 @@ fn infix_call(p: &mut Parser) -> NodeKind {
|
||||||
NodeKind::Call
|
NodeKind::Call
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infix_let(p: &mut Parser, op: TokenKind) -> NodeKind {
|
|
||||||
p.advance();
|
|
||||||
|
|
||||||
if p.peek() == TokenKind::Newline {
|
|
||||||
p.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
precedence_parse(p, op);
|
|
||||||
|
|
||||||
if p.peek() == TokenKind::Newline {
|
|
||||||
p.advance();
|
|
||||||
} else {
|
|
||||||
let span = p.span();
|
|
||||||
p.emit(Diagnostic::error(
|
|
||||||
span,
|
|
||||||
"new line expected after variable definition `a = b`",
|
|
||||||
));
|
|
||||||
p.advance_with_error();
|
|
||||||
}
|
|
||||||
|
|
||||||
expr(p);
|
|
||||||
|
|
||||||
NodeKind::Let
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expr(p: &mut Parser) {
|
pub fn expr(p: &mut Parser) {
|
||||||
precedence_parse(p, TokenKind::Eof)
|
precedence_parse(p, TokenKind::Eof)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ Binary @ 0..6
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ast_eq(
|
assert_ast_eq(
|
||||||
"1 : 1",
|
"1 = 1",
|
||||||
expr,
|
expr,
|
||||||
"
|
"
|
||||||
Binary @ 0..5
|
Binary @ 0..5
|
||||||
|
|
@ -864,49 +864,52 @@ Toplevel @ 1..63
|
||||||
#[test]
|
#[test]
|
||||||
fn let_expr() {
|
fn let_expr() {
|
||||||
assert_ast_eq(
|
assert_ast_eq(
|
||||||
r#" x = 1
|
r#" let x = 1
|
||||||
x "#,
|
x "#,
|
||||||
toplevel,
|
toplevel,
|
||||||
"
|
"
|
||||||
Toplevel @ 1..20
|
Toplevel @ 1..24
|
||||||
Let @ 1..20
|
Let @ 1..24
|
||||||
Ident @ 1..2
|
Token @ 1..4
|
||||||
Token @ 1..2
|
Ident @ 5..6
|
||||||
Token @ 3..4
|
|
||||||
Number @ 5..6
|
|
||||||
Token @ 5..6
|
Token @ 5..6
|
||||||
Token @ 6..7
|
Token @ 7..8
|
||||||
Ident @ 19..20
|
Number @ 9..10
|
||||||
Token @ 19..20",
|
Token @ 9..10
|
||||||
|
Token @ 10..11
|
||||||
|
Ident @ 23..24
|
||||||
|
Token @ 23..24",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ast_eq(
|
assert_ast_eq(
|
||||||
r#" x = 1
|
r#" let x = 1
|
||||||
y = 2
|
let y = 2
|
||||||
x + y "#,
|
x + y "#,
|
||||||
toplevel,
|
toplevel,
|
||||||
"
|
"
|
||||||
Toplevel @ 1..42
|
Toplevel @ 1..50
|
||||||
Let @ 1..42
|
Let @ 1..50
|
||||||
Ident @ 1..2
|
Token @ 1..4
|
||||||
Token @ 1..2
|
Ident @ 5..6
|
||||||
Token @ 3..4
|
|
||||||
Number @ 5..6
|
|
||||||
Token @ 5..6
|
Token @ 5..6
|
||||||
Token @ 6..7
|
Token @ 7..8
|
||||||
Let @ 19..42
|
Number @ 9..10
|
||||||
Ident @ 19..20
|
Token @ 9..10
|
||||||
Token @ 19..20
|
Token @ 10..11
|
||||||
Token @ 21..22
|
Let @ 23..50
|
||||||
Number @ 23..24
|
Token @ 23..26
|
||||||
Token @ 23..24
|
Ident @ 27..28
|
||||||
Token @ 24..25
|
Token @ 27..28
|
||||||
Binary @ 37..42
|
Token @ 29..30
|
||||||
Ident @ 37..38
|
Number @ 31..32
|
||||||
Token @ 37..38
|
Token @ 31..32
|
||||||
Op @ 39..40
|
Token @ 32..33
|
||||||
Token @ 39..40
|
Binary @ 45..50
|
||||||
Ident @ 41..42
|
Ident @ 45..46
|
||||||
Token @ 41..42",
|
Token @ 45..46
|
||||||
|
Op @ 47..48
|
||||||
|
Token @ 47..48
|
||||||
|
Ident @ 49..50
|
||||||
|
Token @ 49..50",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ pub enum TokenKind {
|
||||||
RBrack,
|
RBrack,
|
||||||
Comma,
|
Comma,
|
||||||
Equal,
|
Equal,
|
||||||
Colon,
|
|
||||||
Backslash,
|
Backslash,
|
||||||
RArrow,
|
RArrow,
|
||||||
|
|
||||||
|
|
@ -44,6 +43,7 @@ pub enum TokenKind {
|
||||||
Or,
|
Or,
|
||||||
If,
|
If,
|
||||||
Else,
|
Else,
|
||||||
|
Let,
|
||||||
|
|
||||||
// NOTE: This must be kept last for TokenSet to work correctly.
|
// NOTE: This must be kept last for TokenSet to work correctly.
|
||||||
Error,
|
Error,
|
||||||
|
|
|
||||||
146
docs/rkgk.dj
146
docs/rkgk.dj
|
|
@ -317,7 +317,7 @@ Once you define a name, its associated data stays the same throughout the entire
|
||||||
So we can define `thickness` to be `4`, and then use it in our scribbles.
|
So we can define `thickness` to be `4`, and then use it in our scribbles.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
thickness: 4
|
thickness = 4
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
|
|
@ -327,11 +327,10 @@ withDotter \d ->
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
`name: data` is a special operator in haku that means "whenever we say `name`, we mean `data`."
|
`name = data` is a special operator in haku that tells the language "whenever we say `name`, we mean `data`."
|
||||||
We call this operator _def_, short for _definition_.
|
|
||||||
|
|
||||||
We cannot use it in arbitrary places in our program, because it wouldn't make sense.
|
We cannot use it in arbitrary places in our program, because it wouldn't make sense.
|
||||||
What does it mean to have a stroke whose thickness is `meow: 5`?
|
What does it mean to have a stroke whose thickness is `meow = 5`?
|
||||||
|
|
||||||
To keep a consistent program structure, haku also forces all your defs to appear _before_ your scribble.
|
To keep a consistent program structure, haku also forces all your defs to appear _before_ your scribble.
|
||||||
You can think of the defs as a list of ingredients for the final scribble.
|
You can think of the defs as a list of ingredients for the final scribble.
|
||||||
|
|
@ -349,8 +348,8 @@ We'll get to why soon!
|
||||||
Anyways, we can likewise replace our `2` constants with a def:
|
Anyways, we can likewise replace our `2` constants with a def:
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
thickness: 4
|
thickness = 4
|
||||||
xOffset: 2
|
xOffset = 2
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
|
|
@ -381,9 +380,9 @@ But now there's a problem.
|
||||||
If we change our `thickness` back to `8`, our points will overlap!
|
If we change our `thickness` back to `8`, our points will overlap!
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
thickness: 8
|
thickness = 8
|
||||||
---
|
---
|
||||||
xOffset: 2
|
xOffset = 2
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
|
|
@ -395,8 +394,8 @@ withDotter \d ->
|
||||||
So we'll make our `xOffset` calculated dynamically from the `thickness`, to not have to update it every time.
|
So we'll make our `xOffset` calculated dynamically from the `thickness`, to not have to update it every time.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
thickness: 8
|
thickness = 8
|
||||||
xOffset: thickness / 2
|
xOffset = thickness / 2
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
|
|
@ -485,7 +484,7 @@ That'll need fixing!
|
||||||
Either way, let's define a function that'll make us those circles!
|
Either way, let's define a function that'll make us those circles!
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
splat: \d, radius ->
|
splat = \d, radius ->
|
||||||
fill #0001 (circle (d To) radius)
|
fill #0001 (circle (d To) radius)
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
|
|
@ -535,7 +534,7 @@ haku limits the use of overloading to system functions for simplicity---adding o
|
||||||
Since these transparent circles are so much easier to draw now, let's make a few more of them!
|
Since these transparent circles are so much easier to draw now, let's make a few more of them!
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
splat: \d, radius ->
|
splat = \d, radius ->
|
||||||
fill #0001 (circle (d To) radius)
|
fill #0001 (circle (d To) radius)
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
|
|
@ -581,10 +580,10 @@ Until some threshold is reached, in which case we just make a single circle.
|
||||||
The first part is easy to do: haku allows us to define a function that calls itself without making any fuss.
|
The first part is easy to do: haku allows us to define a function that calls itself without making any fuss.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
splat: \d, radius ->
|
splat = \d, radius ->
|
||||||
fill #0001 (circle (d To) radius)
|
fill #0001 (circle (d To) radius)
|
||||||
|
|
||||||
airbrush: \d, size ->
|
airbrush = \d, size ->
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
airbrush d (size - 8)
|
airbrush d (size - 8)
|
||||||
|
|
@ -617,9 +616,9 @@ We call this act of switching execution paths _branching_.
|
||||||
Try this out---change the `radius`, and observe how your brush changes color once you set it beyond 16:
|
Try this out---change the `radius`, and observe how your brush changes color once you set it beyond 16:
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
radius: 8
|
radius = 8
|
||||||
|
|
||||||
color:
|
color =
|
||||||
if (radius < 16)
|
if (radius < 16)
|
||||||
#00F
|
#00F
|
||||||
else
|
else
|
||||||
|
|
@ -642,10 +641,10 @@ An `if` only calculates the argument it needs to produce the result.
|
||||||
This allows us to use it to prevent unbounded recursion in our `airbrush` example.
|
This allows us to use it to prevent unbounded recursion in our `airbrush` example.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
splat: \d, radius ->
|
splat = \d, radius ->
|
||||||
fill #0001 (circle (d To) radius)
|
fill #0001 (circle (d To) radius)
|
||||||
|
|
||||||
airbrush: \d, size ->
|
airbrush = \d, size ->
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
|
|
@ -668,10 +667,10 @@ But the airbrush still looks super primitive.
|
||||||
Let's try increasing the fidelity by doing smaller steps!
|
Let's try increasing the fidelity by doing smaller steps!
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
splat: \d, radius ->
|
splat = \d, radius ->
|
||||||
fill #0001 (circle (d To) radius)
|
fill #0001 (circle (d To) radius)
|
||||||
|
|
||||||
airbrush: \d, size ->
|
airbrush = \d, size ->
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
|
|
@ -688,11 +687,11 @@ withDotter \d ->
|
||||||
Well... sure, that's just a black blob with a slight gradient on the outer edge, so let's decrease the opacity.
|
Well... sure, that's just a black blob with a slight gradient on the outer edge, so let's decrease the opacity.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
splat: \d, radius ->
|
splat = \d, radius ->
|
||||||
fill #00000004 (circle (d To) radius)
|
fill #00000004 (circle (d To) radius)
|
||||||
---------
|
---------
|
||||||
|
|
||||||
airbrush: \d, size ->
|
airbrush = \d, size ->
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
|
|
@ -741,7 +740,7 @@ Most commonly, colors are blended using _linear interpolation_---which is essent
|
||||||
Mathematically, linear interpolation is defined using this formula:
|
Mathematically, linear interpolation is defined using this formula:
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
lerp: \a, b, t ->
|
lerp = \a, b, t ->
|
||||||
a + (b - a) * t
|
a + (b - a) * t
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -807,30 +806,12 @@ For now you'll have to construct your brushes with this in mind.
|
||||||
|
|
||||||
### And more limits
|
### And more limits
|
||||||
|
|
||||||
There are more limits on top of this, which stem from rakugaki's design.
|
There are more limits on top of this, which stem from haku's design.
|
||||||
|
Since it's running _your_ code on _my_ server, it has some arbitrary limits set to prevent it from causing much harm.
|
||||||
|
|
||||||
Recall that rakugaki is multiplayer!
|
haku code cannot be too long, and it cannot execute too long.
|
||||||
You can draw with your friends.
|
It cannot consume too much memory---you cannot have too many definitions, or too many temporary values at once.
|
||||||
But, your friends may not have a computer as good as yours.
|
There are also memory usage limits on "heavyweight" data, such as functions or lists.
|
||||||
So to keep the experience fair, haku sets some limits on brush code.
|
|
||||||
|
|
||||||
A brush cannot be too long, and it cannot execute too long.
|
|
||||||
It also cannot consume too much memory---you cannot have too many definitions, or too many temporary values at once.
|
|
||||||
|
|
||||||
If you ever brush (ha ha) up against these limits, you'll see colorful bars appear beside the brush preview in the bottom right corner.
|
|
||||||
These bars show you how much you're nearing the limits!
|
|
||||||
|
|
||||||
Try adding this line to your brush:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
r: range 1 30000
|
|
||||||
```
|
|
||||||
|
|
||||||
`range 1 30000` generates a list of integers between 1 and 30000, without having you write them out one by one.
|
|
||||||
But we can also see that it consumes a bunch of _fuel_ (units of execution time), and _a lot_ of memory---almost all, in fact!
|
|
||||||
|
|
||||||
Code size is harder to run up against, because it requires writing a pretty huge amount of characters into the editor.
|
|
||||||
Feel free to try it out yourself---try writing out some really long lists by hand, and see what happens!
|
|
||||||
|
|
||||||
|
|
||||||
## Reticles
|
## Reticles
|
||||||
|
|
@ -858,17 +839,17 @@ This allows you to _animate_ your brushes over time!
|
||||||
For example, this brush draws a rainbow.
|
For example, this brush draws a rainbow.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
colorCurve: \n ->
|
colorCurve = \n ->
|
||||||
abs (cos n)
|
abs (cos n)
|
||||||
|
|
||||||
pi: 3.14159265
|
pi = 3.14159265
|
||||||
l: 0.1 -- wavelength
|
l = 0.1 -- wavelength
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
r = colorCurve (d Num * l)
|
let r = colorCurve (d Num * l)
|
||||||
g = colorCurve (d Num * l + pi/3)
|
let g = colorCurve (d Num * l + pi/3)
|
||||||
b = colorCurve (d Num * l + 2*pi/3)
|
let b = colorCurve (d Num * l + 2*pi/3)
|
||||||
color = rgba r g b 1
|
let color = rgba r g b 1
|
||||||
stroke 8 color (line (d From) (d To))
|
stroke 8 color (line (d From) (d To))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -878,28 +859,28 @@ Currently, `withDotter` is the only reticle available in rakugaki, and it cannot
|
||||||
In the future rakugaki might get reticles that let you select lines, rectangles, ellipses, curves... but today is not that day.
|
In the future rakugaki might get reticles that let you select lines, rectangles, ellipses, curves... but today is not that day.
|
||||||
|
|
||||||
|
|
||||||
### What's that, `=`?
|
### What's that, `let`?
|
||||||
|
|
||||||
I mentioned before that you cannot have defs inside functions.
|
I mentioned before that you cannot have defs inside functions.
|
||||||
What you _can_ have though, is _variables_, defined with `name = data`.
|
What you _can_ have though, is `let`s, which define _variables_.
|
||||||
|
|
||||||
Unlike defs, which are constant and cannot vary, variables' values can depend on function parameters---and a function can be called with a different set of parameters each time, thus making them variable!
|
Unlike defs, which are constant and cannot vary, variables' values can depend on function parameters---and a function can be called with a different set of parameters each time, thus making them variable!
|
||||||
|
|
||||||
A variable always takes the following form.
|
A `let` always takes the following form.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
name = value
|
let name = value
|
||||||
then
|
then
|
||||||
```
|
```
|
||||||
|
|
||||||
It's very similar to a def, with one major difference.
|
It's very similar to a def, with one major difference.
|
||||||
Because a variable by itself only _names a value_ and does not have a result, it must be followed by another expression on the following line---and that expression determines the result.
|
Because a `let` by itself only _names a value_ and does not have a result, it must be followed by another expression on the following line---and that expression determines the result.
|
||||||
The magic is that this continuing expression can refer to the `name` we had previously assigned in the `name = value` expression.
|
The magic is that this continuing expression can refer to the `name` we had previously assigned in the `let` expression.
|
||||||
|
|
||||||
::: aside
|
::: aside
|
||||||
|
|
||||||
Here's a bit of trivia: variables are exactly the same as function parameters!
|
Here's a bit of trivia: the variable defined by a `let` is exactly the same as a function parameter.
|
||||||
The `name = value` expression above is equivalent to applying the argument `value` to a function taking in the parameter `name`, and returning `then` as the result.
|
The `let` above is equivalent to applying the argument `value` to a function taking in the parameter `name`, and returning `then` as the result.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
(\name -> then) value
|
(\name -> then) value
|
||||||
|
|
@ -912,15 +893,15 @@ That's right. haku is a cute little Haskell for artists.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Variables aren't only useful for reusability---they're also helpful for breaking your brushes into smaller, more digestible pieces!
|
`let`s aren't only useful for reusability---they're also helpful for breaking your brushes into smaller, more digestible pieces!
|
||||||
Compare the above version of the rainbow brush to this version, where all the `let`s are written inline:
|
Compare the above version of the rainbow brush to this version, where all the `let`s are written inline:
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
colorCurve: \n ->
|
colorCurve = \n ->
|
||||||
abs (cos n)
|
abs (cos n)
|
||||||
|
|
||||||
pi: 3.14159265
|
pi = 3.14159265
|
||||||
l: 0.1 -- wavelength
|
l = 0.1 -- wavelength
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
stroke 8 (rgba (colorCurve (d Num * l)) (colorCurve (d Num * l + pi/3)) (colorCurve (d Num * l + 2*pi/3)) 1) (line (d From) (d To))
|
stroke 8 (rgba (colorCurve (d Num * l)) (colorCurve (d Num * l + pi/3)) (colorCurve (d Num * l + 2*pi/3)) 1) (line (d From) (d To))
|
||||||
|
|
@ -931,41 +912,6 @@ That's one hard to read beast of a `stroke`!
|
||||||
Generally, if a line is so long it wraps around rakugaki's narrow little text editor, it's probably a good idea to split it into variables.
|
Generally, if a line is so long it wraps around rakugaki's narrow little text editor, it's probably a good idea to split it into variables.
|
||||||
|
|
||||||
|
|
||||||
### So... sometimes it's `:`, sometimes it's `=`, help, I'm confused!
|
|
||||||
|
|
||||||
If you're having trouble understanding when to use `:` and when to use `=`, here's a short version:
|
|
||||||
|
|
||||||
- `:` defines a name across your whole program.
|
|
||||||
- `=` defines a name that's only visible on the next line (more or less.)
|
|
||||||
|
|
||||||
Since `:` defines a name accessible from your whole program, it cannot access function parameters---which are temporary and local.
|
|
||||||
The opposite of "accessible in the whole program"!
|
|
||||||
|
|
||||||
As for `=`, I said it defines a name that's only visible on the next line "_more or less_", because technically the line that follows can actually be broken up into multiple lines, as would be the case with e.g. an `if`:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
color: #000
|
|
||||||
thickness: 4
|
|
||||||
length: 5
|
|
||||||
duty: 0.5
|
|
||||||
|
|
||||||
or_: \a, b -> -- haku doesn't have a boolean OR yet...
|
|
||||||
if (a) a
|
|
||||||
else b
|
|
||||||
|
|
||||||
withDotter \d ->
|
|
||||||
visible = mod (d Num) length < length * duty
|
|
||||||
if (visible)
|
|
||||||
-- this is more than one line, and `visible` can still be used here!
|
|
||||||
stroke thickness color (line (d From) (d To))
|
|
||||||
else
|
|
||||||
() -- ...and here, too!
|
|
||||||
```
|
|
||||||
|
|
||||||
Another thing: as shown in the examples above, you can chain multiple `=` expressions together, which also breaks this "next line" rule of thumb.
|
|
||||||
But it should still help in building an intuition and spotting the patterns!
|
|
||||||
|
|
||||||
|
|
||||||
## Have fun
|
## Have fun
|
||||||
|
|
||||||
With that said, I hope you can have fun with rakugaki despite it being in its infancy!
|
With that said, I hope you can have fun with rakugaki despite it being in its infancy!
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ Operators may have one or two arguments, where one argument corresponds to a pre
|
||||||
Note that this documentation lists a unary and binary operator of the same spelling as _two separate functions_, not overloads of a single function.
|
Note that this documentation lists a unary and binary operator of the same spelling as _two separate functions_, not overloads of a single function.
|
||||||
|
|
||||||
The argument name usually does not matter when calling the function - it is only used for documentation purposes.
|
The argument name usually does not matter when calling the function - it is only used for documentation purposes.
|
||||||
|
The one exception is arguments called `...`, which signify that zero or more arguments can be passed to the function at that position.
|
||||||
|
(Currently there are no functions that accept any number of arguments, though.)
|
||||||
|
|
||||||
The argument _type_ however is important.
|
The argument _type_ however is important.
|
||||||
If you try to use a function with the wrong type of value as its argument, it will fail with an error.
|
If you try to use a function with the wrong type of value as its argument, it will fail with an error.
|
||||||
|
|
@ -565,7 +567,7 @@ For example, consider multiplicatively blending two colors.
|
||||||
```haku
|
```haku
|
||||||
-- This is how you can multiply two colors together.
|
-- This is how you can multiply two colors together.
|
||||||
-- Note that the `*` operator works for colors, so you don't need to define this in your brushes.
|
-- Note that the `*` operator works for colors, so you don't need to define this in your brushes.
|
||||||
mulRgba: \a, b ->
|
mulRgba = \a, b ->
|
||||||
rgba (rgbaR a * rgbaR b) (rgbaG a * rgbaG b) (rgbaB a * rgbaB b) (rgbaA a * rgbaA b)
|
rgba (rgbaR a * rgbaR b) (rgbaG a * rgbaG b) (rgbaB a * rgbaB b) (rgbaA a * rgbaA b)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -573,11 +575,11 @@ If haku represented colors using an 8-bit `0` to `255` range instead, to multipl
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
-- NOTE: This example does NOT work correctly.
|
-- NOTE: This example does NOT work correctly.
|
||||||
mulRgba: \a, b ->
|
mulRgba = \a, b ->
|
||||||
red = (rgbaR a * rgbaR b) / 255
|
let red = (rgbaR a * rgbaR b) / 255
|
||||||
green = (rgbaG a * rgbaG b) / 255
|
let green = (rgbaG a * rgbaG b) / 255
|
||||||
blue = (rgbaB a * rgbaB b) / 255
|
let blue = (rgbaB a * rgbaB b) / 255
|
||||||
alpha = (rgbaA a * rgbaA b) / 255
|
let alpha = (rgbaA a * rgbaA b) / 255
|
||||||
rgba red green blue alpha
|
rgba red green blue alpha
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -681,38 +683,32 @@ Some of these operations may be a bit confusing, so here are some examples.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
-- To add two to all elements in a list:
|
-- To add two to all elements in a list:
|
||||||
list: range 1 4 -- [1, 2, 3, 4]
|
list = range 1 4 -- [1, 2, 3, 4]
|
||||||
twoAdded: map list \x ->
|
twoAdded = map list \x ->
|
||||||
x + 2
|
x + 2
|
||||||
-- [3, 4, 5, 6]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
-- To filter out only even numbers in a list:
|
-- To filter out only even numbers in a list:
|
||||||
list: range 1 10 -- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
list = range 1 10
|
||||||
isEven: \x -> mod x 2 == 0
|
isEven = \x -> mod x 2 == 0
|
||||||
onlyEven: filter list isEven
|
onlyEven = filter list isEven
|
||||||
-- [2, 4, 6, 8, 10]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
-- To sum all the numbers in a list:
|
-- To sum all the numbers in a list:
|
||||||
list: [1, 3, 10, 2, 30, 4, 1]
|
list = [1, 3, 10, 2, 30, 4, 1]
|
||||||
sum: reduce list 0 \acc, value -> acc + value
|
sum = reduce list 0 \acc, value -> acc + value
|
||||||
-- 51
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
-- To flatten a singly-nested list:
|
-- To flatten a singly-nested list:
|
||||||
list: [[1, 2], [3, 4], [5, 6]]
|
list = [[1, 2], [3, 4], [5, 6]]
|
||||||
flatList: flatten list -- [1, 2, 3, 4, 5, 6]
|
flatList = flatten list -- [1, 2, 3, 4, 5, 6]
|
||||||
|
|
||||||
-- Note that this only applies to a single level of nesting:
|
-- Note that this only applies to a single level of nesting:
|
||||||
deepList: [[[1, 2, 3, 4]]]
|
deepList = [[[1, 2, 3, 4]]]
|
||||||
lessDeepList: flatten deepList -- [[1, 2, 3, 4]]
|
lessDeepList = flatten deepList -- [[1, 2, 3, 4]]
|
||||||
|
|
||||||
-- This can be used to join lists together without nesting:
|
|
||||||
join: \a, b -> flatten [a, b]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ export const builtInPresets = [
|
||||||
code: `
|
code: `
|
||||||
-- Try playing around with the values
|
-- Try playing around with the values
|
||||||
-- and see what happens!
|
-- and see what happens!
|
||||||
color: #000
|
color = #000
|
||||||
thickness: 8
|
thickness = 8
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
stroke thickness color (line (d From) (d To))
|
stroke thickness color (line (d From) (d To))
|
||||||
|
|
@ -23,42 +23,20 @@ withDotter \\d ->
|
||||||
id: "builtin/thick",
|
id: "builtin/thick",
|
||||||
name: "Thick",
|
name: "Thick",
|
||||||
code: `
|
code: `
|
||||||
color: #000
|
color = #000
|
||||||
thickness: 48
|
thickness = 48
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
stroke thickness color (line (d From) (d To))
|
stroke thickness color (line (d From) (d To))
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
id: "builtin/dashes",
|
|
||||||
name: "Dashes",
|
|
||||||
code: `
|
|
||||||
color: #000
|
|
||||||
thickness: 4
|
|
||||||
length: 5
|
|
||||||
duty: 0.5
|
|
||||||
|
|
||||||
or_: \\a, b ->
|
|
||||||
if (a) a
|
|
||||||
else b
|
|
||||||
|
|
||||||
withDotter \\d ->
|
|
||||||
visible = mod (d Num) length < length * duty
|
|
||||||
if (visible)
|
|
||||||
stroke thickness color (line (d From) (d To))
|
|
||||||
else
|
|
||||||
()
|
|
||||||
`.trim(),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "builtin/pencil",
|
id: "builtin/pencil",
|
||||||
name: "Pencil",
|
name: "Pencil",
|
||||||
code: `
|
code: `
|
||||||
color: #0003
|
color = #0003
|
||||||
thickness: 6
|
thickness = 6
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
stroke thickness color (line (d From) (d To))
|
stroke thickness color (line (d From) (d To))
|
||||||
|
|
@ -69,16 +47,16 @@ withDotter \\d ->
|
||||||
id: "builtin/woobly",
|
id: "builtin/woobly",
|
||||||
name: "Woobly",
|
name: "Woobly",
|
||||||
code: `
|
code: `
|
||||||
color: #000
|
color = #000
|
||||||
minThickness: 8
|
minThickness = 8
|
||||||
maxThickness: 20
|
maxThickness = 20
|
||||||
wavelength: 1
|
wavelength = 1
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
pi = 3.14159265
|
let pi = 3.14159265
|
||||||
a = (sin (d Num * wavelength / pi) + 1) / 2
|
let a = (sin (d Num * wavelength / pi) + 1) / 2
|
||||||
range = maxThickness - minThickness
|
let range = maxThickness - minThickness
|
||||||
thickness = minThickness + a * range
|
let thickness = minThickness + a * range
|
||||||
stroke thickness color (line (d From) (d To))
|
stroke thickness color (line (d From) (d To))
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
@ -87,28 +65,28 @@ withDotter \\d ->
|
||||||
id: "builtin/wavy",
|
id: "builtin/wavy",
|
||||||
name: "Wavy",
|
name: "Wavy",
|
||||||
code: `
|
code: `
|
||||||
color: #000
|
color = #000
|
||||||
thickness: 4
|
thickness = 4
|
||||||
amplitude: 50
|
amplitude = 50
|
||||||
wavelength: 1
|
wavelength = 1
|
||||||
|
|
||||||
mag: \\v ->
|
mag = \\v ->
|
||||||
hypot (vecX v) (vecY v)
|
hypot (vecX v) (vecY v)
|
||||||
|
|
||||||
norm: \\u ->
|
norm = \\u ->
|
||||||
l = mag u
|
let l = mag u
|
||||||
u / vec l l
|
u / vec l l
|
||||||
|
|
||||||
perpClockwise: \\v ->
|
perpClockwise = \\v ->
|
||||||
vec (vecY v) (-(vecX v))
|
vec (vecY v) (-(vecX v))
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
pi = 3.14159265
|
let pi = 3.14159265
|
||||||
a = sin (d Num * wavelength / pi) * amplitude
|
let a = sin (d Num * wavelength / pi) * amplitude
|
||||||
direction = (d To) - (d From)
|
let direction = (d To) - (d From)
|
||||||
clockwise = norm (perpClockwise direction) * vec a a
|
let clockwise = norm (perpClockwise direction) * vec a a
|
||||||
from = d From + clockwise
|
let from = d From + clockwise
|
||||||
to = d To + clockwise
|
let to = d To + clockwise
|
||||||
stroke thickness color (line from to)
|
stroke thickness color (line from to)
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
@ -117,19 +95,19 @@ withDotter \\d ->
|
||||||
id: "builtin/rainbow",
|
id: "builtin/rainbow",
|
||||||
name: "Rainbow",
|
name: "Rainbow",
|
||||||
code: `
|
code: `
|
||||||
wavelength: 0.1
|
wavelength = 0.1
|
||||||
thickness: 8
|
thickness = 8
|
||||||
|
|
||||||
colorCurve: \\n ->
|
colorCurve = \\n ->
|
||||||
abs (cos n)
|
abs (cos n)
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
pi = 3.14159265
|
let pi = 3.14159265
|
||||||
l = wavelength
|
let l = wavelength
|
||||||
r = colorCurve (d Num * l)
|
let r = colorCurve (d Num * l)
|
||||||
g = colorCurve (d Num * l + pi/3)
|
let g = colorCurve (d Num * l + pi/3)
|
||||||
b = colorCurve (d Num * l + 2*pi/3)
|
let b = colorCurve (d Num * l + 2*pi/3)
|
||||||
color = rgba r g b 1
|
let color = rgba r g b 1
|
||||||
stroke thickness color (line (d From) (d To))
|
stroke thickness color (line (d From) (d To))
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue