Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
rosettacode\.org
stackoverflow\.com
23 changes: 23 additions & 0 deletions bin/.test-in-docker
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ for slug in $all_slugs; do
local_exit_code=1;
fi

# Approach docs: by convention, the first ```factor block in each
# approach's content.md is a complete solution, so it must also pass.
for content in "/exercises/practice/${slug}"/.approaches/*/content.md; do
[ -e "${content}" ] || continue
approach=$(basename "$(dirname "${content}")")
awk '/^```factor$/ && !found { in_block = 1; found = 1; next }
in_block && /^```$/ { exit }
in_block { print }' "${content}" > "/tmp/solution/${slug}/${slug}.factor"
bin/run.sh "${slug}" /tmp/solution /tmp/solution > /dev/null
approach_status=$(jq -r '.status' /tmp/solution/results.json)

if [ "$approach_status" != "pass" ]; then
msg=$(jq -r '
if .message then .message
else (.tests // [] | map(select(.status != "pass")
| " \(.name): \(.message // "no message")")
| join("\n"))
end' /tmp/solution/results.json)
errors="${errors}\n\nApproach ${approach} solution is incorrect:\n${msg}"
local_exit_code=1;
fi
done

if [ $local_exit_code = 0 ]; then
echo -e "${slug}: \e[32mPASSED\e[0m"
else
Expand Down
21 changes: 21 additions & 0 deletions bin/verify-exercises-in-docker
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ run_tests() {
jq -e '.status == "pass"' "${dir}/results.json" >/dev/null 2>&1
}

first_factor_block() {
awk '/^```factor$/ && !found { in_block = 1; found = 1; next }
in_block && /^```$/ { exit }
in_block { print }' "${1}"
}

verify_approaches() {
local slug="${1}" dir="${2}"
local content approach solution
solution="$(jq -r '.files.solution[0]' "${dir}/.meta/config.json")"
# Approach docs: by convention, the first ```factor block in each
# approach's content.md is a complete solution, so it must also pass.
for content in "${dir}"/.approaches/*/content.md; do
approach="$(basename "$(dirname "${content}")")"
echo "Verifying ${slug} approach: ${approach}..."
first_factor_block "${content}" > "${dir}/${solution}"
run_tests "${slug}" "${dir}" || { cat "${dir}/results.json"; return 1; }
done
}

verify_exercise() {
local dir slug tmpdir
dir="${1%/}"
Expand All @@ -65,6 +85,7 @@ verify_exercise() {
cp -r "${dir}/." "${tmpdir}" || exit
copy_example_or_exemplar_to_solution "${tmpdir}"
run_tests "${slug}" "${tmpdir}" || { cat "${tmpdir}/results.json"; exit 1; }
verify_approaches "${slug}" "${tmpdir}"
)
}

Expand Down
21 changes: 21 additions & 0 deletions exercises/practice/complex-numbers/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"introduction": {
"authors": ["keiravillekode"]
},
"approaches": [
{
"uuid": "8d7668a3-15b7-4f61-acea-759bd30f2da6",
"slug": "postfix-locals",
"title": "Postfix arithmetic with locals",
"blurb": "Compute each formula in Factor's native postfix notation, with named locals.",
"authors": ["keiravillekode"]
},
{
"uuid": "a8dfdbf7-69b6-42cf-a958-6ac66acf9405",
"slug": "infix",
"title": "Infix arithmetic",
"blurb": "Write each formula in conventional mathematical notation with the infix vocabulary.",
"authors": ["keiravillekode"]
}
]
}
99 changes: 99 additions & 0 deletions exercises/practice/complex-numbers/.approaches/infix/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Infix arithmetic

```factor
USING: accessors arrays infix kernel locals math math.functions
sequences ;
IN: complex-numbers

TUPLE: cmplx real imaginary ;

: <cmplx> ( real imag -- cmplx ) cmplx boa ;

: >cmplx ( pair -- cmplx ) first2 <cmplx> ;

: cmplx>pair ( cmplx -- pair )
[ real>> ] [ imaginary>> ] bi 2array ;

: parts ( z -- re im ) [ real>> ] [ imaginary>> ] bi ;

:: c+ ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix a+c infix] [infix b+d infix] <cmplx> ;

:: c- ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix a-c infix] [infix b-d infix] <cmplx> ;

:: c* ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix a*c - b*d infix] [infix a*d + b*c infix] <cmplx> ;

:: c/ ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix (a*c + b*d) / (c*c + d*d) infix]
[infix (b*c - a*d) / (c*c + d*d) infix] <cmplx> ;

:: c-abs ( z -- |z| )
z parts :> ( a b )
[infix sqrt(a*a + b*b) infix] ;

:: c-conj ( z -- z* )
z parts :> ( a b )
a [infix -b infix] <cmplx> ;

:: c-exp ( z -- e^z )
z parts :> ( a b )
a e^ :> ea
[infix ea*cos(b) infix] [infix ea*sin(b) infix] <cmplx> ;
```

