Compare commits
13 commits
b52c1b26c9
...
914da923f7
| Author | SHA1 | Date | |
|---|---|---|---|
| 914da923f7 | |||
| b5cdfdb1b6 | |||
| a4c18c37dc | |||
| ec7ee9626f | |||
| 69cc34d07e | |||
| e31dde1048 | |||
| c4ad609717 | |||
| 958201cd18 | |||
| 2eea1f201f | |||
| 45099916fe | |||
| 449f2b59df | |||
| 9808d3227f | |||
| 29a80854a4 |
15 changed files with 866 additions and 161 deletions
3
.ignore
Normal file
3
.ignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
*.ttf
|
||||||
|
*.woff2
|
||||||
|
*.png
|
||||||
|
|
@ -401,6 +401,7 @@ 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 };
|
||||||
|
|
||||||
|
|
@ -431,6 +432,7 @@ 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;
|
||||||
|
|
@ -446,7 +448,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,14 +361,18 @@ 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);
|
||||||
|
|
||||||
if name == "=" {
|
match name {
|
||||||
|
":" => {
|
||||||
|
// Invalid use of a def inside an expression.
|
||||||
c.emit(Diagnostic::error(
|
c.emit(Diagnostic::error(
|
||||||
src.ast.span(op),
|
src.ast.span(op),
|
||||||
"defs `a = b` may only appear at the top level",
|
"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, left)?;
|
||||||
compile_expr(c, src, right)?;
|
compile_expr(c, src, right)?;
|
||||||
if let Some(index) = system::resolve(SystemFnArity::Binary, name) {
|
if let Some(index) = system::resolve(SystemFnArity::Binary, name) {
|
||||||
|
|
@ -379,34 +383,21 @@ fn compile_binary<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -
|
||||||
} else {
|
} else {
|
||||||
c.emit(Diagnostic::error(
|
c.emit(Diagnostic::error(
|
||||||
src.ast.span(op),
|
src.ast.span(op),
|
||||||
"this unary operator is currently unimplemented",
|
"this binary operator is currently unimplemented",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -> CompileResult {
|
fn emit_nary_call<'a>(
|
||||||
let mut walk = src.ast.walk(node_id);
|
c: &mut Compiler<'a>,
|
||||||
let Some(func) = walk.node() else {
|
src: &Source<'a>,
|
||||||
return Ok(());
|
func: NodeId,
|
||||||
};
|
argument_count: u8,
|
||||||
|
) -> CompileResult {
|
||||||
let name = src.ast.span(func).slice(src.code);
|
let name = src.ast.span(func).slice(src.code);
|
||||||
|
|
||||||
let mut argument_count = 0;
|
|
||||||
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(node_id),
|
|
||||||
"function call has too many arguments",
|
|
||||||
));
|
|
||||||
0
|
|
||||||
});
|
|
||||||
|
|
||||||
if let (NodeKind::Ident, Some(index)) = (
|
if let (NodeKind::Ident, Some(index)) = (
|
||||||
src.ast.kind(func),
|
src.ast.kind(func),
|
||||||
system::resolve(SystemFnArity::Nary, name),
|
system::resolve(SystemFnArity::Nary, name),
|
||||||
|
|
@ -423,6 +414,81 @@ fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) ->
|
||||||
c.chunk.emit_opcode(Opcode::Call)?;
|
c.chunk.emit_opcode(Opcode::Call)?;
|
||||||
c.chunk.emit_u8(argument_count)?;
|
c.chunk.emit_u8(argument_count)?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_call<'a>(c: &mut Compiler<'a>, src: &Source<'a>, node_id: NodeId) -> CompileResult {
|
||||||
|
let mut walk = src.ast.walk(node_id);
|
||||||
|
let Some(func) = walk.node() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut argument_count = 0;
|
||||||
|
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(node_id),
|
||||||
|
"function call has too many arguments",
|
||||||
|
));
|
||||||
|
0
|
||||||
|
});
|
||||||
|
|
||||||
|
emit_nary_call(c, src, func, argument_count)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_dot_call<'a>(
|
||||||
|
c: &mut Compiler<'a>,
|
||||||
|
src: &Source<'a>,
|
||||||
|
func: NodeId,
|
||||||
|
right: NodeId,
|
||||||
|
) -> CompileResult {
|
||||||
|
compile_expr(c, src, right)?;
|
||||||
|
emit_nary_call(c, src, func, 1)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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,7 +57,11 @@ 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 {
|
||||||
|
|
@ -65,6 +69,9 @@ 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) {
|
||||||
|
|
@ -208,6 +215,8 @@ 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,31 +311,13 @@ 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 Spacing {
|
enum Tightness {
|
||||||
Loose,
|
Loose(usize),
|
||||||
Call,
|
Call,
|
||||||
Tight,
|
Tight(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tightness((kind, spaces): (TokenKind, Spaces)) -> Option<(Spacing, usize)> {
|
fn tightness((kind, spaces): (TokenKind, Spaces)) -> Option<Tightness> {
|
||||||
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`
|
||||||
|
|
@ -345,12 +327,36 @@ 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,
|
|
||||||
// 4: reserve for `.`
|
TokenKind::Plus
|
||||||
_ if PREFIX_TOKENS.contains(kind) => 5,
|
| TokenKind::Minus
|
||||||
|
| 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((spacing, index))
|
Some(match kind {
|
||||||
|
// 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 {
|
||||||
|
|
@ -361,6 +367,12 @@ 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 {
|
||||||
|
|
@ -570,13 +582,15 @@ 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,
|
||||||
|
|
@ -584,6 +598,10 @@ 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),
|
||||||
|
|
@ -592,16 +610,17 @@ 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 | TokenKind::Not => unary(p),
|
TokenKind::Minus => 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!(
|
||||||
!PREFIX_TOKENS.contains(p.peek()),
|
!is_prefix_token(p.peek_with_spaces()),
|
||||||
"{:?} found in PREFIX_TOKENS",
|
"{:?} is not a prefix token",
|
||||||
p.peek()
|
p.peek_with_spaces()
|
||||||
);
|
);
|
||||||
|
|
||||||
let span = p.span();
|
let span = p.span();
|
||||||
|
|
@ -615,9 +634,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 {
|
||||||
match op.0 {
|
let (kind, spaces) = op;
|
||||||
|
match kind {
|
||||||
TokenKind::Plus
|
TokenKind::Plus
|
||||||
| TokenKind::Minus
|
|
||||||
| TokenKind::Star
|
| TokenKind::Star
|
||||||
| TokenKind::Slash
|
| TokenKind::Slash
|
||||||
| TokenKind::EqualEqual
|
| TokenKind::EqualEqual
|
||||||
|
|
@ -626,11 +645,14 @@ fn infix(p: &mut Parser, op: (TokenKind, Spaces)) -> NodeKind {
|
||||||
| TokenKind::LessEqual
|
| TokenKind::LessEqual
|
||||||
| TokenKind::Greater
|
| TokenKind::Greater
|
||||||
| TokenKind::GreaterEqual
|
| TokenKind::GreaterEqual
|
||||||
| TokenKind::Colon => infix_binary(p, op),
|
| TokenKind::Colon
|
||||||
|
| 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 PREFIX_TOKENS.contains(op.0) => infix_call(p, op),
|
_ if is_prefix_token(op) => infix_call(p, op),
|
||||||
|
|
||||||
_ => panic!("unhandled infix operator {op:?}"),
|
_ => panic!("unhandled infix operator {op:?}"),
|
||||||
}
|
}
|
||||||
|
|
@ -650,7 +672,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 PREFIX_TOKENS.contains(p.peek()) {
|
while is_prefix_token(p.peek_with_spaces()) {
|
||||||
precedence_parse(p, arg);
|
precedence_parse(p, arg);
|
||||||
arg = p.peek_with_spaces();
|
arg = p.peek_with_spaces();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ pub enum TokenKind {
|
||||||
Colon,
|
Colon,
|
||||||
Backslash,
|
Backslash,
|
||||||
RArrow,
|
RArrow,
|
||||||
|
Dot,
|
||||||
|
Pipe,
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
@ -135,6 +137,10 @@ 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,6 +46,7 @@ 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,6 +22,7 @@ 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 !a.eql(b) and !(a.lt(b) orelse return null);
|
return b.lt(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
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, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
pub fn format(value: Value, writer: *std.Io.Writer) !void {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
.nil => try std.fmt.formatBuf("Nil", options, writer),
|
.nil => try writer.writeAll("Nil"),
|
||||||
.false => try std.fmt.formatBuf("False", options, writer),
|
.false => try writer.writeAll("False"),
|
||||||
.true => try std.fmt.formatBuf("True", options, writer),
|
.true => try writer.writeAll("True"),
|
||||||
inline .tag, .number => |x| try std.fmt.format(writer, "{d}", .{x}),
|
inline .tag, .number => |x| try writer.print("{d}", .{x}),
|
||||||
inline .vec4, .rgba => |x| try std.fmt.format(writer, "{s}{d}", .{ @tagName(value), x }),
|
inline .vec4, .rgba => |x| try writer.print("{s}{d}", .{ @tagName(value), x }),
|
||||||
.ref => |ref| switch (ref.*) {
|
.ref => |ref| switch (ref.*) {
|
||||||
.closure => |c| try std.fmt.format(writer, "function {s}", .{c.name}),
|
.closure => |c| try writer.print("function {s}", .{c.name}),
|
||||||
.list => |l| {
|
.list => |l| {
|
||||||
try std.fmt.formatBuf("[", options, writer);
|
try writer.writeAll("[");
|
||||||
for (l, 0..) |elem, i| {
|
for (l, 0..) |elem, i| {
|
||||||
if (i != 0) try std.fmt.formatBuf(", ", options, writer);
|
if (i != 0) try writer.writeAll(", ");
|
||||||
try elem.format(fmt, options, writer);
|
try elem.format(writer);
|
||||||
}
|
}
|
||||||
try std.fmt.formatBuf("]", options, writer);
|
try writer.writeAll("]");
|
||||||
},
|
},
|
||||||
inline .shape, .scribble, .reticle => |x| try std.fmt.format(writer, "{}", .{x}),
|
inline .shape, .scribble, .reticle => |x| try writer.print("{}", .{x}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,24 @@ 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 {
|
||||||
|
|
@ -167,7 +185,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 {any} <- {}", .{ vm.stack[0..vm.stack_top], val });
|
log.debug("PUSH {f} <- {f}", .{ stackFmt(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;
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +194,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 {any} -> {}", .{ vm.stack[0..vm.stack_top], result });
|
log.debug("POP {f} -> {f}", .{ stackFmt(vm.stack[0..vm.stack_top]), result });
|
||||||
return vm.stack[vm.stack_top];
|
return vm.stack[vm.stack_top];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
520
docs/haku.dj
Normal file
520
docs/haku.dj
Normal file
|
|
@ -0,0 +1,520 @@
|
||||||
|
# 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.
|
||||||
138
docs/rkgk.dj
138
docs/rkgk.dj
|
|
@ -1,5 +1,15 @@
|
||||||
# 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.
|
||||||
|
|
@ -28,7 +38,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.
|
||||||
|
|
@ -86,8 +96,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)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -108,26 +118,16 @@ 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,6 +179,7 @@ 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.
|
||||||
|
|
||||||
|
|
@ -197,22 +198,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` expression is parenthesized.
|
Also note how the `d.To + vec 10 0` expression is parenthesized.
|
||||||
This is because otherwise, its individual parts would be interpreted as separate arguments to `stroke`, which is not what we want!
|
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!
|
||||||
|
|
@ -231,10 +232,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
|
||||||
|
|
@ -249,8 +250,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)) 32 32)
|
stroke 8 #00F (rect (d.To + vec 0 -16) (vec 32 32))
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -259,6 +260,64 @@ 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.
|
||||||
|
|
@ -270,7 +329,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)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -281,7 +340,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)
|
||||||
---
|
---
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +353,7 @@ 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)
|
||||||
--
|
--
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -322,7 +381,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)
|
||||||
---------
|
---------
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -355,7 +414,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)
|
||||||
---------
|
---------
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -371,7 +430,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 represents the name of the piece of data we're extracting from `d`.
|
It is the name of the piece of data we're extracting from `d`.
|
||||||
|
|
||||||
There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood.
|
There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood.
|
||||||
|
|
||||||
|
|
@ -388,7 +447,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)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -402,7 +461,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)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -564,6 +623,7 @@ 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.
|
||||||
|
|
@ -587,7 +647,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 ->
|
||||||
|
|
@ -649,7 +709,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
|
||||||
[]
|
[]
|
||||||
|
|
@ -675,7 +735,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
|
||||||
|
|
@ -696,7 +756,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,6 +23,7 @@ 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,6 +53,7 @@ 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,14 +40,10 @@ 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? = mod (d Num) length < length * duty
|
visible? = d.Num |mod 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(),
|
||||||
|
|
@ -61,7 +57,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(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -76,10 +72,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(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -93,22 +89,21 @@ 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
|
||||||
direction = (d To) - (d From)
|
clockwise = norm (perpClockwise d.To-d.From) * vec a a
|
||||||
clockwise = norm (perpClockwise direction) * vec a a
|
from = d.From + clockwise
|
||||||
from = d From + clockwise
|
to = d.To + clockwise
|
||||||
to = d To + clockwise
|
|
||||||
stroke thickness color (line from to)
|
stroke thickness color (line from to)
|
||||||
`.trim(),
|
`.trim(),
|
||||||
},
|
},
|
||||||
|
|
@ -121,16 +116,16 @@ wavelength: 0.1
|
||||||
thickness: 8
|
thickness: 8
|
||||||
|
|
||||||
colorCurve: \\n ->
|
colorCurve: \\n ->
|
||||||
abs (cos n)
|
n |cos |abs
|
||||||
|
|
||||||
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