[BREAKING] Feat: quotation is now using ()
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
# Uncomment to use local doux for development
|
# Uncomment to use local doux for development
|
||||||
paths = ["/Users/bubo/doux"]
|
paths = ["/Users/bubo/doux"]
|
||||||
|
|
||||||
|
[env]
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = "12.0"
|
||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
xtask = "run --package xtask --release --"
|
xtask = "run --package xtask --release --"
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -7,6 +7,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: "12.0"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ doux = { git = "https://github.com/sova-org/doux", features = ["native", "soundf
|
|||||||
rusty_link = "0.4"
|
rusty_link = "0.4"
|
||||||
ratatui = "0.30"
|
ratatui = "0.30"
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
cpal = { version = "0.17", features = ["jack"], optional = true }
|
cpal = { version = "0.17", optional = true }
|
||||||
clap = { version = "4", features = ["derive"], optional = true }
|
clap = { version = "4", features = ["derive"], optional = true }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@@ -83,6 +83,9 @@ rustc-hash = { version = "2", optional = true }
|
|||||||
image = { version = "0.25", default-features = false, features = ["png"], optional = true }
|
image = { version = "0.25", default-features = false, features = ["png"], optional = true }
|
||||||
|
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
cpal = { version = "0.17", optional = true, features = ["jack"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
winres = "0.1"
|
winres = "0.1"
|
||||||
|
|
||||||
@@ -109,3 +112,4 @@ icon = ["assets/Cagire.icns", "assets/Cagire.ico", "assets/Cagire.png"]
|
|||||||
copyright = "Copyright (c) 2025 Raphaël Forment"
|
copyright = "Copyright (c) 2025 Raphaël Forment"
|
||||||
category = "Music"
|
category = "Music"
|
||||||
short_description = "Forth-based music sequencer"
|
short_description = "Forth-based music sequencer"
|
||||||
|
minimum_system_version = "12.0"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ fn tokenize(input: &str) -> Vec<Token> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == '(' || c == ')' {
|
if c == '{' || c == '}' {
|
||||||
chars.next();
|
chars.next();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
|||||||
Token::Str(s, span) => ops.push(Op::PushStr(Arc::from(s.as_str()), Some(*span))),
|
Token::Str(s, span) => ops.push(Op::PushStr(Arc::from(s.as_str()), Some(*span))),
|
||||||
Token::Word(w, span) => {
|
Token::Word(w, span) => {
|
||||||
let word = w.as_str();
|
let word = w.as_str();
|
||||||
if word == "{" {
|
if word == "(" {
|
||||||
let (quote_ops, consumed, end_span) =
|
let (quote_ops, consumed, end_span) =
|
||||||
compile_quotation(&tokens[i + 1..], dict)?;
|
compile_quotation(&tokens[i + 1..], dict)?;
|
||||||
i += consumed;
|
i += consumed;
|
||||||
@@ -142,8 +142,8 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
|||||||
end: end_span.end,
|
end: end_span.end,
|
||||||
};
|
};
|
||||||
ops.push(Op::Quotation(Arc::from(quote_ops), Some(body_span)));
|
ops.push(Op::Quotation(Arc::from(quote_ops), Some(body_span)));
|
||||||
} else if word == "}" {
|
} else if word == ")" {
|
||||||
return Err("unexpected }".into());
|
return Err("unexpected )".into());
|
||||||
} else if word == "[" {
|
} else if word == "[" {
|
||||||
let (bracket_ops, consumed, end_span) =
|
let (bracket_ops, consumed, end_span) =
|
||||||
compile_bracket(&tokens[i + 1..], dict)?;
|
compile_bracket(&tokens[i + 1..], dict)?;
|
||||||
@@ -203,8 +203,8 @@ fn compile_quotation(
|
|||||||
for (i, tok) in tokens.iter().enumerate() {
|
for (i, tok) in tokens.iter().enumerate() {
|
||||||
if let Token::Word(w, _) = tok {
|
if let Token::Word(w, _) = tok {
|
||||||
match w.as_str() {
|
match w.as_str() {
|
||||||
"{" => depth += 1,
|
"(" => depth += 1,
|
||||||
"}" => {
|
")" => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
end_idx = Some(i);
|
end_idx = Some(i);
|
||||||
@@ -216,7 +216,7 @@ fn compile_quotation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let end_idx = end_idx.ok_or("missing }")?;
|
let end_idx = end_idx.ok_or("missing )")?;
|
||||||
let end_span = match &tokens[end_idx] {
|
let end_span = match &tokens[end_idx] {
|
||||||
Token::Word(_, span) => *span,
|
Token::Word(_, span) => *span,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|||||||
@@ -502,7 +502,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Logic",
|
category: "Logic",
|
||||||
stack: "(true-quot false-quot bool --)",
|
stack: "(true-quot false-quot bool --)",
|
||||||
desc: "Execute true-quot if true, else false-quot",
|
desc: "Execute true-quot if true, else false-quot",
|
||||||
example: "{ 1 } { 2 } coin ifelse",
|
example: "( 1 ) ( 2 ) coin ifelse",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -512,7 +512,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Logic",
|
category: "Logic",
|
||||||
stack: "(..quots n --)",
|
stack: "(..quots n --)",
|
||||||
desc: "Execute nth quotation (0-indexed)",
|
desc: "Execute nth quotation (0-indexed)",
|
||||||
example: "{ 1 } { 2 } { 3 } 2 select => 3",
|
example: "( 1 ) ( 2 ) ( 3 ) 2 select => 3",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: true,
|
varargs: true,
|
||||||
},
|
},
|
||||||
@@ -522,7 +522,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Logic",
|
category: "Logic",
|
||||||
stack: "(quot bool --)",
|
stack: "(quot bool --)",
|
||||||
desc: "Execute quotation if true",
|
desc: "Execute quotation if true",
|
||||||
example: "{ 2 distort } 0.5 chance ?",
|
example: "( 2 distort ) 0.5 chance ?",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -532,7 +532,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Logic",
|
category: "Logic",
|
||||||
stack: "(quot bool --)",
|
stack: "(quot bool --)",
|
||||||
desc: "Execute quotation if false",
|
desc: "Execute quotation if false",
|
||||||
example: "{ 1 distort } 0.5 chance !?",
|
example: "( 1 distort ) 0.5 chance !?",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -542,7 +542,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Logic",
|
category: "Logic",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Execute quotation unconditionally",
|
desc: "Execute quotation unconditionally",
|
||||||
example: "{ 2 * } apply",
|
example: "( 2 * ) apply",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -553,7 +553,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Control",
|
category: "Control",
|
||||||
stack: "(n quot --)",
|
stack: "(n quot --)",
|
||||||
desc: "Execute quotation n times, @i holds current index",
|
desc: "Execute quotation n times, @i holds current index",
|
||||||
example: "4 { @i . } times => 0 1 2 3",
|
example: "4 ( @i . ) times => 0 1 2 3",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot prob --)",
|
stack: "(quot prob --)",
|
||||||
desc: "Execute quotation with probability (0.0-1.0)",
|
desc: "Execute quotation with probability (0.0-1.0)",
|
||||||
example: "{ 2 distort } 0.75 chance",
|
example: "( 2 distort ) 0.75 chance",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -70,7 +70,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot pct --)",
|
stack: "(quot pct --)",
|
||||||
desc: "Execute quotation with probability (0-100)",
|
desc: "Execute quotation with probability (0-100)",
|
||||||
example: "{ 2 distort } 75 prob",
|
example: "( 2 distort ) 75 prob",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -150,7 +150,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Always execute quotation",
|
desc: "Always execute quotation",
|
||||||
example: "{ 2 distort } always",
|
example: "( 2 distort ) always",
|
||||||
compile: Probability(1.0),
|
compile: Probability(1.0),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -160,7 +160,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Never execute quotation",
|
desc: "Never execute quotation",
|
||||||
example: "{ 2 distort } never",
|
example: "( 2 distort ) never",
|
||||||
compile: Probability(0.0),
|
compile: Probability(0.0),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -170,7 +170,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Execute quotation 75% of the time",
|
desc: "Execute quotation 75% of the time",
|
||||||
example: "{ 2 distort } often",
|
example: "( 2 distort ) often",
|
||||||
compile: Probability(0.75),
|
compile: Probability(0.75),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -180,7 +180,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Execute quotation 50% of the time",
|
desc: "Execute quotation 50% of the time",
|
||||||
example: "{ 2 distort } sometimes",
|
example: "( 2 distort ) sometimes",
|
||||||
compile: Probability(0.5),
|
compile: Probability(0.5),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -190,7 +190,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Execute quotation 25% of the time",
|
desc: "Execute quotation 25% of the time",
|
||||||
example: "{ 2 distort } rarely",
|
example: "( 2 distort ) rarely",
|
||||||
compile: Probability(0.25),
|
compile: Probability(0.25),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -200,7 +200,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Execute quotation 10% of the time",
|
desc: "Execute quotation 10% of the time",
|
||||||
example: "{ 2 distort } almostNever",
|
example: "( 2 distort ) almostNever",
|
||||||
compile: Probability(0.1),
|
compile: Probability(0.1),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -210,7 +210,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Probability",
|
category: "Probability",
|
||||||
stack: "(quot --)",
|
stack: "(quot --)",
|
||||||
desc: "Execute quotation 90% of the time",
|
desc: "Execute quotation 90% of the time",
|
||||||
example: "{ 2 distort } almostAlways",
|
example: "( 2 distort ) almostAlways",
|
||||||
compile: Probability(0.9),
|
compile: Probability(0.9),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -221,7 +221,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Time",
|
category: "Time",
|
||||||
stack: "(quot n --)",
|
stack: "(quot n --)",
|
||||||
desc: "Execute quotation every nth iteration",
|
desc: "Execute quotation every nth iteration",
|
||||||
example: "{ 2 distort } 4 every",
|
example: "( 2 distort ) 4 every",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -231,7 +231,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Time",
|
category: "Time",
|
||||||
stack: "(quot n --)",
|
stack: "(quot n --)",
|
||||||
desc: "Execute quotation on all iterations except every nth",
|
desc: "Execute quotation on all iterations except every nth",
|
||||||
example: "{ 2 distort } 4 except",
|
example: "( 2 distort ) 4 except",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -241,7 +241,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Time",
|
category: "Time",
|
||||||
stack: "(quot n offset --)",
|
stack: "(quot n offset --)",
|
||||||
desc: "Execute quotation every nth iteration with phase offset",
|
desc: "Execute quotation every nth iteration with phase offset",
|
||||||
example: "{ snare } 4 2 every+ => fires at iter 2, 6, 10...",
|
example: "( snare ) 4 2 every+ => fires at iter 2, 6, 10...",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -251,7 +251,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Time",
|
category: "Time",
|
||||||
stack: "(quot n offset --)",
|
stack: "(quot n offset --)",
|
||||||
desc: "Skip quotation every nth iteration with phase offset",
|
desc: "Skip quotation every nth iteration with phase offset",
|
||||||
example: "{ snare } 4 2 except+ => skips at iter 2, 6, 10...",
|
example: "( snare ) 4 2 except+ => skips at iter 2, 6, 10...",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -261,7 +261,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Time",
|
category: "Time",
|
||||||
stack: "(quot k n --)",
|
stack: "(quot k n --)",
|
||||||
desc: "Execute quotation using Euclidean distribution over step runs",
|
desc: "Execute quotation using Euclidean distribution over step runs",
|
||||||
example: "{ 2 distort } 3 8 bjork",
|
example: "( 2 distort ) 3 8 bjork",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -271,7 +271,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Time",
|
category: "Time",
|
||||||
stack: "(quot k n --)",
|
stack: "(quot k n --)",
|
||||||
desc: "Execute quotation using Euclidean distribution over pattern iterations",
|
desc: "Execute quotation using Euclidean distribution over pattern iterations",
|
||||||
example: "{ 2 distort } 3 8 pbjork",
|
example: "( 2 distort ) 3 8 pbjork",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -456,7 +456,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Desktop",
|
category: "Desktop",
|
||||||
stack: "(-- bool)",
|
stack: "(-- bool)",
|
||||||
desc: "1 when mouse button held, 0 otherwise",
|
desc: "1 when mouse button held, 0 otherwise",
|
||||||
example: "mdown { \"crash\" s . } ?",
|
example: "mdown ( \"crash\" s . ) ?",
|
||||||
compile: Context("mdown"),
|
compile: Context("mdown"),
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
@@ -487,7 +487,7 @@ pub(super) const WORDS: &[Word] = &[
|
|||||||
category: "Generator",
|
category: "Generator",
|
||||||
stack: "(quot n -- results...)",
|
stack: "(quot n -- results...)",
|
||||||
desc: "Execute quotation n times, push all results",
|
desc: "Execute quotation n times, push all results",
|
||||||
example: "{ 1 6 rand } 4 gen => 4 random values",
|
example: "( 1 6 rand ) 4 gen => 4 random values",
|
||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: true,
|
varargs: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"i": 0,
|
"i": 0,
|
||||||
"script": "0 8 12 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n{ inv } rarely\n{ inv } sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n2 release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand \n0.0 1.0 rand timbre\n0.5 gain\n0.8 sustain\n2 8 rand release\n."
|
"script": "0 8 12 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n( inv ) rarely\n( inv ) sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n2 release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand \n0.0 1.0 rand timbre\n0.5 gain\n0.8 sustain\n2 8 rand release\n."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"i": 4,
|
"i": 4,
|
||||||
"script": "0 12 20 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n{ inv } rarely\n{ inv } sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n10 16 rand release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand 0.0 1.0 rand timbre\n0.5 gain\n{ . } 2 every"
|
"script": "0 12 20 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n( inv ) rarely\n( inv ) sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n10 16 rand release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand 0.0 1.0 rand timbre\n0.5 gain\n( . ) 2 every"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"length": 16,
|
"length": 16,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Four basic types of values can live on the stack:
|
|||||||
- **Integers**: `42`, `-7`, `0`
|
- **Integers**: `42`, `-7`, `0`
|
||||||
- **Floats**: `0.5`, `3.14`, `-1.0`
|
- **Floats**: `0.5`, `3.14`, `-1.0`
|
||||||
- **Strings**: `"kick"`, `"hello"`
|
- **Strings**: `"kick"`, `"hello"`
|
||||||
- **Quotations**: `{ dup + }` (code as data)
|
- **Quotations**: `( dup + )` (code as data)
|
||||||
|
|
||||||
Floats can omit the leading zero: `.25` is the same as `0.25`, and `-.5` is `-0.5`.
|
Floats can omit the leading zero: `.25` is the same as `0.25`, and `-.5` is `-0.5`.
|
||||||
|
|
||||||
|
|||||||
@@ -29,22 +29,22 @@ These are compiled directly into branch instructions. For that reason, these wor
|
|||||||
When you already have a quotation, `?` executes it if the condition is truthy:
|
When you already have a quotation, `?` executes it if the condition is truthy:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0.4 verb } coin ?
|
( 0.4 verb ) coin ?
|
||||||
saw s c4 note 0.5 gain . ;; reverb on half the hits
|
saw s c4 note 0.5 gain . ;; reverb on half the hits
|
||||||
```
|
```
|
||||||
|
|
||||||
`!?` is the opposite — executes when falsy:
|
`!?` is the opposite — executes when falsy:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0.2 gain } coin !?
|
( 0.2 gain ) coin !?
|
||||||
saw s c4 note . ;; quiet on half the hits
|
saw s c4 note . ;; quiet on half the hits
|
||||||
```
|
```
|
||||||
|
|
||||||
These pair well with `chance`, `prob`, and the other probability words:
|
These pair well with `chance`, `prob`, and the other probability words:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0.5 verb } 0.3 chance ? ;; occasional reverb wash
|
( 0.5 verb ) 0.3 chance ? ;; occasional reverb wash
|
||||||
{ 12 + } fill ? ;; octave up during fills
|
( 12 + ) fill ? ;; octave up during fills
|
||||||
```
|
```
|
||||||
|
|
||||||
## ifelse
|
## ifelse
|
||||||
@@ -52,14 +52,14 @@ These pair well with `chance`, `prob`, and the other probability words:
|
|||||||
Two quotations, one condition. The true branch comes first:
|
Two quotations, one condition. The true branch comes first:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ c3 note } { c4 note } coin ifelse
|
( c3 note ) ( c4 note ) coin ifelse
|
||||||
saw s 0.6 gain . ;; bass or lead, coin flip
|
saw s 0.6 gain . ;; bass or lead, coin flip
|
||||||
```
|
```
|
||||||
|
|
||||||
Reads naturally: "c3 or c4, depending on the coin."
|
Reads naturally: "c3 or c4, depending on the coin."
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0.8 gain } { 0.3 gain } fill ifelse
|
( 0.8 gain ) ( 0.3 gain ) fill ifelse
|
||||||
tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise
|
tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise
|
|||||||
Choose the nth option from a list of quotations:
|
Choose the nth option from a list of quotations:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ c4 } { e4 } { g4 } { b4 } iter 4 mod select
|
( c4 ) ( e4 ) ( g4 ) ( b4 ) iter 4 mod select
|
||||||
note sine s 0.5 decay .
|
note sine s 0.5 decay .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ Four notes cycling through a major seventh chord, one per pattern iteration. The
|
|||||||
When you have a quotation and want to execute it unconditionally, use `apply`:
|
When you have a quotation and want to execute it unconditionally, use `apply`:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ dup + } apply ;; doubles the top value
|
( dup + ) apply ;; doubles the top value
|
||||||
```
|
```
|
||||||
|
|
||||||
This is simpler than `?` when there is no condition to check. It pops the quotation and runs it.
|
This is simpler than `?` when there is no condition to check. It pops the quotation and runs it.
|
||||||
@@ -115,14 +115,14 @@ saw s c4 note .
|
|||||||
Repeat a quotation n times. The variable `@i` is automatically set to the current iteration index (starting from 0):
|
Repeat a quotation n times. The variable `@i` is automatically set to the current iteration index (starting from 0):
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
3 { c4 @i 4 * + note } times
|
3 ( c4 @i 4 * + note ) times
|
||||||
sine s 0.4 gain 0.5 verb . ;; c4, e4, g#4 — a chord
|
sine s 0.4 gain 0.5 verb . ;; c4, e4, g#4 — a chord
|
||||||
```
|
```
|
||||||
|
|
||||||
Subdivide with `at`:
|
Subdivide with `at`:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
4 { @i 4 / at sine s c4 note 0.3 gain . } times
|
4 ( @i 4 / at sine s c4 note 0.3 gain . ) times
|
||||||
```
|
```
|
||||||
|
|
||||||
Four evenly spaced notes within the step.
|
Four evenly spaced notes within the step.
|
||||||
@@ -130,11 +130,11 @@ Four evenly spaced notes within the step.
|
|||||||
Vary intensity per iteration:
|
Vary intensity per iteration:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
8 {
|
8 (
|
||||||
@i 8 / at
|
@i 8 / at
|
||||||
@i 4 mod 0 = if 0.7 else 0.2 then gain
|
@i 4 mod 0 = if 0.7 else 0.2 then gain
|
||||||
tri s c5 note 0.1 decay .
|
tri s c5 note 0.1 decay .
|
||||||
} times
|
) times
|
||||||
```
|
```
|
||||||
|
|
||||||
Eight notes per step. Every fourth one louder.
|
Eight notes per step. Every fourth one louder.
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ Everything after `;;` until the end of the line is ignored.
|
|||||||
|
|
||||||
Classic Forth has no quotations. Code is not a value you can pass around.
|
Classic Forth has no quotations. Code is not a value you can pass around.
|
||||||
|
|
||||||
Cagire has first-class quotations using curly braces:
|
Cagire has first-class quotations using parentheses:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ dup + }
|
( dup + )
|
||||||
```
|
```
|
||||||
|
|
||||||
This pushes a block of code onto the stack. You can store it, pass it to other words, and execute it later. Quotations enable conditionals, probability, and cycling.
|
This pushes a block of code onto the stack. You can store it, pass it to other words, and execute it later. Quotations enable conditionals, probability, and cycling.
|
||||||
@@ -41,14 +41,14 @@ x 0 > IF 1 ELSE -1 THEN
|
|||||||
Cagire supports this syntax but also provides quotation-based conditionals:
|
Cagire supports this syntax but also provides quotation-based conditionals:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 1 } { -1 } x 0 > ifelse
|
( 1 ) ( -1 ) x 0 > ifelse
|
||||||
```
|
```
|
||||||
|
|
||||||
The words `?` and `!?` execute a quotation based on a condition:
|
The words `?` and `!?` execute a quotation based on a condition:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ "kick" s . } coin ? ;; execute if coin is 1
|
( "kick" s . ) coin ? ;; execute if coin is 1
|
||||||
{ "snare" s . } coin !? ;; execute if coin is 0
|
( "snare" s . ) coin !? ;; execute if coin is 0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Strings
|
## Strings
|
||||||
@@ -116,21 +116,21 @@ Classic Forth has `DO ... LOOP`:
|
|||||||
Cagire uses a quotation-based loop with `times`:
|
Cagire uses a quotation-based loop with `times`:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
4 { @i . } times ;; prints 0 1 2 3
|
4 ( @i . ) times ;; prints 0 1 2 3
|
||||||
```
|
```
|
||||||
|
|
||||||
The loop counter is stored in the variable `i`, accessed with `@i`. This fits Cagire's style where control flow uses quotations.
|
The loop counter is stored in the variable `i`, accessed with `@i`. This fits Cagire's style where control flow uses quotations.
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
4 { @i 4 / at hat s . } times ;; hat at 0, 0.25, 0.5, 0.75
|
4 ( @i 4 / at hat s . ) times ;; hat at 0, 0.25, 0.5, 0.75
|
||||||
4 { c4 @i + note sine s . } times ;; ascending notes
|
4 ( c4 @i + note sine s . ) times ;; ascending notes
|
||||||
```
|
```
|
||||||
|
|
||||||
For generating sequences without side effects, use `..` or `gen`:
|
For generating sequences without side effects, use `..` or `gen`:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
1 5 .. ;; pushes 1 2 3 4 5
|
1 5 .. ;; pushes 1 2 3 4 5
|
||||||
{ dup * } 4 gen ;; pushes 0 1 4 9 (squares)
|
( dup * ) 4 gen ;; pushes 0 1 4 9 (squares)
|
||||||
```
|
```
|
||||||
|
|
||||||
## The Command Register
|
## The Command Register
|
||||||
@@ -167,11 +167,11 @@ These have no equivalent in classic Forth. They connect your script to the seque
|
|||||||
Classic Forth is deterministic. Cagire has built-in randomness:
|
Classic Forth is deterministic. Cagire has built-in randomness:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ "snare" s . } 50 prob ;; 50% chance
|
( "snare" s . ) 50 prob ;; 50% chance
|
||||||
{ "clap" s . } 0.25 chance ;; 25% chance
|
( "clap" s . ) 0.25 chance ;; 25% chance
|
||||||
{ "hat" s . } often ;; 75% chance
|
( "hat" s . ) often ;; 75% chance
|
||||||
{ "rim" s . } sometimes ;; 50% chance
|
( "rim" s . ) sometimes ;; 50% chance
|
||||||
{ "tom" s . } rarely ;; 25% chance
|
( "tom" s . ) rarely ;; 25% chance
|
||||||
```
|
```
|
||||||
|
|
||||||
These words take a quotation and execute it probabilistically.
|
These words take a quotation and execute it probabilistically.
|
||||||
@@ -181,9 +181,9 @@ These words take a quotation and execute it probabilistically.
|
|||||||
Execute a quotation on specific iterations:
|
Execute a quotation on specific iterations:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ "snare" s . } 4 every ;; every 4th pattern iteration
|
( "snare" s . ) 4 every ;; every 4th pattern iteration
|
||||||
{ "hat" s . } 3 8 bjork ;; Euclidean: 3 hits across 8 step runs
|
( "hat" s . ) 3 8 bjork ;; Euclidean: 3 hits across 8 step runs
|
||||||
{ "hat" s . } 5 8 pbjork ;; Euclidean: 5 hits across 8 pattern iterations
|
( "hat" s . ) 5 8 pbjork ;; Euclidean: 5 hits across 8 pattern iterations
|
||||||
```
|
```
|
||||||
|
|
||||||
`every` checks the pattern iteration count. On iteration 0, 4, 8, 12... the quotation runs. On all other iterations it is skipped.
|
`every` checks the pattern iteration count. On iteration 0, 4, 8, 12... the quotation runs. On all other iterations it is skipped.
|
||||||
@@ -203,7 +203,7 @@ Each time the step runs, a different note is selected. The `3` tells `cycle` how
|
|||||||
You can also use quotations if you need to execute code:
|
You can also use quotations if you need to execute code:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ c4 note } { e4 note } { g4 note } 3 cycle
|
( c4 note ) ( e4 note ) ( g4 note ) 3 cycle
|
||||||
```
|
```
|
||||||
|
|
||||||
When the selected value is a quotation, it gets executed. When it is a plain value, it gets pushed onto the stack.
|
When the selected value is a quotation, it gets executed. When it is a plain value, it gets pushed onto the stack.
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ Euclidean distribution via `euclid`:
|
|||||||
Random timing via `gen`:
|
Random timing via `gen`:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0.0 1.0 rand } 4 gen at hat s . ;; 4 hats at random positions
|
( 0.0 1.0 rand ) 4 gen at hat s . ;; 4 hats at random positions
|
||||||
```
|
```
|
||||||
|
|
||||||
Geometric spacing via `geom..`:
|
Geometric spacing via `geom..`:
|
||||||
@@ -89,10 +89,10 @@ Geometric spacing via `geom..`:
|
|||||||
Wrap `at` expressions in quotations for conditional timing:
|
Wrap `at` expressions in quotations for conditional timing:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0 0.25 0.5 0.75 at } 2 every ;; 16th-note hats every other bar
|
( 0 0.25 0.5 0.75 at ) 2 every ;; 16th-note hats every other bar
|
||||||
hat s .
|
hat s .
|
||||||
|
|
||||||
{ 0 0.5 at } 0.5 chance ;; 50% chance of double-hit
|
( 0 0.5 at ) 0.5 chance ;; 50% chance of double-hit
|
||||||
kick s .
|
kick s .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -40,15 +40,15 @@ That gives you 110, 220, 440, 880, 1760 (reversed), ready to feed into `freq`.
|
|||||||
`gen` executes a quotation n times and collects all results. The quotation must push exactly one value per call:
|
`gen` executes a quotation n times and collects all results. The quotation must push exactly one value per call:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 1 6 rand } 4 gen ;; 4 random values between 1 and 6
|
( 1 6 rand ) 4 gen ;; 4 random values between 1 and 6
|
||||||
{ coin } 8 gen ;; 8 random 0s and 1s
|
( coin ) 8 gen ;; 8 random 0s and 1s
|
||||||
```
|
```
|
||||||
|
|
||||||
Contrast with `times`, which executes for side effects and does not collect. `times` sets `@i` to the current index:
|
Contrast with `times`, which executes for side effects and does not collect. `times` sets `@i` to the current index:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
4 { @i } times ;; 0 1 2 3 (pushes @i each iteration)
|
4 ( @i ) times ;; 0 1 2 3 (pushes @i each iteration)
|
||||||
4 { @i 60 + note sine s . } times ;; plays 4 notes, collects nothing
|
4 ( @i 60 + note sine s . ) times ;; plays 4 notes, collects nothing
|
||||||
```
|
```
|
||||||
|
|
||||||
The distinction: `gen` is for building data. `times` is for doing things.
|
The distinction: `gen` is for building data. `times` is for doing things.
|
||||||
@@ -109,7 +109,7 @@ c4 e4 g4 b4 4 shuffle ;; random permutation each time
|
|||||||
Useful for computing averages or accumulating values:
|
Useful for computing averages or accumulating values:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 1 6 rand } 4 gen 4 sum ;; sum of 4 dice rolls
|
( 1 6 rand ) 4 gen 4 sum ;; sum of 4 dice rolls
|
||||||
```
|
```
|
||||||
|
|
||||||
## Replication
|
## Replication
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ c4 mtof freq sine s .
|
|||||||
A chord progression cycling every pattern iteration:
|
A chord progression cycling every pattern iteration:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ c3 maj7 } { f3 maj7 } { g3 dom7 } { c3 maj7 } 4 pcycle
|
( c3 maj7 ) ( f3 maj7 ) ( g3 dom7 ) ( c3 maj7 ) 4 pcycle
|
||||||
note sine s .
|
note sine s .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ Chord voicings with random inversion:
|
|||||||
|
|
||||||
```forth
|
```forth
|
||||||
e3 min9
|
e3 min9
|
||||||
{ } { 1 oct } 2 choose
|
( ) ( 1 oct ) 2 choose
|
||||||
note modal s .
|
note modal s .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ These are useful for parameters where perception is logarithmic, like frequency
|
|||||||
The probability words take a quotation and execute it with some chance. `chance` takes a float from 0.0 to 1.0, `prob` takes a percentage from 0 to 100:
|
The probability words take a quotation and execute it with some chance. `chance` takes a float from 0.0 to 1.0, `prob` takes a percentage from 0 to 100:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ hat s . } 0.25 chance ;; 25% chance
|
( hat s . ) 0.25 chance ;; 25% chance
|
||||||
{ hat s . } 75 prob ;; 75% chance
|
( hat s . ) 75 prob ;; 75% chance
|
||||||
```
|
```
|
||||||
|
|
||||||
Named probability words save you from remembering numbers:
|
Named probability words save you from remembering numbers:
|
||||||
@@ -48,9 +48,9 @@ Named probability words save you from remembering numbers:
|
|||||||
| `never` | 0% |
|
| `never` | 0% |
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ hat s . } often ;; 75%
|
( hat s . ) often ;; 75%
|
||||||
{ snare s . } sometimes ;; 50%
|
( snare s . ) sometimes ;; 50%
|
||||||
{ clap s . } rarely ;; 25%
|
( clap s . ) rarely ;; 25%
|
||||||
```
|
```
|
||||||
|
|
||||||
`always` and `never` are useful when you want to temporarily mute or unmute a voice without deleting code. Change `sometimes` to `never` to silence it, `always` to bring it back.
|
`always` and `never` are useful when you want to temporarily mute or unmute a voice without deleting code. Change `sometimes` to `never` to silence it, `always` to bring it back.
|
||||||
@@ -58,8 +58,8 @@ Named probability words save you from remembering numbers:
|
|||||||
Use `?` and `!?` with `coin` for quick coin-flip decisions:
|
Use `?` and `!?` with `coin` for quick coin-flip decisions:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ hat s . } coin ? ;; execute if coin is 1
|
( hat s . ) coin ? ;; execute if coin is 1
|
||||||
{ rim s . } coin !? ;; execute if coin is 0
|
( rim s . ) coin !? ;; execute if coin is 0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Selection
|
## Selection
|
||||||
@@ -74,7 +74,7 @@ kick snare hat 3 choose s . ;; random drum hit
|
|||||||
When a chosen item is a quotation, it gets executed:
|
When a chosen item is a quotation, it gets executed:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 0.1 decay } { 0.5 decay } { 0.9 decay } 3 choose
|
( 0.1 decay ) ( 0.5 decay ) ( 0.9 decay ) 3 choose
|
||||||
sine s .
|
sine s .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ The difference matters when patterns have different lengths. `cycle` counts per-
|
|||||||
Quotations work here too:
|
Quotations work here too:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ c4 note } { e4 note } { g4 note } 3 cycle
|
( c4 note ) ( e4 note ) ( g4 note ) 3 cycle
|
||||||
sine s .
|
sine s .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -130,20 +130,20 @@ sine s .
|
|||||||
`every` runs a quotation once every n pattern iterations:
|
`every` runs a quotation once every n pattern iterations:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ crash s . } 4 every ;; crash cymbal every 4th iteration
|
( crash s . ) 4 every ;; crash cymbal every 4th iteration
|
||||||
```
|
```
|
||||||
|
|
||||||
`except` is the inverse -- it runs a quotation on all iterations *except* every nth:
|
`except` is the inverse -- it runs a quotation on all iterations *except* every nth:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ 2 distort } 4 except ;; distort on all iterations except every 4th
|
( 2 distort ) 4 except ;; distort on all iterations except every 4th
|
||||||
```
|
```
|
||||||
|
|
||||||
`bjork` and `pbjork` use Bjorklund's algorithm to distribute k hits across n positions as evenly as possible. Classic Euclidean rhythms:
|
`bjork` and `pbjork` use Bjorklund's algorithm to distribute k hits across n positions as evenly as possible. Classic Euclidean rhythms:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ hat s . } 3 8 bjork ;; tresillo: x..x..x. (by step runs)
|
( hat s . ) 3 8 bjork ;; tresillo: x..x..x. (by step runs)
|
||||||
{ hat s . } 5 8 pbjork ;; cinquillo: x.xx.xx. (by pattern iterations)
|
( hat s . ) 5 8 pbjork ;; cinquillo: x.xx.xx. (by pattern iterations)
|
||||||
```
|
```
|
||||||
|
|
||||||
`bjork` counts by step runs (how many times this particular step has played). `pbjork` counts by pattern iterations. Some classic patterns:
|
`bjork` counts by step runs (how many times this particular step has played). `pbjork` counts by pattern iterations. Some classic patterns:
|
||||||
@@ -172,7 +172,7 @@ The real power comes from mixing techniques. A hi-hat pattern with ghost notes:
|
|||||||
|
|
||||||
```forth
|
```forth
|
||||||
hat s
|
hat s
|
||||||
{ 0.3 0.6 rand gain } { 0.8 gain } 2 cycle
|
( 0.3 0.6 rand gain ) ( 0.8 gain ) 2 cycle
|
||||||
.
|
.
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -181,18 +181,18 @@ Full volume on even runs, random quiet on odd runs.
|
|||||||
A bass line that changes every 4 bars:
|
A bass line that changes every 4 bars:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ c2 note } { e2 note } { g2 note } { a2 note } 4 pcycle
|
( c2 note ) ( e2 note ) ( g2 note ) ( a2 note ) 4 pcycle
|
||||||
{ 0.5 decay } often
|
( 0.5 decay ) often
|
||||||
sine s .
|
sine s .
|
||||||
```
|
```
|
||||||
|
|
||||||
Layered percussion with different densities:
|
Layered percussion with different densities:
|
||||||
|
|
||||||
```forth
|
```forth
|
||||||
{ kick s . } always
|
( kick s . ) always
|
||||||
{ snare s . } 2 every
|
( snare s . ) 2 every
|
||||||
{ hat s . } 5 8 bjork
|
( hat s . ) 5 8 bjork
|
||||||
{ rim s . } rarely
|
( rim s . ) rarely
|
||||||
```
|
```
|
||||||
|
|
||||||
A melodic step with weighted note selection and random timbre:
|
A melodic step with weighted note selection and random timbre:
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Reset on some condition:
|
|||||||
|
|
||||||
```forth
|
```forth
|
||||||
@n 1 + !n
|
@n 1 + !n
|
||||||
{ 0 !n } @n 16 > ? ;; reset after 16
|
( 0 !n ) @n 16 > ? ;; reset after 16
|
||||||
```
|
```
|
||||||
|
|
||||||
## When Changes Take Effect
|
## When Changes Take Effect
|
||||||
|
|||||||
36
releases/Cagire-aarch64.app/Contents/Info.plist
Normal file
36
releases/Cagire-aarch64.app/Contents/Info.plist
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Cagire</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>cagire-desktop</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>Cagire.icns</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.sova.cagire</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Cagire</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.0.9</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>20260228.123720</string>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.music</string>
|
||||||
|
<key>LSRequiresCarbon</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright (c) 2025 Raphaël Forment</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
releases/Cagire-aarch64.app/Contents/MacOS/cagire-desktop
Executable file
BIN
releases/Cagire-aarch64.app/Contents/MacOS/cagire-desktop
Executable file
Binary file not shown.
BIN
releases/Cagire-aarch64.app/Contents/Resources/Cagire.icns
Normal file
BIN
releases/Cagire-aarch64.app/Contents/Resources/Cagire.icns
Normal file
Binary file not shown.
BIN
releases/Cagire-aarch64.dmg
Normal file
BIN
releases/Cagire-aarch64.dmg
Normal file
Binary file not shown.
BIN
releases/big-bubo-archive-2.zip
Normal file
BIN
releases/big-bubo-archive-2.zip
Normal file
Binary file not shown.
BIN
releases/cagire-desktop-macos-aarch64
Executable file
BIN
releases/cagire-desktop-macos-aarch64
Executable file
Binary file not shown.
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET="12.0"
|
||||||
|
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
PLUGIN_NAME="cagire-plugins"
|
PLUGIN_NAME="cagire-plugins"
|
||||||
LIB_NAME="cagire_plugins" # cargo converts hyphens to underscores
|
LIB_NAME="cagire_plugins" # cargo converts hyphens to underscores
|
||||||
OUT="target/releases"
|
OUT="releases"
|
||||||
|
|
||||||
PLATFORMS=(
|
PLATFORMS=(
|
||||||
"aarch64-apple-darwin"
|
"aarch64-apple-darwin"
|
||||||
@@ -312,13 +314,16 @@ copy_artifacts() {
|
|||||||
# macOS .app bundle
|
# macOS .app bundle
|
||||||
if [[ "$os" == "macos" ]]; then
|
if [[ "$os" == "macos" ]]; then
|
||||||
local app_src="$rd/bundle/osx/Cagire.app"
|
local app_src="$rd/bundle/osx/Cagire.app"
|
||||||
if [[ -d "$app_src" ]]; then
|
if [[ ! -d "$app_src" ]]; then
|
||||||
local app_dst="$OUT/Cagire-${arch}.app"
|
echo " ERROR: .app bundle not found at $app_src"
|
||||||
rm -rf "$app_dst"
|
echo " Did 'cargo bundle' succeed?"
|
||||||
cp -R "$app_src" "$app_dst"
|
return 1
|
||||||
echo " Cagire.app -> $app_dst"
|
|
||||||
scripts/make-dmg.sh "$app_dst" "$OUT"
|
|
||||||
fi
|
fi
|
||||||
|
local app_dst="$OUT/Cagire-${arch}.app"
|
||||||
|
rm -rf "$app_dst"
|
||||||
|
cp -R "$app_src" "$app_dst"
|
||||||
|
echo " Cagire.app -> $app_dst"
|
||||||
|
scripts/make-dmg.sh "$app_dst" "$OUT"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -187,30 +187,30 @@ fn nor_ff() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ifelse_true() {
|
fn ifelse_true() {
|
||||||
expect_int("{ 42 } { 99 } 1 ifelse", 42);
|
expect_int("( 42 ) ( 99 ) 1 ifelse", 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ifelse_false() {
|
fn ifelse_false() {
|
||||||
expect_int("{ 42 } { 99 } 0 ifelse", 99);
|
expect_int("( 42 ) ( 99 ) 0 ifelse", 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_first() {
|
fn select_first() {
|
||||||
expect_int("{ 10 } { 20 } { 30 } 0 select", 10);
|
expect_int("( 10 ) ( 20 ) ( 30 ) 0 select", 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_second() {
|
fn select_second() {
|
||||||
expect_int("{ 10 } { 20 } { 30 } 1 select", 20);
|
expect_int("( 10 ) ( 20 ) ( 30 ) 1 select", 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_third() {
|
fn select_third() {
|
||||||
expect_int("{ 10 } { 20 } { 30 } 2 select", 30);
|
expect_int("( 10 ) ( 20 ) ( 30 ) 2 select", 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_preserves_stack() {
|
fn select_preserves_stack() {
|
||||||
expect_int("5 { 10 } { 20 } 0 select +", 15);
|
expect_int("5 ( 10 ) ( 20 ) 0 select +", 15);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,14 +59,14 @@ fn iter() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn every_true_on_zero() {
|
fn every_true_on_zero() {
|
||||||
let ctx = ctx_with(|c| c.iter = 0);
|
let ctx = ctx_with(|c| c.iter = 0);
|
||||||
let f = run_ctx("{ 100 } 4 every", &ctx);
|
let f = run_ctx("( 100 ) 4 every", &ctx);
|
||||||
assert_eq!(stack_int(&f), 100);
|
assert_eq!(stack_int(&f), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_true_on_multiple() {
|
fn every_true_on_multiple() {
|
||||||
let ctx = ctx_with(|c| c.iter = 8);
|
let ctx = ctx_with(|c| c.iter = 8);
|
||||||
let f = run_ctx("{ 100 } 4 every", &ctx);
|
let f = run_ctx("( 100 ) 4 every", &ctx);
|
||||||
assert_eq!(stack_int(&f), 100);
|
assert_eq!(stack_int(&f), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,14 +74,14 @@ fn every_true_on_multiple() {
|
|||||||
fn every_false_between() {
|
fn every_false_between() {
|
||||||
for i in 1..4 {
|
for i in 1..4 {
|
||||||
let ctx = ctx_with(|c| c.iter = i);
|
let ctx = ctx_with(|c| c.iter = i);
|
||||||
let f = run_ctx("{ 100 } 4 every", &ctx);
|
let f = run_ctx("( 100 ) 4 every", &ctx);
|
||||||
assert!(f.stack().is_empty(), "iter={} should not execute quotation", i);
|
assert!(f.stack().is_empty(), "iter={} should not execute quotation", i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_zero_count() {
|
fn every_zero_count() {
|
||||||
expect_error("{ 1 } 0 every", "every count must be > 0");
|
expect_error("( 1 ) 0 every", "every count must be > 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -105,7 +105,7 @@ fn bjork_tresillo() {
|
|||||||
// Bresenham(3,8) hits at positions 0, 2, 5
|
// Bresenham(3,8) hits at positions 0, 2, 5
|
||||||
for runs in 0..8 {
|
for runs in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.runs = runs);
|
let ctx = ctx_with(|c| c.runs = runs);
|
||||||
let f = run_ctx("{ 100 } 3 8 bjork", &ctx);
|
let f = run_ctx("( 100 ) 3 8 bjork", &ctx);
|
||||||
let hit = ((runs + 1) * 3) / 8 != (runs * 3) / 8;
|
let hit = ((runs + 1) * 3) / 8 != (runs * 3) / 8;
|
||||||
if hit {
|
if hit {
|
||||||
assert_eq!(stack_int(&f), 100, "runs={} should hit", runs);
|
assert_eq!(stack_int(&f), 100, "runs={} should hit", runs);
|
||||||
@@ -121,7 +121,7 @@ fn bjork_hit_count() {
|
|||||||
let mut hit_count = 0;
|
let mut hit_count = 0;
|
||||||
for runs in 0..8 {
|
for runs in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.runs = runs);
|
let ctx = ctx_with(|c| c.runs = runs);
|
||||||
let f = run_ctx("{ 100 } 3 8 bjork", &ctx);
|
let f = run_ctx("( 100 ) 3 8 bjork", &ctx);
|
||||||
if !f.stack().is_empty() {
|
if !f.stack().is_empty() {
|
||||||
hit_count += 1;
|
hit_count += 1;
|
||||||
}
|
}
|
||||||
@@ -132,20 +132,20 @@ fn bjork_hit_count() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bjork_all_hits() {
|
fn bjork_all_hits() {
|
||||||
let ctx = ctx_with(|c| c.runs = 0);
|
let ctx = ctx_with(|c| c.runs = 0);
|
||||||
let f = run_ctx("{ 100 } 8 8 bjork", &ctx);
|
let f = run_ctx("( 100 ) 8 8 bjork", &ctx);
|
||||||
assert_eq!(stack_int(&f), 100);
|
assert_eq!(stack_int(&f), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bjork_zero_hits() {
|
fn bjork_zero_hits() {
|
||||||
let ctx = ctx_with(|c| c.runs = 0);
|
let ctx = ctx_with(|c| c.runs = 0);
|
||||||
let f = run_ctx("{ 100 } 0 8 bjork", &ctx);
|
let f = run_ctx("( 100 ) 0 8 bjork", &ctx);
|
||||||
assert!(f.stack().is_empty());
|
assert!(f.stack().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bjork_invalid() {
|
fn bjork_invalid() {
|
||||||
expect_error("{ 1 } 3 0 bjork", "bjork");
|
expect_error("( 1 ) 3 0 bjork", "bjork");
|
||||||
}
|
}
|
||||||
|
|
||||||
// pbjork (iter-based)
|
// pbjork (iter-based)
|
||||||
@@ -155,7 +155,7 @@ fn pbjork_cinquillo() {
|
|||||||
let mut hit_count = 0;
|
let mut hit_count = 0;
|
||||||
for iter in 0..8 {
|
for iter in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = run_ctx("{ 100 } 5 8 pbjork", &ctx);
|
let f = run_ctx("( 100 ) 5 8 pbjork", &ctx);
|
||||||
if !f.stack().is_empty() {
|
if !f.stack().is_empty() {
|
||||||
hit_count += 1;
|
hit_count += 1;
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ fn pbjork_cinquillo() {
|
|||||||
fn pbjork_wraps() {
|
fn pbjork_wraps() {
|
||||||
let ctx0 = ctx_with(|c| c.iter = 0);
|
let ctx0 = ctx_with(|c| c.iter = 0);
|
||||||
let ctx8 = ctx_with(|c| c.iter = 8);
|
let ctx8 = ctx_with(|c| c.iter = 8);
|
||||||
let f0 = run_ctx("{ 100 } 3 8 pbjork", &ctx0);
|
let f0 = run_ctx("( 100 ) 3 8 pbjork", &ctx0);
|
||||||
let f8 = run_ctx("{ 100 } 3 8 pbjork", &ctx8);
|
let f8 = run_ctx("( 100 ) 3 8 pbjork", &ctx8);
|
||||||
assert_eq!(f0.stack().is_empty(), f8.stack().is_empty());
|
assert_eq!(f0.stack().is_empty(), f8.stack().is_empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ fn unexpected_semicolon_errors() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_executes_quotation() {
|
fn apply_executes_quotation() {
|
||||||
expect_int("5 { 2 * } apply", 10);
|
expect_int("5 ( 2 * ) apply", 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_with_stack_ops() {
|
fn apply_with_stack_ops() {
|
||||||
expect_int("3 4 { + } apply", 7);
|
expect_int("3 4 ( + ) apply", 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -88,12 +88,12 @@ fn apply_non_quotation_errors() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_nested() {
|
fn apply_nested() {
|
||||||
expect_int("2 { { 3 * } apply } apply", 6);
|
expect_int("2 ( ( 3 * ) apply ) apply", 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn define_word_containing_quotation() {
|
fn define_word_containing_quotation() {
|
||||||
expect_int(": dbl { 2 * } apply ; 7 dbl", 14);
|
expect_int(": dbl ( 2 * ) apply ; 7 dbl", 14);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ fn range_underflow() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_basic() {
|
fn gen_basic() {
|
||||||
expect_stack("{ 42 } 3 gen", &[int(42), int(42), int(42)]);
|
expect_stack("( 42 ) 3 gen", &[int(42), int(42), int(42)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -42,7 +42,7 @@ fn gen_with_computation() {
|
|||||||
// 0 → dup(0,0) 1+(0,1) → pop 1, stack [0]
|
// 0 → dup(0,0) 1+(0,1) → pop 1, stack [0]
|
||||||
// 0 → dup(0,0) 1+(0,1) → pop 1, stack [0]
|
// 0 → dup(0,0) 1+(0,1) → pop 1, stack [0]
|
||||||
// So we get [0, 1, 1, 1] - the 0 stays, we collect three 1s
|
// So we get [0, 1, 1, 1] - the 0 stays, we collect three 1s
|
||||||
expect_stack("0 { dup 1 + } 3 gen", &[int(0), int(1), int(1), int(1)]);
|
expect_stack("0 ( dup 1 + ) 3 gen", &[int(0), int(1), int(1), int(1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -50,12 +50,12 @@ fn gen_chained() {
|
|||||||
// Start with 1, each iteration: dup, multiply by 2
|
// Start with 1, each iteration: dup, multiply by 2
|
||||||
// 1 → dup(1,1) 2*(1,2) → pop 2, stack [1]
|
// 1 → dup(1,1) 2*(1,2) → pop 2, stack [1]
|
||||||
// 1 → dup(1,1) 2*(1,2) → pop 2, stack [1]
|
// 1 → dup(1,1) 2*(1,2) → pop 2, stack [1]
|
||||||
expect_stack("1 { dup 2 * } 3 gen", &[int(1), int(2), int(2), int(2)]);
|
expect_stack("1 ( dup 2 * ) 3 gen", &[int(1), int(2), int(2), int(2)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_zero() {
|
fn gen_zero() {
|
||||||
expect_stack("{ 1 } 0 gen", &[]);
|
expect_stack("( 1 ) 0 gen", &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -65,17 +65,17 @@ fn gen_underflow() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_not_a_number() {
|
fn gen_not_a_number() {
|
||||||
expect_error("{ 1 } gen", "expected number");
|
expect_error("( 1 ) gen", "expected number");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_negative() {
|
fn gen_negative() {
|
||||||
expect_error("{ 1 } -1 gen", "gen count must be >= 0");
|
expect_error("( 1 ) -1 gen", "gen count must be >= 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_empty_quot_error() {
|
fn gen_empty_quot_error() {
|
||||||
expect_error("{ } 3 gen", "quotation must produce");
|
expect_error("( ) 3 gen", "quotation must produce");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -46,20 +46,20 @@ fn pcycle_by_iter() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cycle_with_quotations() {
|
fn cycle_with_quotations() {
|
||||||
let ctx = ctx_with(|c| c.runs = 0);
|
let ctx = ctx_with(|c| c.runs = 0);
|
||||||
let f = run_ctx("5 { dup } { 2 * } 2 cycle", &ctx);
|
let f = run_ctx("5 ( dup ) ( 2 * ) 2 cycle", &ctx);
|
||||||
let stack = f.stack();
|
let stack = f.stack();
|
||||||
assert_eq!(stack.len(), 2);
|
assert_eq!(stack.len(), 2);
|
||||||
assert_eq!(stack_int(&f), 5);
|
assert_eq!(stack_int(&f), 5);
|
||||||
|
|
||||||
let ctx = ctx_with(|c| c.runs = 1);
|
let ctx = ctx_with(|c| c.runs = 1);
|
||||||
let f = run_ctx("5 { dup } { 2 * } 2 cycle", &ctx);
|
let f = run_ctx("5 ( dup ) ( 2 * ) 2 cycle", &ctx);
|
||||||
assert_eq!(stack_int(&f), 10);
|
assert_eq!(stack_int(&f), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cycle_executes_quotation() {
|
fn cycle_executes_quotation() {
|
||||||
let ctx = ctx_with(|c| c.runs = 0);
|
let ctx = ctx_with(|c| c.runs = 0);
|
||||||
let f = run_ctx("10 { 3 + } { 5 + } 2 cycle", &ctx);
|
let f = run_ctx("10 ( 3 + ) ( 5 + ) 2 cycle", &ctx);
|
||||||
assert_eq!(stack_int(&f), 13);
|
assert_eq!(stack_int(&f), 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,11 +108,11 @@ fn bracket_cycle() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bracket_with_quotations() {
|
fn bracket_with_quotations() {
|
||||||
let ctx = ctx_with(|c| c.runs = 0);
|
let ctx = ctx_with(|c| c.runs = 0);
|
||||||
let f = run_ctx("5 [ { 3 + } { 5 + } ] cycle", &ctx);
|
let f = run_ctx("5 [ ( 3 + ) ( 5 + ) ] cycle", &ctx);
|
||||||
assert_eq!(stack_int(&f), 8);
|
assert_eq!(stack_int(&f), 8);
|
||||||
|
|
||||||
let ctx = ctx_with(|c| c.runs = 1);
|
let ctx = ctx_with(|c| c.runs = 1);
|
||||||
let f = run_ctx("5 [ { 3 + } { 5 + } ] cycle", &ctx);
|
let f = run_ctx("5 [ ( 3 + ) ( 5 + ) ] cycle", &ctx);
|
||||||
assert_eq!(stack_int(&f), 10);
|
assert_eq!(stack_int(&f), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ fn index_negative_wraps() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_with_quotation() {
|
fn index_with_quotation() {
|
||||||
expect_int("5 [ { 3 + } { 5 + } ] 0 index", 8);
|
expect_int("5 [ ( 3 + ) ( 5 + ) ] 0 index", 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ use super::harness::*;
|
|||||||
fn quotation_on_stack() {
|
fn quotation_on_stack() {
|
||||||
// Quotation should be pushable to stack
|
// Quotation should be pushable to stack
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let result = f.evaluate("{ 1 2 + }", &default_ctx());
|
let result = f.evaluate("( 1 2 + )", &default_ctx());
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_true_executes() {
|
fn when_true_executes() {
|
||||||
let f = run("{ 42 } 1 ?");
|
let f = run("( 42 ) 1 ?");
|
||||||
assert_eq!(stack_int(&f), 42);
|
assert_eq!(stack_int(&f), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_false_skips() {
|
fn when_false_skips() {
|
||||||
let f = run("99 { 42 } 0 ?");
|
let f = run("99 ( 42 ) 0 ?");
|
||||||
// Stack should still have 99, quotation not executed
|
// Stack should still have 99, quotation not executed
|
||||||
assert_eq!(stack_int(&f), 99);
|
assert_eq!(stack_int(&f), 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_with_arithmetic() {
|
fn when_with_arithmetic() {
|
||||||
let f = run("10 { 5 + } 1 ?");
|
let f = run("10 ( 5 + ) 1 ?");
|
||||||
assert_eq!(stack_int(&f), 15);
|
assert_eq!(stack_int(&f), 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,48 +31,48 @@ fn when_with_arithmetic() {
|
|||||||
fn when_with_every() {
|
fn when_with_every() {
|
||||||
// iter=0, every 2 executes quotation
|
// iter=0, every 2 executes quotation
|
||||||
let ctx = ctx_with(|c| c.iter = 0);
|
let ctx = ctx_with(|c| c.iter = 0);
|
||||||
let f = run_ctx("{ 100 } 2 every", &ctx);
|
let f = run_ctx("( 100 ) 2 every", &ctx);
|
||||||
assert_eq!(stack_int(&f), 100);
|
assert_eq!(stack_int(&f), 100);
|
||||||
|
|
||||||
// iter=1, every 2 skips quotation
|
// iter=1, every 2 skips quotation
|
||||||
let ctx = ctx_with(|c| c.iter = 1);
|
let ctx = ctx_with(|c| c.iter = 1);
|
||||||
let f = run_ctx("50 { 100 } 2 every", &ctx);
|
let f = run_ctx("50 ( 100 ) 2 every", &ctx);
|
||||||
assert_eq!(stack_int(&f), 50); // quotation not executed
|
assert_eq!(stack_int(&f), 50); // quotation not executed
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_with_chance_deterministic() {
|
fn when_with_chance_deterministic() {
|
||||||
// 1.0 chance always executes quotation
|
// 1.0 chance always executes quotation
|
||||||
let f = run("{ 42 } 1.0 chance");
|
let f = run("( 42 ) 1.0 chance");
|
||||||
assert_eq!(stack_int(&f), 42);
|
assert_eq!(stack_int(&f), 42);
|
||||||
|
|
||||||
// 0.0 chance never executes quotation
|
// 0.0 chance never executes quotation
|
||||||
let f = run("99 { 42 } 0.0 chance");
|
let f = run("99 ( 42 ) 0.0 chance");
|
||||||
assert_eq!(stack_int(&f), 99);
|
assert_eq!(stack_int(&f), 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_quotations() {
|
fn nested_quotations() {
|
||||||
let f = run("{ { 42 } 1 ? } 1 ?");
|
let f = run("( ( 42 ) 1 ? ) 1 ?");
|
||||||
assert_eq!(stack_int(&f), 42);
|
assert_eq!(stack_int(&f), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quotation_with_param() {
|
fn quotation_with_param() {
|
||||||
let outputs = expect_outputs(r#""kick" s { 2 distort } 1 ? ."#, 1);
|
let outputs = expect_outputs(r#""kick" s ( 2 distort ) 1 ? ."#, 1);
|
||||||
assert!(outputs[0].contains("distort/2"));
|
assert!(outputs[0].contains("distort/2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quotation_skips_param() {
|
fn quotation_skips_param() {
|
||||||
let outputs = expect_outputs(r#""kick" s { 2 distort } 0 ? ."#, 1);
|
let outputs = expect_outputs(r#""kick" s ( 2 distort ) 0 ? ."#, 1);
|
||||||
assert!(!outputs[0].contains("distort"));
|
assert!(!outputs[0].contains("distort"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quotation_with_emit() {
|
fn quotation_with_emit() {
|
||||||
// When true, . should fire
|
// When true, . should fire
|
||||||
let outputs = expect_outputs(r#""kick" s { . } 1 ?"#, 1);
|
let outputs = expect_outputs(r#""kick" s ( . ) 1 ?"#, 1);
|
||||||
assert!(outputs[0].contains("kick"));
|
assert!(outputs[0].contains("kick"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ fn quotation_skips_emit() {
|
|||||||
// When false, . should not fire
|
// When false, . should not fire
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f
|
let outputs = f
|
||||||
.evaluate(r#""kick" s { . } 0 ?"#, &default_ctx())
|
.evaluate(r#""kick" s ( . ) 0 ?"#, &default_ctx())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// No output since . was skipped and no implicit emit
|
// No output since . was skipped and no implicit emit
|
||||||
assert_eq!(outputs.len(), 0);
|
assert_eq!(outputs.len(), 0);
|
||||||
@@ -94,22 +94,22 @@ fn missing_quotation_error() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_quotation_error() {
|
fn unclosed_quotation_error() {
|
||||||
expect_error("{ 1 2", "missing }");
|
expect_error("( 1 2", "missing )");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unexpected_close_error() {
|
fn unexpected_close_error() {
|
||||||
expect_error("1 2 }", "unexpected }");
|
expect_error("1 2 )", "unexpected )");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_with_quotation_integration() {
|
fn every_with_quotation_integration() {
|
||||||
// { 2 distort } 2 every — on even iterations, distort is applied
|
// ( 2 distort ) 2 every — on even iterations, distort is applied
|
||||||
for iter in 0..4 {
|
for iter in 0..4 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f
|
let outputs = f
|
||||||
.evaluate(r#""kick" s { 2 distort } 2 every ."#, &ctx)
|
.evaluate(r#""kick" s ( 2 distort ) 2 every ."#, &ctx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if iter % 2 == 0 {
|
if iter % 2 == 0 {
|
||||||
assert!(
|
assert!(
|
||||||
@@ -134,7 +134,7 @@ fn bjork_with_sound() {
|
|||||||
let ctx = ctx_with(|c| c.runs = 2); // position 2 is a hit for (3,8)
|
let ctx = ctx_with(|c| c.runs = 2); // position 2 is a hit for (3,8)
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f
|
let outputs = f
|
||||||
.evaluate(r#""kick" s { 2 distort } 3 8 bjork ."#, &ctx)
|
.evaluate(r#""kick" s ( 2 distort ) 3 8 bjork ."#, &ctx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(outputs[0].contains("distort/2"));
|
assert!(outputs[0].contains("distort/2"));
|
||||||
}
|
}
|
||||||
@@ -143,13 +143,13 @@ fn bjork_with_sound() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unless_false_executes() {
|
fn unless_false_executes() {
|
||||||
let f = run("{ 42 } 0 !?");
|
let f = run("( 42 ) 0 !?");
|
||||||
assert_eq!(stack_int(&f), 42);
|
assert_eq!(stack_int(&f), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unless_true_skips() {
|
fn unless_true_skips() {
|
||||||
let f = run("99 { 42 } 1 !?");
|
let f = run("99 ( 42 ) 1 !?");
|
||||||
assert_eq!(stack_int(&f), 99);
|
assert_eq!(stack_int(&f), 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ fn when_and_unless_complementary() {
|
|||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f
|
let outputs = f
|
||||||
.evaluate(
|
.evaluate(
|
||||||
r#""kick" s { 2 distort } iter 2 mod 0 = ? { 4 distort } iter 2 mod 0 = !? ."#,
|
r#""kick" s ( 2 distort ) iter 2 mod 0 = ? ( 4 distort ) iter 2 mod 0 = !? ."#,
|
||||||
&ctx,
|
&ctx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ fn coin_binary() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn chance_zero() {
|
fn chance_zero() {
|
||||||
// 0.0 probability should never execute the quotation
|
// 0.0 probability should never execute the quotation
|
||||||
let f = run("99 { 42 } 0.0 chance");
|
let f = run("99 ( 42 ) 0.0 chance");
|
||||||
assert_eq!(stack_int(&f), 99); // quotation not executed, 99 still on stack
|
assert_eq!(stack_int(&f), 99); // quotation not executed, 99 still on stack
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chance_one() {
|
fn chance_one() {
|
||||||
// 1.0 probability should always execute the quotation
|
// 1.0 probability should always execute the quotation
|
||||||
let f = run("{ 42 } 1.0 chance");
|
let f = run("( 42 ) 1.0 chance");
|
||||||
assert_eq!(stack_int(&f), 42);
|
assert_eq!(stack_int(&f), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ fn wchoose_negative_weight() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn wchoose_quotation() {
|
fn wchoose_quotation() {
|
||||||
let f = forth_seeded(42);
|
let f = forth_seeded(42);
|
||||||
f.evaluate("{ 10 } 0.0 { 20 } 1.0 2 wchoose", &default_ctx())
|
f.evaluate("( 10 ) 0.0 ( 20 ) 1.0 2 wchoose", &default_ctx())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(stack_int(&f), 20);
|
assert_eq!(stack_int(&f), 20);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ fn cycle_picks_by_runs() {
|
|||||||
for runs in 0..4 {
|
for runs in 0..4 {
|
||||||
let ctx = ctx_with(|c| c.runs = runs);
|
let ctx = ctx_with(|c| c.runs = runs);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f.evaluate(r#""kick" s { . } { } 2 cycle"#, &ctx).unwrap();
|
let outputs = f.evaluate(r#""kick" s ( . ) ( ) 2 cycle"#, &ctx).unwrap();
|
||||||
if runs % 2 == 0 {
|
if runs % 2 == 0 {
|
||||||
assert_eq!(outputs.len(), 1, "runs={}: emit should be picked", runs);
|
assert_eq!(outputs.len(), 1, "runs={}: emit should be picked", runs);
|
||||||
} else {
|
} else {
|
||||||
@@ -113,7 +113,7 @@ fn pcycle_picks_by_iter() {
|
|||||||
for iter in 0..4 {
|
for iter in 0..4 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f.evaluate(r#""kick" s { . } { } 2 pcycle"#, &ctx).unwrap();
|
let outputs = f.evaluate(r#""kick" s ( . ) ( ) 2 pcycle"#, &ctx).unwrap();
|
||||||
if iter % 2 == 0 {
|
if iter % 2 == 0 {
|
||||||
assert_eq!(outputs.len(), 1, "iter={}: emit should be picked", iter);
|
assert_eq!(outputs.len(), 1, "iter={}: emit should be picked", iter);
|
||||||
} else {
|
} else {
|
||||||
@@ -128,7 +128,7 @@ fn cycle_with_sounds() {
|
|||||||
let ctx = ctx_with(|c| c.runs = runs);
|
let ctx = ctx_with(|c| c.runs = runs);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f.evaluate(
|
let outputs = f.evaluate(
|
||||||
r#"{ "kick" s . } { "hat" s . } { "snare" s . } 3 cycle"#,
|
r#"( "kick" s . ) ( "hat" s . ) ( "snare" s . ) 3 cycle"#,
|
||||||
&ctx
|
&ctx
|
||||||
).unwrap();
|
).unwrap();
|
||||||
assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs);
|
assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs);
|
||||||
@@ -346,7 +346,7 @@ fn every_offset_fires_at_offset() {
|
|||||||
for iter in 0..8 {
|
for iter in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f.evaluate(r#""kick" s { . } 4 2 every+"#, &ctx).unwrap();
|
let outputs = f.evaluate(r#""kick" s ( . ) 4 2 every+"#, &ctx).unwrap();
|
||||||
if iter % 4 == 2 {
|
if iter % 4 == 2 {
|
||||||
assert_eq!(outputs.len(), 1, "iter={}: should fire", iter);
|
assert_eq!(outputs.len(), 1, "iter={}: should fire", iter);
|
||||||
} else {
|
} else {
|
||||||
@@ -361,7 +361,7 @@ fn every_offset_wraps_large_offset() {
|
|||||||
for iter in 0..8 {
|
for iter in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f.evaluate(r#""kick" s { . } 4 6 every+"#, &ctx).unwrap();
|
let outputs = f.evaluate(r#""kick" s ( . ) 4 6 every+"#, &ctx).unwrap();
|
||||||
if iter % 4 == 2 {
|
if iter % 4 == 2 {
|
||||||
assert_eq!(outputs.len(), 1, "iter={}: should fire (wrapped offset)", iter);
|
assert_eq!(outputs.len(), 1, "iter={}: should fire (wrapped offset)", iter);
|
||||||
} else {
|
} else {
|
||||||
@@ -375,7 +375,7 @@ fn except_offset_inverse() {
|
|||||||
for iter in 0..8 {
|
for iter in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let outputs = f.evaluate(r#""kick" s { . } 4 2 except+"#, &ctx).unwrap();
|
let outputs = f.evaluate(r#""kick" s ( . ) 4 2 except+"#, &ctx).unwrap();
|
||||||
if iter % 4 != 2 {
|
if iter % 4 != 2 {
|
||||||
assert_eq!(outputs.len(), 1, "iter={}: should fire", iter);
|
assert_eq!(outputs.len(), 1, "iter={}: should fire", iter);
|
||||||
} else {
|
} else {
|
||||||
@@ -389,8 +389,8 @@ fn every_offset_zero_is_same_as_every() {
|
|||||||
for iter in 0..8 {
|
for iter in 0..8 {
|
||||||
let ctx = ctx_with(|c| c.iter = iter);
|
let ctx = ctx_with(|c| c.iter = iter);
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let a = f.evaluate(r#""kick" s { . } 3 every"#, &ctx).unwrap();
|
let a = f.evaluate(r#""kick" s ( . ) 3 every"#, &ctx).unwrap();
|
||||||
let b = f.evaluate(r#""kick" s { . } 3 0 every+"#, &ctx).unwrap();
|
let b = f.evaluate(r#""kick" s ( . ) 3 0 every+"#, &ctx).unwrap();
|
||||||
assert_eq!(a.len(), b.len(), "iter={}: every and every+ 0 should match", iter);
|
assert_eq!(a.len(), b.len(), "iter={}: every and every+ 0 should match", iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user