## Notation as a library

Factor's postfix notation is a poor match for formulas like
`(ac + bd) / (c² + d²)` — precisely the place where stack shuffling hurts
most.
The [`infix`][infix] vocabulary fixes that with a pair of parsing words:
everything between [`[infix`][infix-bracket] and `infix]` is parsed as a
conventional mathematical expression and compiled into the surrounding
word.
Inside an expression you get the usual operators `+ - * / ^` with their
familiar precedence, parentheses, unary minus (`-b` in `c-conj`), and
function-call syntax — `sqrt(a*a + b*b)`, `cos(b)` — which invokes the
Factor word of that name.

The operands are the [`::`][double-colon] locals in scope.
Each operation therefore starts by unpacking both tuples: `parts` turns one
complex number into its two scalar components, `[ parts ] bi@` does it for
both, and `:> ( a b c d )` binds all four values in one
[multiple-binding][bind-local] — `x = a + bi`, `y = c + di`.
After that, every formula reads exactly as the maths textbook writes it,
and the two results feed `<cmplx>` as usual.

## Limits of the notation

Infix expressions work on scalars, not tuples, which is why the unpacking
step exists at all — there is no `x.real` syntax inside `[infix`.
Word names containing operator characters are also out of reach: `e^`
cannot be called inside an expression, so `c-exp` computes `a e^` in
postfix, binds it to `ea`, and only then switches to infix for
`ea*cos(b)` and `ea*sin(b)`.

For words whose inputs are already scalars, the vocabulary also offers
[`INFIX::`][INFIX], which defines an entire word from one infix
expression:

```factor
INFIX:: hypot ( a b -- h ) sqrt(a*a + b*b) ;
```

The tuple-unpacking in this exercise keeps `[infix ... infix]` the better
fit here.

Like every vocabulary in this approach, `infix` ships with Factor and is
available on the Exercism test runner.

[infix]: https://docs.factorcode.org/content/vocab-infix.html
[infix-bracket]: https://docs.factorcode.org/content/word-%5Binfix%2Cinfix.html
[INFIX]: https://docs.factorcode.org/content/word-INFIX__colon____colon__%2Cinfix.html
[double-colon]: https://docs.factorcode.org/content/word-__colon____colon__,locals.html
[bind-local]: https://docs.factorcode.org/content/word-__colon____gt__%2Clocals.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:: c* ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix a*c - b*d infix] [infix a*d + b*c infix] <cmplx> ;

:: c/ ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix (a*c + b*d) / (c*c + d*d) infix]
[infix (b*c - a*d) / (c*c + d*d) infix] <cmplx> ;
56 changes: 56 additions & 0 deletions exercises/practice/complex-numbers/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Introduction

Complex Numbers is pure arithmetic: a `cmplx` tuple holds a real and an
imaginary part, and every operation unpacks the parts, applies a textbook
formula, and packs the result back into a tuple.
The scaffolding — the tuple, `<cmplx>`, `>cmplx`, `cmplx>pair` — is the same
in every solution.

What varies is how the formulas themselves are written.
Multiplication and division are where it shows: four operands flowing through
a formula like `(a*c + b*d) / (c*c + d*d)` are genuinely awkward to juggle on
the stack, so both approaches below reach for named values — they differ in
the notation the formula is then written in.

## Approach: postfix arithmetic with locals

```factor
:: c* ( a b -- c )
a real>> b real>> * a imaginary>> b imaginary>> * -
a real>> b imaginary>> * a imaginary>> b real>> * +
<cmplx> ;
```

Factor's native notation: name the operands with `::` locals and write each
formula in postfix, operators after their operands.
[Read more about the postfix approach][postfix-locals].

## Approach: infix arithmetic

```factor
:: c* ( x y -- z )
x y [ parts ] bi@ :> ( a b c d )
[infix a*c - b*d infix] [infix a*d + b*c infix] <cmplx> ;
```

The `infix` vocabulary embeds ordinary mathematical notation in a Factor
word: everything between `[infix` and `infix]` is parsed as a conventional
expression over the locals in scope.
[Read more about the infix approach][infix].

## Which approach to use?

