Compare commits
No commits in common. "914da923f7b5e5aee0d7a703a676a201a3e594b7" and "b52c1b26c9aa6c77d901196d2eee7125d5955d6f" have entirely different histories.
914da923f7
...
b52c1b26c9
15 changed files with 150 additions and 855 deletions
3
.ignore
3
.ignore
|
|
@ -1,3 +0,0 @@
|
||||||
*.ttf
|
|
||||||
*.woff2
|
|
||||||
*.png
|
|
||||||
|
|
@ -401,7 +401,6 @@ unsafe extern "C" fn haku_compile_brush(
|
||||||
"compiling: parsed successfully into {} AST nodes",
|
"compiling: parsed successfully into {} AST nodes",
|
||||||
ast.len()
|
ast.len()
|
||||||
);
|
);
|
||||||
// debug!("ast: {}", ast::dump::dump(&ast, root, Some(code)));
|
|
||||||
|
|
||||||
let src = Source { code, ast: &ast };
|
let src = Source { code, ast: &ast };
|
||||||
|
|
||||||
|
|
@ -432,7 +431,6 @@ unsafe extern "C" fn haku_compile_brush(
|
||||||
);
|
);
|
||||||
debug!("compiling: {closure_spec:?}");
|
debug!("compiling: {closure_spec:?}");
|
||||||
|
|
||||||
/*
|
|
||||||
debug!("bytecode: {:?}", chunk.bytecode);
|
debug!("bytecode: {:?}", chunk.bytecode);
|
||||||
{
|
{
|
||||||
let mut cursor = 0_usize;
|
let mut cursor = 0_usize;
|
||||||
|
|
@ -448,7 +446,7 @@ unsafe extern "C" fn haku_compile_brush(
|
||||||
cursor += info.len as usize;
|
cursor += info.len as usize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// */
|
|
||||||
instance.compile_result2 = Some(CompileResult {
|
instance.compile_result2 = Some(CompileResult {
|
||||||
defs_string: instance.defs.serialize_defs(),
|
defs_string: instance.defs.serialize_defs(),
|
||||||
tags_string: instance.defs.serialize_tags(),
|
tags_string: instance.defs.serialize_tags(),
|
||||||
|
|
|
||||||
|
|
@ -361,59 +361,28 @@ fn compile_binary<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -
|
||||||
}
|
}
|
||||||
let name = src.ast.span(op).slice(src.code);
|
let name = src.ast.span(op).slice(src.code);
|
||||||
|
|
||||||
match name {
|
if name == "=" {
|
||||||
":" => {
|
c.emit(Diagnostic::error(
|
||||||
// Invalid use of a def inside an expression.
|
src.ast.span(op),
|
||||||
c.emit(Diagnostic::error(
|
"defs `a = b` may only appear at the top level",
|
||||||
src.ast.span(op),
|
));
|
||||||
"defs `a: b` may only appear at the top level",
|
return Ok(());
|
||||||
));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
"." => compile_dot_call(c, src, left, right),
|
|
||||||
"|" => compile_pipe_call(c, src, left, right),
|
|
||||||
_ => {
|
|
||||||
compile_expr(c, src, left)?;
|
|
||||||
compile_expr(c, src, right)?;
|
|
||||||
if let Some(index) = system::resolve(SystemFnArity::Binary, name) {
|
|
||||||
let argument_count = 2;
|
|
||||||
c.chunk.emit_opcode(Opcode::System)?;
|
|
||||||
c.chunk.emit_u8(index)?;
|
|
||||||
c.chunk.emit_u8(argument_count)?;
|
|
||||||
} else {
|
|
||||||
c.emit(Diagnostic::error(
|
|
||||||
src.ast.span(op),
|
|
||||||
"this binary operator is currently unimplemented",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_nary_call<'a>(
|
compile_expr(c, src, left)?;
|
||||||
c: &mut Compiler<'a>,
|
compile_expr(c, src, right)?;
|
||||||
src: &Source<'a>,
|
if let Some(index) = system::resolve(SystemFnArity::Binary, name) {
|
||||||
func: NodeId,
|
let argument_count = 2;
|
||||||
argument_count: u8,
|
|
||||||
) -> CompileResult {
|
|
||||||
let name = src.ast.span(func).slice(src.code);
|
|
||||||
if let (NodeKind::Ident, Some(index)) = (
|
|
||||||
src.ast.kind(func),
|
|
||||||
system::resolve(SystemFnArity::Nary, name),
|
|
||||||
) {
|
|
||||||
c.chunk.emit_opcode(Opcode::System)?;
|
c.chunk.emit_opcode(Opcode::System)?;
|
||||||
c.chunk.emit_u8(index)?;
|
c.chunk.emit_u8(index)?;
|
||||||
c.chunk.emit_u8(argument_count)?;
|
c.chunk.emit_u8(argument_count)?;
|
||||||
} else {
|
} else {
|
||||||
// This is a bit of an oddity: we only emit the function expression _after_ the arguments,
|
c.emit(Diagnostic::error(
|
||||||
// but since the language is effectless this doesn't matter in practice.
|
src.ast.span(op),
|
||||||
// It makes for a bit less code in the VM, since there's no need to find the function
|
"this unary operator is currently unimplemented",
|
||||||
// down the stack - it's always on top.
|
));
|
||||||
compile_expr(c, src, func)?;
|
|
||||||
c.chunk.emit_opcode(Opcode::Call)?;
|
|
||||||
c.chunk.emit_u8(argument_count)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,6 +391,7 @@ fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) ->
|
||||||
let Some(func) = walk.node() else {
|
let Some(func) = walk.node() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let name = src.ast.span(func).slice(src.code);
|
||||||
|
|
||||||
let mut argument_count = 0;
|
let mut argument_count = 0;
|
||||||
while let Some(arg) = walk.node() {
|
while let Some(arg) = walk.node() {
|
||||||
|
|
@ -437,57 +407,21 @@ fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) ->
|
||||||
0
|
0
|
||||||
});
|
});
|
||||||
|
|
||||||
emit_nary_call(c, src, func, argument_count)?;
|
if let (NodeKind::Ident, Some(index)) = (
|
||||||
|
src.ast.kind(func),
|
||||||
Ok(())
|
system::resolve(SystemFnArity::Nary, name),
|
||||||
}
|
) {
|
||||||
|
c.chunk.emit_opcode(Opcode::System)?;
|
||||||
fn compile_dot_call<'a>(
|
c.chunk.emit_u8(index)?;
|
||||||
c: &mut Compiler<'a>,
|
c.chunk.emit_u8(argument_count)?;
|
||||||
src: &Source<'a>,
|
} else {
|
||||||
func: NodeId,
|
// This is a bit of an oddity: we only emit the function expression _after_ the arguments,
|
||||||
right: NodeId,
|
// but since the language is effectless this doesn't matter in practice.
|
||||||
) -> CompileResult {
|
// It makes for a bit less code in the VM, since there's no need to find the function
|
||||||
compile_expr(c, src, right)?;
|
// down the stack - it's always on top.
|
||||||
emit_nary_call(c, src, func, 1)?;
|
compile_expr(c, src, func)?;
|
||||||
|
c.chunk.emit_opcode(Opcode::Call)?;
|
||||||
Ok(())
|
c.chunk.emit_u8(argument_count)?;
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_pipe_call<'a>(
|
|
||||||
c: &mut Compiler<'a>,
|
|
||||||
src: &Source<'a>,
|
|
||||||
left: NodeId,
|
|
||||||
call: NodeId,
|
|
||||||
) -> CompileResult {
|
|
||||||
match src.ast.kind(call) {
|
|
||||||
NodeKind::Call => {
|
|
||||||
let mut walk = src.ast.walk(call);
|
|
||||||
let Some(func) = walk.node() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
compile_expr(c, src, left)?;
|
|
||||||
let mut argument_count = 1;
|
|
||||||
while let Some(arg) = walk.node() {
|
|
||||||
compile_expr(c, src, arg)?;
|
|
||||||
argument_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let argument_count = u8::try_from(argument_count).unwrap_or_else(|_| {
|
|
||||||
c.emit(Diagnostic::error(
|
|
||||||
src.ast.span(call),
|
|
||||||
"function call has too many arguments",
|
|
||||||
));
|
|
||||||
0
|
|
||||||
});
|
|
||||||
|
|
||||||
emit_nary_call(c, src, func, argument_count)?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
compile_expr(c, src, left)?;
|
|
||||||
emit_nary_call(c, src, call, 1)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,7 @@ fn one_or_two(l: &mut Lexer<'_>, kind1: TokenKind, c2: char, kind2: TokenKind) -
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ident_char(c: char) -> bool {
|
fn is_ident_char(c: char) -> bool {
|
||||||
matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')
|
matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '\'' | '?')
|
||||||
}
|
|
||||||
|
|
||||||
fn is_ident_extra_char(c: char) -> bool {
|
|
||||||
matches!(c, '\'' | '?')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ident(l: &mut Lexer<'_>) -> TokenKind {
|
fn ident(l: &mut Lexer<'_>) -> TokenKind {
|
||||||
|
|
@ -69,9 +65,6 @@ fn ident(l: &mut Lexer<'_>) -> TokenKind {
|
||||||
while is_ident_char(l.current()) {
|
while is_ident_char(l.current()) {
|
||||||
l.advance();
|
l.advance();
|
||||||
}
|
}
|
||||||
while is_ident_extra_char(l.current()) {
|
|
||||||
l.advance();
|
|
||||||
}
|
|
||||||
let end = l.position;
|
let end = l.position;
|
||||||
|
|
||||||
match Span::new(start, end).slice(l.input) {
|
match Span::new(start, end).slice(l.input) {
|
||||||
|
|
@ -215,8 +208,6 @@ fn token(l: &mut Lexer<'_>) -> (TokenKind, Span, bool) {
|
||||||
'!' => one_or_two(l, TokenKind::Not, '=', TokenKind::NotEqual),
|
'!' => one_or_two(l, TokenKind::Not, '=', TokenKind::NotEqual),
|
||||||
'<' => one_or_two(l, TokenKind::Less, '=', TokenKind::LessEqual),
|
'<' => one_or_two(l, TokenKind::Less, '=', TokenKind::LessEqual),
|
||||||
'>' => one_or_two(l, TokenKind::Greater, '=', TokenKind::GreaterEqual),
|
'>' => one_or_two(l, TokenKind::Greater, '=', TokenKind::GreaterEqual),
|
||||||
'.' => one(l, TokenKind::Dot),
|
|
||||||
'|' => one(l, TokenKind::Pipe),
|
|
||||||
|
|
||||||
'\n' => return newline(l, has_left_space),
|
'\n' => return newline(l, has_left_space),
|
||||||
'(' => one(l, TokenKind::LParen),
|
'(' => one(l, TokenKind::LParen),
|
||||||
|
|
|
||||||
|
|
@ -311,13 +311,31 @@ enum Tighter {
|
||||||
|
|
||||||
fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum Tightness {
|
enum Spacing {
|
||||||
Loose(usize),
|
Loose,
|
||||||
Call,
|
Call,
|
||||||
Tight(usize),
|
Tight,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tightness((kind, spaces): (TokenKind, Spaces)) -> Option<Tightness> {
|
fn tightness((kind, spaces): (TokenKind, Spaces)) -> Option<(Spacing, usize)> {
|
||||||
|
let spacing = match kind {
|
||||||
|
// There are a few types of operators which are independent of tightness.
|
||||||
|
|
||||||
|
// For : and =, it does not matter if they're spelled one way or the other, because
|
||||||
|
// there is only one way to use them (at the beginning of the expression).
|
||||||
|
TokenKind::Colon | TokenKind::Equal => Spacing::Loose,
|
||||||
|
|
||||||
|
// For calls, there is a special intermediate level, such that they can sit between
|
||||||
|
// loose operators and tight operators.
|
||||||
|
_ if PREFIX_TOKENS.contains(kind) => Spacing::Call,
|
||||||
|
|
||||||
|
// For everything else, the usual rules apply.
|
||||||
|
_ => match spaces.pair() {
|
||||||
|
(false, false) => Spacing::Tight,
|
||||||
|
(true, true) => Spacing::Loose,
|
||||||
|
_ => return None, // not a valid infix operator
|
||||||
|
},
|
||||||
|
};
|
||||||
let index = match kind {
|
let index = match kind {
|
||||||
TokenKind::Equal | TokenKind::Colon => 0,
|
TokenKind::Equal | TokenKind::Colon => 0,
|
||||||
// 1: reserved for `and` and `or`
|
// 1: reserved for `and` and `or`
|
||||||
|
|
@ -327,36 +345,12 @@ fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
||||||
| TokenKind::LessEqual
|
| TokenKind::LessEqual
|
||||||
| TokenKind::Greater
|
| TokenKind::Greater
|
||||||
| TokenKind::GreaterEqual => 2,
|
| TokenKind::GreaterEqual => 2,
|
||||||
|
TokenKind::Plus | TokenKind::Minus | TokenKind::Star | TokenKind::Slash => 3,
|
||||||
TokenKind::Plus
|
// 4: reserve for `.`
|
||||||
| TokenKind::Minus
|
_ if PREFIX_TOKENS.contains(kind) => 5,
|
||||||
| TokenKind::Star
|
|
||||||
| TokenKind::Slash
|
|
||||||
| TokenKind::Pipe => 3,
|
|
||||||
|
|
||||||
TokenKind::Dot => 4,
|
|
||||||
_ if is_prefix_token((kind, spaces)) => 5,
|
|
||||||
_ => return None, // not an infix operator
|
_ => return None, // not an infix operator
|
||||||
};
|
};
|
||||||
Some(match kind {
|
Some((spacing, index))
|
||||||
// There are a few types of operators which work independent of tightness.
|
|
||||||
TokenKind::Colon | TokenKind::Equal | TokenKind::Pipe => Tightness::Loose(index),
|
|
||||||
|
|
||||||
// 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.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 is_prefix_token((kind, spaces)) => Tightness::Call,
|
|
||||||
|
|
||||||
// For everything else, the usual rules apply.
|
|
||||||
_ => match spaces.pair() {
|
|
||||||
(false, false) => Tightness::Tight(index),
|
|
||||||
(true, true) => Tightness::Loose(index),
|
|
||||||
_ => return None, // not a valid infix operator
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(right_tightness) = tightness(right) else {
|
let Some(right_tightness) = tightness(right) else {
|
||||||
|
|
@ -367,12 +361,6 @@ fn tighter(left: (TokenKind, Spaces), right: (TokenKind, Spaces)) -> Tighter {
|
||||||
return Tighter::Right;
|
return Tighter::Right;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 && right.0 == TokenKind::Minus && !right.1.are_balanced() {
|
|
||||||
return Tighter::Left;
|
|
||||||
}
|
|
||||||
|
|
||||||
if right_tightness > left_tightness {
|
if right_tightness > left_tightness {
|
||||||
Tighter::Right
|
Tighter::Right
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -582,15 +570,13 @@ fn if_expr(p: &mut Parser) -> Closed {
|
||||||
p.close(o, NodeKind::If)
|
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().
|
// NOTE: This must be synchronised with the match expression in prefix().
|
||||||
const PREFIX_TOKENS: TokenKindSet = TokenKindSet::new(&[
|
const PREFIX_TOKENS: TokenKindSet = TokenKindSet::new(&[
|
||||||
TokenKind::Ident,
|
TokenKind::Ident,
|
||||||
TokenKind::Tag,
|
TokenKind::Tag,
|
||||||
TokenKind::Number,
|
TokenKind::Number,
|
||||||
TokenKind::Color,
|
TokenKind::Color,
|
||||||
|
TokenKind::Minus,
|
||||||
TokenKind::Not,
|
TokenKind::Not,
|
||||||
TokenKind::LParen,
|
TokenKind::LParen,
|
||||||
TokenKind::Backslash,
|
TokenKind::Backslash,
|
||||||
|
|
@ -598,10 +584,6 @@ const PREFIX_TOKENS: TokenKindSet = TokenKindSet::new(&[
|
||||||
TokenKind::LBrack,
|
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 {
|
fn prefix(p: &mut Parser) -> Closed {
|
||||||
match p.peek() {
|
match p.peek() {
|
||||||
TokenKind::Ident => one(p, NodeKind::Ident),
|
TokenKind::Ident => one(p, NodeKind::Ident),
|
||||||
|
|
@ -610,17 +592,16 @@ fn prefix(p: &mut Parser) -> Closed {
|
||||||
TokenKind::Color => one(p, NodeKind::Color),
|
TokenKind::Color => one(p, NodeKind::Color),
|
||||||
TokenKind::LBrack => list(p),
|
TokenKind::LBrack => list(p),
|
||||||
|
|
||||||
TokenKind::Minus => unary(p),
|
TokenKind::Minus | TokenKind::Not => unary(p),
|
||||||
TokenKind::Not => unary(p),
|
|
||||||
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),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
assert!(
|
assert!(
|
||||||
!is_prefix_token(p.peek_with_spaces()),
|
!PREFIX_TOKENS.contains(p.peek()),
|
||||||
"{:?} is not a prefix token",
|
"{:?} found in PREFIX_TOKENS",
|
||||||
p.peek_with_spaces()
|
p.peek()
|
||||||
);
|
);
|
||||||
|
|
||||||
let span = p.span();
|
let span = p.span();
|
||||||
|
|
@ -634,9 +615,9 @@ fn prefix(p: &mut Parser) -> Closed {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infix(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
fn infix(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
||||||
let (kind, spaces) = op;
|
match op.0 {
|
||||||
match kind {
|
|
||||||
TokenKind::Plus
|
TokenKind::Plus
|
||||||
|
| TokenKind::Minus
|
||||||
| TokenKind::Star
|
| TokenKind::Star
|
||||||
| TokenKind::Slash
|
| TokenKind::Slash
|
||||||
| TokenKind::EqualEqual
|
| TokenKind::EqualEqual
|
||||||
|
|
@ -645,14 +626,11 @@ fn infix(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
||||||
| TokenKind::LessEqual
|
| TokenKind::LessEqual
|
||||||
| TokenKind::Greater
|
| TokenKind::Greater
|
||||||
| TokenKind::GreaterEqual
|
| TokenKind::GreaterEqual
|
||||||
| TokenKind::Colon
|
| TokenKind::Colon => infix_binary(p, op),
|
||||||
| TokenKind::Dot
|
|
||||||
| TokenKind::Pipe => infix_binary(p, op),
|
|
||||||
TokenKind::Minus if spaces.are_balanced() => infix_binary(p, op),
|
|
||||||
|
|
||||||
TokenKind::Equal => infix_let(p, op),
|
TokenKind::Equal => infix_let(p, op),
|
||||||
|
|
||||||
_ if is_prefix_token(op) => infix_call(p, op),
|
_ if PREFIX_TOKENS.contains(op.0) => infix_call(p, op),
|
||||||
|
|
||||||
_ => panic!("unhandled infix operator {op:?}"),
|
_ => panic!("unhandled infix operator {op:?}"),
|
||||||
}
|
}
|
||||||
|
|
@ -672,7 +650,7 @@ fn infix_binary(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infix_call(p: &mut Parser, mut arg: (TokenKind, Spaces)) -> NodeKind {
|
fn infix_call(p: &mut Parser, mut arg: (TokenKind, Spaces)) -> NodeKind {
|
||||||
while is_prefix_token(p.peek_with_spaces()) {
|
while PREFIX_TOKENS.contains(p.peek()) {
|
||||||
precedence_parse(p, arg);
|
precedence_parse(p, arg);
|
||||||
arg = p.peek_with_spaces();
|
arg = p.peek_with_spaces();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,6 @@ pub enum TokenKind {
|
||||||
Colon,
|
Colon,
|
||||||
Backslash,
|
Backslash,
|
||||||
RArrow,
|
RArrow,
|
||||||
Dot,
|
|
||||||
Pipe,
|
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
@ -137,10 +135,6 @@ impl Spaces {
|
||||||
pub fn pair(self) -> (bool, bool) {
|
pub fn pair(self) -> (bool, bool) {
|
||||||
(self.left(), self.right())
|
(self.left(), self.right())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_balanced(self) -> bool {
|
|
||||||
matches!(self.pair(), (true, true) | (false, false))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Spaces {
|
impl fmt::Debug for Spaces {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.arg("--prominent-compile-errors")
|
.arg("--prominent-compile-errors")
|
||||||
.arg("--color")
|
.arg("--color")
|
||||||
.arg(color)
|
.arg(color)
|
||||||
.arg("-freference-trace")
|
|
||||||
// Build output
|
// Build output
|
||||||
.arg("--cache-dir")
|
.arg("--cache-dir")
|
||||||
.arg(out_path.join("zig-cache"))
|
.arg(out_path.join("zig-cache"))
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ pub const std_options: std.Options = .{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn enableLogScope(scope: @TypeOf(.enum_literal)) bool {
|
pub fn enableLogScope(scope: @TypeOf(.enum_literal)) bool {
|
||||||
// Replace any of the false returns below to enable log scopes for the build.
|
|
||||||
if (scope == .vm)
|
if (scope == .vm)
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ pub const Value = union(enum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gt(a: Value, b: Value) ?bool {
|
pub fn gt(a: Value, b: Value) ?bool {
|
||||||
return b.lt(a);
|
return !a.eql(b) and !(a.lt(b) orelse return null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typeName(value: Value) []const u8 {
|
pub fn typeName(value: Value) []const u8 {
|
||||||
|
|
@ -69,24 +69,24 @@ pub const Value = union(enum) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(value: Value, writer: *std.Io.Writer) !void {
|
pub fn format(value: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
.nil => try writer.writeAll("Nil"),
|
.nil => try std.fmt.formatBuf("Nil", options, writer),
|
||||||
.false => try writer.writeAll("False"),
|
.false => try std.fmt.formatBuf("False", options, writer),
|
||||||
.true => try writer.writeAll("True"),
|
.true => try std.fmt.formatBuf("True", options, writer),
|
||||||
inline .tag, .number => |x| try writer.print("{d}", .{x}),
|
inline .tag, .number => |x| try std.fmt.format(writer, "{d}", .{x}),
|
||||||
inline .vec4, .rgba => |x| try writer.print("{s}{d}", .{ @tagName(value), x }),
|
inline .vec4, .rgba => |x| try std.fmt.format(writer, "{s}{d}", .{ @tagName(value), x }),
|
||||||
.ref => |ref| switch (ref.*) {
|
.ref => |ref| switch (ref.*) {
|
||||||
.closure => |c| try writer.print("function {s}", .{c.name}),
|
.closure => |c| try std.fmt.format(writer, "function {s}", .{c.name}),
|
||||||
.list => |l| {
|
.list => |l| {
|
||||||
try writer.writeAll("[");
|
try std.fmt.formatBuf("[", options, writer);
|
||||||
for (l, 0..) |elem, i| {
|
for (l, 0..) |elem, i| {
|
||||||
if (i != 0) try writer.writeAll(", ");
|
if (i != 0) try std.fmt.formatBuf(", ", options, writer);
|
||||||
try elem.format(writer);
|
try elem.format(fmt, options, writer);
|
||||||
}
|
}
|
||||||
try writer.writeAll("]");
|
try std.fmt.formatBuf("]", options, writer);
|
||||||
},
|
},
|
||||||
inline .shape, .scribble, .reticle => |x| try writer.print("{}", .{x}),
|
inline .shape, .scribble, .reticle => |x| try std.fmt.format(writer, "{}", .{x}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,24 +147,6 @@ pub fn stackFrame(vm: *const Vm, index: usize) StackFrame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const StackFmt = struct {
|
|
||||||
stack: []const Value,
|
|
||||||
|
|
||||||
pub fn format(f: *const StackFmt, writer: *std.Io.Writer) !void {
|
|
||||||
try writer.writeAll("[");
|
|
||||||
for (f.stack, 0..) |val, i| {
|
|
||||||
if (i != 0)
|
|
||||||
try writer.writeAll(", ");
|
|
||||||
try val.format(writer);
|
|
||||||
}
|
|
||||||
try writer.writeAll("]");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn stackFmt(stack: []const Value) StackFmt {
|
|
||||||
return .{ .stack = stack };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Debug assertion for bytecode validity.
|
/// Debug assertion for bytecode validity.
|
||||||
/// In future versions, this may become disabled in release builds.
|
/// In future versions, this may become disabled in release builds.
|
||||||
fn validateBytecode(vm: *Vm, ok: bool, comptime fmt: []const u8, args: anytype) Error!void {
|
fn validateBytecode(vm: *Vm, ok: bool, comptime fmt: []const u8, args: anytype) Error!void {
|
||||||
|
|
@ -185,7 +167,7 @@ pub fn push(vm: *Vm, val: Value) Error!void {
|
||||||
if (vm.stack_top >= vm.stack.len) {
|
if (vm.stack_top >= vm.stack.len) {
|
||||||
return vm.throw("too many live temporary values (local variables and expression operands)", .{});
|
return vm.throw("too many live temporary values (local variables and expression operands)", .{});
|
||||||
}
|
}
|
||||||
log.debug("PUSH {f} <- {f}", .{ stackFmt(vm.stack[0..vm.stack_top]), val });
|
log.debug("PUSH {any} <- {}", .{ vm.stack[0..vm.stack_top], val });
|
||||||
vm.stack[vm.stack_top] = val;
|
vm.stack[vm.stack_top] = val;
|
||||||
vm.stack_top += 1;
|
vm.stack_top += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +176,7 @@ pub fn pop(vm: *Vm) Error!Value {
|
||||||
try vm.validateBytecode(vm.stack_top > 0, "value stack underflow", .{});
|
try vm.validateBytecode(vm.stack_top > 0, "value stack underflow", .{});
|
||||||
vm.stack_top -= 1;
|
vm.stack_top -= 1;
|
||||||
const result = vm.stack[vm.stack_top];
|
const result = vm.stack[vm.stack_top];
|
||||||
log.debug("POP {f} -> {f}", .{ stackFmt(vm.stack[0..vm.stack_top]), result });
|
log.debug("POP {any} -> {}", .{ vm.stack[0..vm.stack_top], result });
|
||||||
return vm.stack[vm.stack_top];
|
return vm.stack[vm.stack_top];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
520
docs/haku.dj
520
docs/haku.dj
|
|
@ -1,520 +0,0 @@
|
||||||
# haku reference manual
|
|
||||||
|
|
||||||
haku is a dynamically typed, pure functional programming language, used for programming brushes in rakugaki.
|
|
||||||
|
|
||||||
For the layperson, it can be thought of a beefed up calculator.
|
|
||||||
It has roots in ordinary school algebra, but has a much more powerful set of features, with the special ability of being able to edit images.
|
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
haku programs are called _brushes_.
|
|
||||||
The purpose of a brush is to produce instructions for rakugaki on how the user may interact with the wall (via _reticles_), and what things should be drawn on the wall as a result of those interactions (via _scribbles_).
|
|
||||||
Collectively, reticles and scribbles are what we call _effects_.
|
|
||||||
|
|
||||||
A brush's task is to compute (or in simpler terms, calculate) an effect that rakugaki will then _perform_.
|
|
||||||
|
|
||||||
In case of reticles, rakugaki will allow the user to interact with the wall, and then ask haku for another effect to perform afterwards (a _continuation_).
|
|
||||||
|
|
||||||
In case of scribbles, rakugaki will draw the scribble onto the wall, without asking haku for more.
|
|
||||||
|
|
||||||
Once rakugaki runs through all effects, we say that the brush has _finished executing_.
|
|
||||||
|
|
||||||
|
|
||||||
## Lexical elements
|
|
||||||
|
|
||||||
|
|
||||||
### Comments
|
|
||||||
|
|
||||||
haku's most basic lexical element is the _comment_.
|
|
||||||
|
|
||||||
```
|
|
||||||
-- This is a comment.
|
|
||||||
-- こんにちは!
|
|
||||||
```
|
|
||||||
|
|
||||||
Comments introduce human-readable remarks into a brush's code.
|
|
||||||
Their only purpose is documentation.
|
|
||||||
They serve no semantic meaning, and thus do not affect the result of the brush whatsoever.
|
|
||||||
|
|
||||||
Comments begin with `--`, and span until the end of the current line of text.
|
|
||||||
Once the line ends, the comment does, too.
|
|
||||||
|
|
||||||
Comments do not necessarily have to appear at the beginning of a line.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
magnitude: \v -> -- calculate the magnitude of a vector
|
|
||||||
hypot vecX.v vecY.v
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Literals
|
|
||||||
|
|
||||||
Literals represent _literal values_ that are input into the brush.
|
|
||||||
|
|
||||||
haku has a few types of literals, but not all literals are purely lexical elements (some of them can nest)---which is why the different types of literals are covered under the [Expressions](#Expressions) section.
|
|
||||||
|
|
||||||
|
|
||||||
### Identifiers
|
|
||||||
|
|
||||||
Identifiers are used for naming values inside a brush.
|
|
||||||
New names are introduced using [defs](#Structure-of-a-brush) and [lets](#Let-expression).
|
|
||||||
Once a name is introduced, it may be referenced using its identifier in its corresponding scope---the whole brush for defs, and the following expression in lets.
|
|
||||||
|
|
||||||
An identifier starts with a *lowercase* ASCII letter---`a`--`z` or an underscore---`_`, and is followed by zero or more ASCII letters of any case---`a`--`z`, `A`--`Z`, digits---`0`--`9`, and underscores---`_`.
|
|
||||||
This then may be followed by an arbitrary number of _suffix characters_ prime symbols---`'` and question marks---`?`.
|
|
||||||
|
|
||||||
By convention, prime symbols are used in the same way they are used in math notation---for introducing a distinct variable of the same name as another variable, usually derived from the previous.
|
|
||||||
For example, given a variable named `position`, an _updated_ position may be named `position'`.
|
|
||||||
|
|
||||||
The question mark suffix is conventionally used for [boolean](#Boolean-type) variables, as well as boolean-returning functions.
|
|
||||||
By convention, only one question mark is always used.
|
|
||||||
|
|
||||||
Identifiers starting with *uppercase* ASCII letters---`A`--`Z` are not identifiers, but rather [tags](#tag-literal), and therefore cannot be used as def and let names.
|
|
||||||
|
|
||||||
The following identifiers are reserved as _keywords_, and have special meaning assigned within the language syntax.
|
|
||||||
|
|
||||||
- `if` --- Introduces an [`if` expression](#if-expression).
|
|
||||||
- `else` --- Introduces the `else` clause in an [`if` expression](#if-expression).
|
|
||||||
|
|
||||||
Additionally, the following identifiers are reserved for future use, and may not be used for naming defs and lets.
|
|
||||||
|
|
||||||
- `and`
|
|
||||||
- `or`
|
|
||||||
|
|
||||||
By convention, a prime symbol `'` suffix can be used to work around this restriction.
|
|
||||||
For example, instead of naming a variable `if`, try naming it `if'` (read as _if prime_, "_the other if_").
|
|
||||||
|
|
||||||
|
|
||||||
### Operators and punctuation
|
|
||||||
|
|
||||||
Operators and punctuation share a section in this part of the reference due to their lexical similarity.
|
|
||||||
|
|
||||||
[Operators](#operators) serve as a terse syntax for calling a small set of built-in functions within the program (described in detail in the [system reference documentation](system.html)), while punctuation serves the purpose of syntactic delimiters.
|
|
||||||
|
|
||||||
The following symbols are operators:
|
|
||||||
|
|
||||||
```
|
|
||||||
+ - * /
|
|
||||||
== != < <= > >= !
|
|
||||||
```
|
|
||||||
|
|
||||||
And the following symbols are punctuation:
|
|
||||||
|
|
||||||
```
|
|
||||||
<newline>
|
|
||||||
( ) [ ] ,
|
|
||||||
= :
|
|
||||||
. |
|
|
||||||
\ ->
|
|
||||||
```
|
|
||||||
|
|
||||||
`<newline>` is literally written down as a line break in programs, which would be invisible in this reference.
|
|
||||||
|
|
||||||
|
|
||||||
## Structure of a brush
|
|
||||||
|
|
||||||
A brush is structured like so:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
def1: expr1
|
|
||||||
def2: expr2
|
|
||||||
def3: expr3
|
|
||||||
-- ...
|
|
||||||
effectExpr
|
|
||||||
```
|
|
||||||
|
|
||||||
That is, there are two parts to a brush: its _defs_, followed by the resulting effect [expression](#Expressions).
|
|
||||||
The effect produced by this expression is the effect performed by rakugaki when the user interacts with the wall (clicks on it, touches it, starts a pen stroke).
|
|
||||||
|
|
||||||
Defs introduce names for values that are available across the entire program.
|
|
||||||
They are most commonly used to name constants and functions.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
-- Constant definition
|
|
||||||
pi: 3.14159265
|
|
||||||
|
|
||||||
-- Function definition
|
|
||||||
magnitude: \v ->
|
|
||||||
hypot vecX.v vecY.v
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Expressions
|
|
||||||
|
|
||||||
haku is a strictly expression-oriented language.
|
|
||||||
There is no idea of statements, as would be the case in lower-level languages like C++ or JavaScript.
|
|
||||||
This comes from the fact that haku is a _pure_ functional language, which means there aren't any expressions whose result you would want to discard, only for their side effects.
|
|
||||||
|
|
||||||
|
|
||||||
### Nil
|
|
||||||
|
|
||||||
An empty parenthesis represents a _nil_ value---that is, literally a value that means "no value."
|
|
||||||
|
|
||||||
```haku
|
|
||||||
()
|
|
||||||
```
|
|
||||||
|
|
||||||
It is one of the only values considered [_false_](#Truth) by the language, other than the [`False` boolean](#Tags).
|
|
||||||
|
|
||||||
|
|
||||||
### Numbers
|
|
||||||
|
|
||||||
Numbers in haku are written down as a sequence of ASCII digits---`0`--`9`, followed by an optional decimal point `.` with a decimal part.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
0
|
|
||||||
123
|
|
||||||
3.14159265
|
|
||||||
```
|
|
||||||
|
|
||||||
Internally, they are represented by 32-bit floating point numbers.
|
|
||||||
This means that they have [fairly limited precision](https://float.exposed/0x42f60000), and do not always behave exactly like math on real numbers.
|
|
||||||
For example, there are magical values for ∞ and -∞ (which _can_ exist and can be operated upon), as well as a value called NaN (not a number), which are produced as results of certain operations that aren't well-defined in math (most commonly division by zero).
|
|
||||||
|
|
||||||
|
|
||||||
### Tags
|
|
||||||
|
|
||||||
Tags are similar to [identifiers](#Identifiers), but start with an *uppercase* rather than a lowercase ASCII letter (that is, `A`--`Z`).
|
|
||||||
They are values which represent _names_.
|
|
||||||
|
|
||||||
This concept may feel a bit alien.
|
|
||||||
As an example, consider how haku implements record types.
|
|
||||||
From the perspective of the user, a record type acts like a [function](#Functions) which accepts a tag as an argument---with the tag being the name of the record field.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
withDotter \d ->
|
|
||||||
stroke 8 #000 (line d.From d.To)
|
|
||||||
---- -- these are tags
|
|
||||||
```
|
|
||||||
|
|
||||||
There are also two special tags, `True` and `False`, which are used to represent Boolean logic.
|
|
||||||
|
|
||||||
The boolean `False` is the only [_false_](#Truth) value in the language, other than [nil](#Nil).
|
|
||||||
|
|
||||||
|
|
||||||
### Colors
|
|
||||||
|
|
||||||
haku has a literal for representing RGBA colors.
|
|
||||||
It takes one of the following four forms, from most explicit to least explicit:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
#RRGGBBAA
|
|
||||||
#RRGGBB
|
|
||||||
#RGBA
|
|
||||||
#RGB
|
|
||||||
```
|
|
||||||
|
|
||||||
Each character in a color literal is a hexadecimal digit, with the digits `0`--`9` representing digits 0 to 9, and letters `a`--`f` representing digits 10 to 16 (case insensitive.)
|
|
||||||
|
|
||||||
For `#RGB` and `#RGBA`, the digits are repeated twice implicitly---that is, `#1234` is the same as `#11223344`.
|
|
||||||
|
|
||||||
This syntax is designed to be convenient for working with colors coming from external programs.
|
|
||||||
For example, you may pick a color from an online palette, and paste its hex code straight into your brush code.
|
|
||||||
|
|
||||||
Example (rakugaki logo colors):
|
|
||||||
|
|
||||||
```haku
|
|
||||||
white: #FFF
|
|
||||||
peach: #FFA6A6
|
|
||||||
raspberry: #F44096
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Lists
|
|
||||||
|
|
||||||
Lists are fixed-length sequences of values.
|
|
||||||
They are written down by listing out a sequence of comma `,` or newline-separated items, enclosed in square brackets `[]`.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
six: [1, 2, 3, 4, 5, 6]
|
|
||||||
four: [
|
|
||||||
1
|
|
||||||
2
|
|
||||||
3
|
|
||||||
4
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Lists are allowed to nest, as the values may be of any type---including lists themselves.
|
|
||||||
|
|
||||||
Lists are most commonly used to compose scribbles.
|
|
||||||
A list is also a scribble, which draws the scribbles it contains within itself, from first to last.
|
|
||||||
|
|
||||||
|
|
||||||
### Operators
|
|
||||||
|
|
||||||
Operators in haku are used mostly for basic mathematical operations.
|
|
||||||
They are either unary, written in prefix form `!x`, or binary, written in infix form `a + b`.
|
|
||||||
|
|
||||||
While order of operations in case of unary operators is unambiguous (innermost to outermost), infix operators are not as simple.
|
|
||||||
Certain infix operators have _precedence_ over others.
|
|
||||||
|
|
||||||
This precedence depends on the operator used, as well as the spacing around it.
|
|
||||||
Spacing _only_ matters for infix operators; prefix operators may have any amount of spaces around them, though conventionally they are glued to the expression on the right, like `(-1)`, or `vec -1 0`.
|
|
||||||
|
|
||||||
Infix operators with spaces around them are classified as _loose_, and those without spaces around them are _tight_.
|
|
||||||
An unequal amount of spaces around an infix operator is considered an error (or is parsed as a prefix operator, depending on context).
|
|
||||||
|
|
||||||
Based on these two groups, the precedence of operators is defined as follows:
|
|
||||||
|
|
||||||
1. Prefix operators: `-a`, `!a`
|
|
||||||
1. Tight
|
|
||||||
|
|
||||||
1. Dot: `a.b`
|
|
||||||
1. Arithmetic: `a+b`, `a-b`, `a*b`, `a/b`
|
|
||||||
1. Comparisons: `a==b`, `a!=b`, `a<b`, `a<=b`, `a>b`, `a>=b`
|
|
||||||
|
|
||||||
1. Function calls: `a b`
|
|
||||||
1. Loose
|
|
||||||
|
|
||||||
1. Dot: `a . b`
|
|
||||||
1. Arithmetic: `a + b`, `a - b`, `a * b`, `a / b`, `a |b`
|
|
||||||
1. Comparisons: `a == b`, `a != b`
|
|
||||||
1. Variables: `a: b`, `a = b`
|
|
||||||
|
|
||||||
The operators `+`, `-`, `*`, `/`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `!`, are used for calling functions built into the haku [system library](system.html).
|
|
||||||
|
|
||||||
Other infix tokens listed above have other semantic meaning.
|
|
||||||
|
|
||||||
- `a b`, `.`, and `|` --- Used for calling [functions](#Functions).
|
|
||||||
- `:` --- Used for introducing [defs](#Structure-of-a-brush).
|
|
||||||
- `=` --- Used for introducing [lets](#Let-expression).
|
|
||||||
|
|
||||||
Examples of how these precedence rules work in practice:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
2 + 2 * 2 == 8 -- left to right
|
|
||||||
2 + 2*2 == 6 -- 2*2 goes first
|
|
||||||
2+2 * 2 == 8 -- 2+2 goes first
|
|
||||||
2+2*2 == 8 -- left to right
|
|
||||||
|
|
||||||
sin 2 * pi * x == (sin 2) * pi * x -- function call goes first
|
|
||||||
sin 2*pi*x == sin (2 * pi * x) -- argument goes first
|
|
||||||
|
|
||||||
-- unintuitive for users of C-likes:
|
|
||||||
-- prefix `-` has higher precedence than `.`
|
|
||||||
-vecX.v == (-vecX).v
|
|
||||||
```
|
|
||||||
|
|
||||||
One thing that should be noted about haku's operator precedence is that, unlike math notation and most other programming languages, `+`, `-`, `*`, and `/`, are evaluated from left to right.
|
|
||||||
This is because otherwise they would interact unintuitively with the pipe `|` operator, which is effectively used as an operator that turns any function infix.
|
|
||||||
|
|
||||||
It is not obvious where `|` would sit in the precedence hierarchy if arithmetic was split into separate precedence levels for `+` and `-`, and `*` and `/`, whereas with haku's solution, all arithmetic expressions are simply read from left to right.
|
|
||||||
|
|
||||||
|
|
||||||
### Parentheses
|
|
||||||
|
|
||||||
In case the tight-loose system is not expressive enough, parentheses can be used as an escape hatch for grouping expressions.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
2 + (2 * 2) == 2 + 2*2
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Functions
|
|
||||||
|
|
||||||
Functions in haku follow the definition of mathematical functions: given a set of _arguments_, the arguments are substituted into the function's _parameter variables_, and a result is computed from the resulting expression.
|
|
||||||
|
|
||||||
A function literal is written down like `\params -> result`, where `params` is a comma-separated list of parameters, and `result` is the function's resulting expression.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
square: \x -> x * x
|
|
||||||
```
|
|
||||||
|
|
||||||
A newline is permitted after the arrow `->`, which should be preferred for most functions.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
magnitude: \v ->
|
|
||||||
hypot vecX.v vecY.v
|
|
||||||
|
|
||||||
normalize: \v ->
|
|
||||||
l = magnitude v
|
|
||||||
v / vec l l
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that there must be at least one parameter.
|
|
||||||
In case you need a function with no parameters, you almost always want a constant value instead.
|
|
||||||
|
|
||||||
Functions can be used by _calling_ them with space-separated arguments.
|
|
||||||
Note that space-separated function calls have higher precedence than most arithmetic operators, which means you have to use parentheses or tighten the expression to pass more complicated expressions.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
normalize (vec 4 4)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that a call must pass in _exactly_ the amount of arguments defined by the function.
|
|
||||||
Calling the above-defined `normalize` with more than one argument will not work:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
normalize (vec 4 4) (vec 5 5)
|
|
||||||
```
|
|
||||||
|
|
||||||
In places where a call with space-separated arguments is inconvenient, there are two alternative syntaxes for calling a function.
|
|
||||||
|
|
||||||
The first is by using the `.` operator, which, when used tightly, can be used to do a function call with higher operator precedence than an ordinary space-separated call would have.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
f x == f.x
|
|
||||||
```
|
|
||||||
|
|
||||||
Combined with [records](#Records) and [tags](#Tags), it mimics the record field access syntax found in C-like programming languages.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
withDotter \d ->
|
|
||||||
stroke 8 #000 (line d.From d.To)
|
|
||||||
------ ----
|
|
||||||
```
|
|
||||||
|
|
||||||
The other alternative syntax is the _pipe_ `|` operator.
|
|
||||||
It calls the function on the right with the argument on the left.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
2 |sqrt
|
|
||||||
```
|
|
||||||
|
|
||||||
When a space-separated function call is found on the right, the `|` operator instead inserts the value from the left as the first argument of the function call.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
x |mod 2 == mod x 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The spacing convention around `|` is a bit unusual, but the above example demonstrates why: the `|` operator effectively turns an arbitrary function into an infix operator on par with built-in arithmetic operators.
|
|
||||||
Therefore, the function name is glued to the pipe, like `|mod`, to appear as one word visually.
|
|
||||||
|
|
||||||
Certain functions are _built-in_, and implement core functionality that cannot be implemented in haku alone (at all, or in a performant manner).
|
|
||||||
The [haku system library](system.html) is what defines all the built-in functions.
|
|
||||||
|
|
||||||
Due to temporary limitations of the implementation, built-in functions cannot be referenced like regular functions.
|
|
||||||
They always have to appear in a call.
|
|
||||||
|
|
||||||
If you'd like to reference a built-in function to e.g. pass it to a list-transforming function, you will have to wrap it in a function of your own:
|
|
||||||
|
|
||||||
```haku
|
|
||||||
add: \x, y -> x + y
|
|
||||||
sum: [1, 2, 3] |reduce 0 sum
|
|
||||||
|
|
||||||
sin': \x -> sin x
|
|
||||||
sines: [0, pi*1/2, pi*2/2, pi*3/2] |map sin'
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### `if` expressions
|
|
||||||
|
|
||||||
`if` expressions allow for choosing between two different results based on a condition.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
if (cond)
|
|
||||||
a
|
|
||||||
else
|
|
||||||
b
|
|
||||||
```
|
|
||||||
|
|
||||||
When `cond` is [_true_](#Truth), `a` will be computed and returned as the result.
|
|
||||||
Otherwise, `b` will be computed and returned as the result.
|
|
||||||
|
|
||||||
Note that in both cases, only one of the two expressions is computed.
|
|
||||||
This allows for implementing bounded recursion to achieve repetition.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
-- Fibonacci sequence
|
|
||||||
fib: \x ->
|
|
||||||
if (x > 1)
|
|
||||||
fib n-1 + fib n-2
|
|
||||||
else
|
|
||||||
x
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Let expressions
|
|
||||||
|
|
||||||
Let expressions introduce a new _variable_, or _let_, that can be referenced in the expression on the next line.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
x = y
|
|
||||||
expr
|
|
||||||
```
|
|
||||||
|
|
||||||
The difference between lets and [defs](#Structure-of-a-brush) is that the value of a let can change, because it can depend on non-def values, such as function arguments (therefore making it _variable_.)
|
|
||||||
|
|
||||||
This however means that lets have reduced _scope_.
|
|
||||||
The name introduced by a def can be used in the entire program---even before the line it's introduced on---while the name introduced by a let can only be used in the expression that immediately follows the let.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
x: 1
|
|
||||||
|
|
||||||
f: \y ->
|
|
||||||
z = x + 1
|
|
||||||
x + y + z -- ok
|
|
||||||
|
|
||||||
z -- not ok
|
|
||||||
```
|
|
||||||
|
|
||||||
This also means that lets cannot be used to create recursive functions, because the name introduced by a let only becomes visible in its following expression.
|
|
||||||
|
|
||||||
```haku
|
|
||||||
-- This is okay:
|
|
||||||
defib: \x ->
|
|
||||||
if (x > 1)
|
|
||||||
defib n-1 + defib n-2
|
|
||||||
else
|
|
||||||
x
|
|
||||||
|
|
||||||
-- This will not work:
|
|
||||||
letfib = \x ->
|
|
||||||
if (x > 1)
|
|
||||||
-- Because letfib is not yet defined at this point.
|
|
||||||
letfib n-1 + letfib n-2
|
|
||||||
else
|
|
||||||
x
|
|
||||||
|
|
||||||
defib 5 + letfib 5 -- It is only defined in this expression.
|
|
||||||
```
|
|
||||||
|
|
||||||
Since a let can be followed up by any other expression, multiple lets can be chained together.
|
|
||||||
|
|
||||||
```
|
|
||||||
x = 4
|
|
||||||
y = x + 3
|
|
||||||
x + y
|
|
||||||
```
|
|
||||||
|
|
||||||
Note however that `x + y` finishes the chain of lets, which means we cannot introduce additional ones after that line.
|
|
||||||
That would begin another expression!
|
|
||||||
|
|
||||||
|
|
||||||
## Types
|
|
||||||
|
|
||||||
haku distinguishes values between a few different types.
|
|
||||||
|
|
||||||
- [*nil*](#Nil)
|
|
||||||
- [*tag*](#Tags)
|
|
||||||
|
|
||||||
- [*boolean*]{id="Boolean-type"} - either [`False` or `True`](#Tags). Indicates truth or falsehood, used in [`if` conditions](#if-expression).
|
|
||||||
|
|
||||||
- [*number*](#Numbers)
|
|
||||||
- *vec* - a 4-dimensional vector, composed of four `number`s.
|
|
||||||
- [*rgba*](#Colors) - an RGBA color, composed of four `number`s. This is the type of color literals.
|
|
||||||
- [*function*](#Functions)
|
|
||||||
- [*list*](#Lists)
|
|
||||||
- *shape* - a mathematical shape.
|
|
||||||
- _*effect*_ - an action that can be performed by rakugaki.
|
|
||||||
|
|
||||||
- *scribble* - something that can be drawn on the wall.
|
|
||||||
- *reticle* - an interaction the user can make with the wall.
|
|
||||||
|
|
||||||
These types are incompatible with each other.
|
|
||||||
If you pass in a *tag* to a value that expects a *number*, you will get an error.
|
|
||||||
|
|
||||||
You can refer to the [system library documentation](system.html) for more information on the types accepted by functions.
|
|
||||||
Note that it has a more precise notation for describing types, which explicitly documents the types of values that will be found in more nuanced situations, such as the `map` function.
|
|
||||||
|
|
||||||
|
|
||||||
### Truth
|
|
||||||
|
|
||||||
Conditions in `if` expressions, as well as the `!` operator, consider certain types of values _truthy_, and others _falsy_.
|
|
||||||
|
|
||||||
Falsy values include *nil* and the boolean `False`.
|
|
||||||
All other values are considered truthy.
|
|
||||||
142
docs/rkgk.dj
142
docs/rkgk.dj
|
|
@ -1,15 +1,5 @@
|
||||||
# Introduction to rakugaki
|
# Introduction to rakugaki
|
||||||
|
|
||||||
*Warning: Neither rakugaki, nor this introductory manual is finished.*
|
|
||||||
While it will always use the most up-to-date and recommended syntax, there are things it does cover, and it will probably be rebuilt to improve coverage of features once the app stabilises a bit.
|
|
||||||
|
|
||||||
For now, I recommend cross-referencing it with the following documents:
|
|
||||||
|
|
||||||
- [haku language reference](haku.html)
|
|
||||||
- [System library](system.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Welcome to rakugaki!
|
Welcome to rakugaki!
|
||||||
|
|
||||||
I hope you've been having fun fiddling with the app so far.
|
I hope you've been having fun fiddling with the app so far.
|
||||||
|
|
@ -38,7 +28,7 @@ In case you edited anything in the input box on the right, paste the following t
|
||||||
-- and see what happens!
|
-- and see what happens!
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
stroke 8 #000 d.To
|
stroke 8 #000 (d To)
|
||||||
```
|
```
|
||||||
|
|
||||||
rakugaki is a drawing program for digital scribbles and other pieces of art.
|
rakugaki is a drawing program for digital scribbles and other pieces of art.
|
||||||
|
|
@ -96,8 +86,8 @@ If you want to draw multiple scribbles, you can wrap them into a list, which we
|
||||||
-- Draw two colorful dots instead of one!
|
-- Draw two colorful dots instead of one!
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke 8 #F00 (d.To + vec 4 0)
|
stroke 8 #F00 (d To + vec 4 0)
|
||||||
stroke 8 #00F (d.To + vec -4 0)
|
stroke 8 #00F (d To + vec (-4) 0)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -118,16 +108,26 @@ It'll draw the first inner list, which contains two scribbles, and then it'll dr
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
stroke 8 #F00 (d.To + vec 4 0)
|
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 #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.
|
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!
|
It essentially means you can snap pieces together like Lego bricks!
|
||||||
|
|
||||||
|
|
@ -146,7 +146,7 @@ Recall that super simple brush from before...
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
stroke 8 #000 d.To
|
stroke 8 #000 (d To)
|
||||||
```
|
```
|
||||||
|
|
||||||
This reads as "given a dotter, output a stroke that's 8 pixels wide, has the color `#000`, and is drawn at the dotter's `To` coordinates."
|
This reads as "given a dotter, output a stroke that's 8 pixels wide, has the color `#000`, and is drawn at the dotter's `To` coordinates."
|
||||||
|
|
@ -179,7 +179,6 @@ If you reorder or remove any one of them, your brush isn't going to work!
|
||||||
You can also specify an alpha channel, for transparent colors---`#RRGGBBAA`, or `#RGBA`.
|
You can also specify an alpha channel, for transparent colors---`#RRGGBBAA`, or `#RGBA`.
|
||||||
|
|
||||||
- The third ingredient is the stroke's _position_.
|
- The third ingredient is the stroke's _position_.
|
||||||
`d.To` is the position of your mouse cursor.
|
|
||||||
|
|
||||||
Positions in haku are represented using mathematical _vectors_, which, when broken down into pieces, are just lists of some numbers.
|
Positions in haku are represented using mathematical _vectors_, which, when broken down into pieces, are just lists of some numbers.
|
||||||
|
|
||||||
|
|
@ -198,22 +197,22 @@ Likewise, negative X coordinates go leftwards, and negative Y coordinates go upw
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Vectors in haku are obtained with another function---`vec`---though we don't use it in the basic example, because `d.To` already is a vector.
|
Vectors in haku are obtained with another function---`vec`---though we don't use it in the basic example, because `d To` already is a vector.
|
||||||
Vectors support all the usual math operators though, so if we wanted to, we could, for example, add a vector to `d.To`, thus moving the position of the dot relative to the mouse cursor:
|
Vectors support all the usual math operators though, so if we wanted to, we could, for example, add a vector to `d To`, thus moving the position of the dot relative to the mouse cursor:
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
stroke 8 #000 (d.To + vec 10 0) -- moved 10 pixels rightwards
|
stroke 8 #000 (d To + vec 10 0) -- moved 10 pixels rightwards
|
||||||
```
|
```
|
||||||
|
|
||||||
Also note how the `d.To + vec 10 0` expression is parenthesized.
|
Also note how the `d To` expression is parenthesized.
|
||||||
This is because otherwise, its individual parts would be interpreted as separate arguments to `stroke`, which is not what we want!
|
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.
|
Anyways, with all that, we let haku mix all the ingredients together, and get a black dot under the cursor.
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
stroke 8 #000 d.To
|
stroke 8 #000 (d To)
|
||||||
```
|
```
|
||||||
|
|
||||||
Nice!
|
Nice!
|
||||||
|
|
@ -232,10 +231,10 @@ Let's fix that by drawing a `line` instead!
|
||||||
|
|
||||||
```haku
|
```haku
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
stroke 8 #000 (line d.From d.To)
|
stroke 8 #000 (line (d From) (d To))
|
||||||
```
|
```
|
||||||
|
|
||||||
We replace the singular position `d.To` with a `line`. `line` expects two arguments, which are vectors defining the line's start and end points.
|
We replace the singular position `d To` with a `line`. `line` expects two arguments, which are vectors defining the line's start and end points.
|
||||||
For the starting position we use a _different_ property of `d`, which is `From`---this is the _previous_ value of `To`, which allows us to draw a continuous line.
|
For the starting position we use a _different_ property of `d`, which is `From`---this is the _previous_ value of `To`, which allows us to draw a continuous line.
|
||||||
|
|
||||||
::: aside
|
::: aside
|
||||||
|
|
@ -250,8 +249,8 @@ haku also supports other kinds of shapes: circles and rectangles.
|
||||||
```haku
|
```haku
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke 8 #F00 (circle (d.To + vec -16 0) 16)
|
stroke 8 #F00 (circle (d To + vec (-16) 0) 16)
|
||||||
stroke 8 #00F (rect (d.To + vec 0 -16) (vec 32 32))
|
stroke 8 #00F (rect (d To + vec 0 (-16)) 32 32)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -260,64 +259,6 @@ withDotter \d ->
|
||||||
- `rect`s are made up of the (X and Y) position of their top-left corner, and a size (width and height).\
|
- `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!
|
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. Function applications: `.`
|
|
||||||
1. Arithmetic: `+`, `-`, `*`, `/`
|
|
||||||
1. Comparisons: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
||||||
|
|
||||||
1. Function calls
|
|
||||||
1. Loose
|
|
||||||
|
|
||||||
1. Function applications: `.`
|
|
||||||
1. Arithmetic and pipelines `|`
|
|
||||||
1. Comparisons
|
|
||||||
1. Variables: `:`, `=`
|
|
||||||
|
|
||||||
Naturally, you can still use parentheses when the loose-tight distinction is not enough.
|
|
||||||
|
|
||||||
|
|
||||||
## Programming in haku
|
## Programming in haku
|
||||||
|
|
||||||
So far we've been using haku solely to describe data.
|
So far we've been using haku solely to describe data.
|
||||||
|
|
@ -329,7 +270,7 @@ Remember that example from before?
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke 8 #F00 (d To + vec 4 0)
|
stroke 8 #F00 (d To + vec 4 0)
|
||||||
stroke 8 #00F (d To + vec -4 0)
|
stroke 8 #00F (d To + vec (-4) 0)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -340,7 +281,7 @@ If we wanted to change the size of the points, we'd need to first update the str
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke 4 #F00 (d To + vec 4 0)
|
stroke 4 #F00 (d To + vec 4 0)
|
||||||
stroke 4 #00F (d To + vec -4 0)
|
stroke 4 #00F (d To + vec (-4) 0)
|
||||||
---
|
---
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -353,8 +294,8 @@ So we also have to update their positions.
|
||||||
[
|
[
|
||||||
stroke 4 #F00 (d To + vec 2 0)
|
stroke 4 #F00 (d To + vec 2 0)
|
||||||
---
|
---
|
||||||
stroke 4 #00F (d To + vec -2 0)
|
stroke 4 #00F (d To + vec (-2) 0)
|
||||||
--
|
--
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -381,7 +322,7 @@ thickness: 4
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke thickness #F00 (d To + vec 2 0)
|
stroke thickness #F00 (d To + vec 2 0)
|
||||||
stroke thickness #00F (d To + vec -2 0)
|
stroke thickness #00F (d To + vec (-2) 0)
|
||||||
---------
|
---------
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -414,7 +355,7 @@ xOffset: 2
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke thickness #F00 (d To + vec xOffset 0)
|
stroke thickness #F00 (d To + vec xOffset 0)
|
||||||
stroke thickness #00F (d To + vec -xOffset 0)
|
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||||
---------
|
---------
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -430,7 +371,7 @@ Uppercase names are special values we call _tags_.
|
||||||
|
|
||||||
Tags are values which represent names.
|
Tags are values which represent names.
|
||||||
For example, the `To` in `d To` is a tag.
|
For example, the `To` in `d To` is a tag.
|
||||||
It is the name of the piece of data we're extracting from `d`.
|
It represents 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.
|
There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood.
|
||||||
|
|
||||||
|
|
@ -447,7 +388,7 @@ xOffset: 2
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke thickness #F00 (d To + vec xOffset 0)
|
stroke thickness #F00 (d To + vec xOffset 0)
|
||||||
stroke thickness #00F (d To + vec -xOffset 0)
|
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -461,7 +402,7 @@ xOffset: thickness / 2
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
[
|
[
|
||||||
stroke thickness #F00 (d To + vec xOffset 0)
|
stroke thickness #F00 (d To + vec xOffset 0)
|
||||||
stroke thickness #00F (d To + vec -xOffset 0)
|
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -623,7 +564,6 @@ Seriously, 64 is my limit.
|
||||||
|
|
||||||
I wonder if there's any way we could automate this?
|
I wonder if there's any way we could automate this?
|
||||||
|
|
||||||
|
|
||||||
### The Ouroboros
|
### The Ouroboros
|
||||||
|
|
||||||
You know the drill by now.
|
You know the drill by now.
|
||||||
|
|
@ -647,7 +587,7 @@ splat: \d, radius ->
|
||||||
airbrush: \d, size ->
|
airbrush: \d, size ->
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
airbrush d size-8
|
airbrush d (size - 8)
|
||||||
]
|
]
|
||||||
|
|
||||||
withDotter \d ->
|
withDotter \d ->
|
||||||
|
|
@ -709,7 +649,7 @@ airbrush: \d, size ->
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
airbrush d size-8
|
airbrush d (size - 8)
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
|
@ -735,8 +675,8 @@ airbrush: \d, size ->
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
airbrush d size-1
|
airbrush d (size - 1)
|
||||||
---
|
---
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
|
@ -756,7 +696,7 @@ airbrush: \d, size ->
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
[
|
[
|
||||||
splat d size
|
splat d size
|
||||||
airbrush d size-1
|
airbrush d (size - 1)
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import_roots = [
|
||||||
|
|
||||||
"docs/rkgk.dj" = "Introduction to rakugaki"
|
"docs/rkgk.dj" = "Introduction to rakugaki"
|
||||||
"docs/system.dj" = "System library"
|
"docs/system.dj" = "System library"
|
||||||
"docs/haku.dj" = "haku language reference"
|
|
||||||
|
|
||||||
[wall_broker.default_wall_settings]
|
[wall_broker.default_wall_settings]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ code,
|
||||||
textarea {
|
textarea {
|
||||||
font-family: var(--font-monospace);
|
font-family: var(--font-monospace);
|
||||||
line-height: var(--line-height);
|
line-height: var(--line-height);
|
||||||
font-variant-ligatures: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ 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))
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ 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(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -40,10 +40,14 @@ thickness: 4
|
||||||
length: 5
|
length: 5
|
||||||
duty: 0.5
|
duty: 0.5
|
||||||
|
|
||||||
|
or_: \\a, b ->
|
||||||
|
if (a) a
|
||||||
|
else b
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
visible? = d.Num |mod length < length * duty
|
visible? = mod (d Num) length < length * duty
|
||||||
if (visible?)
|
if (visible?)
|
||||||
stroke thickness color (line d.From d.To)
|
stroke thickness color (line (d From) (d To))
|
||||||
else
|
else
|
||||||
()
|
()
|
||||||
`.trim(),
|
`.trim(),
|
||||||
|
|
@ -57,7 +61,7 @@ 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))
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -72,10 +76,10 @@ wavelength: 1
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
pi = 3.14159265
|
pi = 3.14159265
|
||||||
a = sin (d.Num * wavelength / pi) + 1 / 2
|
a = sin (d Num * wavelength / pi) + 1 / 2
|
||||||
range = maxThickness - minThickness
|
range = maxThickness - minThickness
|
||||||
thickness = a * range + minThickness
|
thickness = a * range + minThickness
|
||||||
stroke thickness color (line d.From d.To)
|
stroke thickness color (line (d From) (d To))
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -89,21 +93,22 @@ 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
|
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
|
pi = 3.14159265
|
||||||
a = sin (d.Num * wavelength / pi) * amplitude
|
a = sin (d Num * wavelength / pi) * amplitude
|
||||||
clockwise = norm (perpClockwise d.To-d.From) * vec a a
|
direction = (d To) - (d From)
|
||||||
from = d.From + clockwise
|
clockwise = norm (perpClockwise direction) * vec a a
|
||||||
to = d.To + clockwise
|
from = d From + clockwise
|
||||||
|
to = d To + clockwise
|
||||||
stroke thickness color (line from to)
|
stroke thickness color (line from to)
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
@ -116,16 +121,16 @@ wavelength: 0.1
|
||||||
thickness: 8
|
thickness: 8
|
||||||
|
|
||||||
colorCurve: \\n ->
|
colorCurve: \\n ->
|
||||||
n |cos |abs
|
abs (cos n)
|
||||||
|
|
||||||
withDotter \\d ->
|
withDotter \\d ->
|
||||||
pi = 3.14159265
|
pi = 3.14159265
|
||||||
l = wavelength
|
l = wavelength
|
||||||
r = colorCurve (d.Num * l)
|
r = colorCurve (d Num * l)
|
||||||
g = colorCurve (d.Num * l + pi/3)
|
g = colorCurve (d Num * l + pi/3)
|
||||||
b = colorCurve (d.Num * l + 2*pi/3)
|
b = colorCurve (d Num * l + 2*pi/3)
|
||||||
color = rgba r g b 1
|
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