Postfix with locals is idiomatic Factor and needs no extra vocabulary; once
the operands are named, the RPN formulas are unambiguous, if unfamiliar to
newcomers.

The infix version matches how the mathematics is written on paper — the
division formula reads exactly like the textbook — at the cost of pulling in
a syntax extension and unpacking the tuples into scalars first.
It is also a fine showcase of Factor's parsing words: notation itself is
library code.
The `infix` vocabulary ships with Factor and is available on the Exercism
test runner.

[postfix-locals]: https://exercism.org/tracks/factor/exercises/complex-numbers/approaches/postfix-locals
[infix]: https://exercism.org/tracks/factor/exercises/complex-numbers/approaches/infix
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Postfix arithmetic with locals

```factor
USING: accessors arrays kernel locals math math.functions
sequences ;
IN: complex-numbers

TUPLE: cmplx real imaginary ;

: <cmplx> ( real imag -- cmplx ) cmplx boa ;

: >cmplx ( pair -- cmplx ) first2 <cmplx> ;

: cmplx>pair ( cmplx -- pair )
[ real>> ] [ imaginary>> ] bi 2array ;

:: c+ ( a b -- c )
a real>> b real>> +
a imaginary>> b imaginary>> +
<cmplx> ;

:: c- ( a b -- c )
a real>> b real>> -
a imaginary>> b imaginary>> -
<cmplx> ;

:: c* ( a b -- c )
a real>> b real>> * a imaginary>> b imaginary>> * -
a real>> b imaginary>> * a imaginary>> b real>> * +
<cmplx> ;

:: c/ ( a b -- c )
b real>> sq b imaginary>> sq + :> denom
a real>> b real>> * a imaginary>> b imaginary>> * + denom /
a imaginary>> b real>> * a real>> b imaginary>> * - denom /
<cmplx> ;

: c-abs ( a -- |a| )
[ real>> sq ] [ imaginary>> sq ] bi + sqrt ;

: c-conj ( a -- a* )
[ real>> ] [ imaginary>> neg ] bi <cmplx> ;

:: c-exp ( z -- e^z )
z real>> e^ :> ea
z imaginary>> :> b
ea b cos *
ea b sin *
<cmplx> ;
```

## Representation

[`TUPLE:`][tuple] declares a class with `real` and `imaginary` slots, and
[`boa`][boa] ("by order of arguments") fills them from the stack.
`>cmplx` and `cmplx>pair` convert between the tuple and the two-element
arrays the tests use.

## Named operands, postfix formulas

The binary operations take two complex numbers — four scalar operands once
unpacked — and that is more than comfortably fits Factor's stack-shuffling
words.
Defining the words with [`::`][double-colon] names the inputs, so each
formula can mention `a real>>`, `b imaginary>>` and so on directly, in any
order, as many times as needed.

Each formula is then ordinary postfix Factor.
Multiplication, `(a + bi)(c + di) = (ac − bd) + (ad + bc)i`, becomes two
lines that each compute one part, leaving both on the stack for `<cmplx>`:

```factor
a real>> b real>> * a imaginary>> b imaginary>> * -
a real>> b imaginary>> * a imaginary>> b real>> * +
```

Division needs the shared denominator `c² + d²` twice, so it is computed
once and bound to a local with [`:>`][bind-local] before the two numerator
lines divide by it.

## Where locals are not needed

Unary operations touch only one number, and a [`bi`][bi] over two accessor
quotations handles them without any locals: `c-abs` squares both parts, sums
and takes the [`sqrt`][sqrt]; `c-conj` negates only the imaginary part.

`c-exp` uses Euler's formula `e^(a+bi) = e^a·(cos b + i·sin b)`: `e^` of the
real part is bound once, then multiplied by `cos` and `sin` of the imaginary
part.

[tuple]: https://docs.factorcode.org/content/word-TUPLE__colon__,syntax.html
[boa]: https://docs.factorcode.org/content/word-boa,classes.tuple.html
[double-colon]: https://docs.factorcode.org/content/word-__colon____colon__,locals.html
[bind-local]: https://docs.factorcode.org/content/word-__colon____gt__%2Clocals.html
[bi]: https://docs.factorcode.org/content/word-bi,kernel.html
[sqrt]: https://docs.factorcode.org/content/word-sqrt,math.functions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:: c* ( a b -- c )
a real>> b real>> * a imaginary>> b imaginary>> * -
a real>> b imaginary>> * a imaginary>> b real>> * +
<cmplx> ;

:: c-exp ( z -- e^z )
z real>> e^ :> ea
z imaginary>> :> b ea b cos * ea b sin * <cmplx> ;
Loading
Loading