From 9e35ddee6a9251148918d176d28d7b325dd3ff79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Berg?= Date: Wed, 24 Jun 2026 11:38:18 -0700 Subject: [PATCH] Fix scanner missing the `o.` operator fixes #162 --- demo/test-cases.html | 1 + src/compiler/tokenizer/scanners/alpha.js | 4 ++-- src/compiler/tokenizer/scanners/alpha.test.js | 18 ++++++++++++++++++ .../tokenizer/scanners/operator.test.js | 8 ++++++++ test/operators.js | 6 ++++++ test/snapshots/operators.js.md | 10 ++++++++++ test/snapshots/operators.js.snap | Bin 1168 -> 1213 bytes 7 files changed, 45 insertions(+), 2 deletions(-) diff --git a/demo/test-cases.html b/demo/test-cases.html index 4348358..df780a0 100644 --- a/demo/test-cases.html +++ b/demo/test-cases.html @@ -92,6 +92,7 @@

Operators

ı.^\^ x.^ \(n) <<< [] >>> + a o. b \`lim sup`._(n -> oo) diff --git a/src/compiler/tokenizer/scanners/alpha.js b/src/compiler/tokenizer/scanners/alpha.js index 37f561e..d45a60f 100644 --- a/src/compiler/tokenizer/scanners/alpha.js +++ b/src/compiler/tokenizer/scanners/alpha.js @@ -30,8 +30,8 @@ export default function alphaScanner(char, input, { start, grouping }) { [nextChar] = input.slice(i); } - // alpha may contain a period, but not never end with one. - if (value.endsWith(".")) { + // alpha may contain a period, but only known ops can end with one. + if (value.endsWith(".") && !KNOWN_OPS.has(value)) { value = value.slice(0, -1); } diff --git a/src/compiler/tokenizer/scanners/alpha.test.js b/src/compiler/tokenizer/scanners/alpha.test.js index e92485c..1926f44 100644 --- a/src/compiler/tokenizer/scanners/alpha.test.js +++ b/src/compiler/tokenizer/scanners/alpha.test.js @@ -138,6 +138,24 @@ test("can’t end with a period", (t) => { t.is(token?.split, true); }); +test("unless it is a known operator", (t) => { + const token = alpha("o", "o.", { start: 0, grouping: false }); + + t.is(token?.type, "operator"); + t.is(token?.value, "⊙"); + t.is(token?.end, 2); + t.falsy(token?.split); +}); + +test("known operator ends in period cannot be followed by an alphanum", (t) => { + const token = alpha("o", "o.o", { start: 0, grouping: false }); + + t.is(token?.type, "ident"); + t.is(token?.value, "o"); + t.is(token?.end, 1); + t.is(token?.split, true); +}); + test("known prefix", (t) => { const token = alpha("h", "hat a", { start: 0, grouping: false }); diff --git a/src/compiler/tokenizer/scanners/operator.test.js b/src/compiler/tokenizer/scanners/operator.test.js index acb37cf..b67e08a 100644 --- a/src/compiler/tokenizer/scanners/operator.test.js +++ b/src/compiler/tokenizer/scanners/operator.test.js @@ -30,6 +30,14 @@ test("known operator", (t) => { t.is(token?.end, 3); }); +test("known operator that ends with a period", (t) => { + const token = operator("o.", " o. b", { start: 1, grouping: false }); + + t.is(token?.type, "operator"); + t.is(token?.value, "⊙"); + t.is(token?.end, 3); +}); + test("emits the longest possible known operator", (t) => { const token = operator("^", "^^^ 1", { start: 0, grouping: false }); diff --git a/test/operators.js b/test/operators.js index 39b9996..7bc7044 100644 --- a/test/operators.js +++ b/test/operators.js @@ -33,6 +33,12 @@ test("i hat", (t) => { t.snapshot(render("ı.^\\^")); }); +test("circumpunct", (t) => { + t.is(render("o."), ""); + t.snapshot(render("a o. b")); + t.snapshot(render("a o.b")); +}); + test("Norm of two parallel lines", (t) => { t.snapshot(render("||a || b||")); }); diff --git a/test/snapshots/operators.js.md b/test/snapshots/operators.js.md index 271c68e..f7440dd 100644 --- a/test/snapshots/operators.js.md +++ b/test/snapshots/operators.js.md @@ -36,6 +36,16 @@ Generated by [AVA](https://avajs.dev). 'ı^' +## circumpunct + +> Snapshot 1 + + 'ab' + +> Snapshot 2 + + 'ao.b' + ## Norm of two parallel lines > Snapshot 1 diff --git a/test/snapshots/operators.js.snap b/test/snapshots/operators.js.snap index 63dab0ccc8ca3a5c30729b97263577bca0120a63..1858c05e94c8d7e607cf65efbc212e7a49a41883 100644 GIT binary patch literal 1213 zcmV;u1VZ~kRzVPLri9cZqf(~PtvU-z zU=$b8UmuGI00000000BcSj}%7H5A|Nhd6UWg)B#J|9O4qUiI9D3k{=EL@QW;UHo zQ^+1x&ieWN-ut}|KWD4obD1>=KK%*{B3!)MKF_#?fy?Mefccms!8lkU!U0QN=63<2 zHdyL*LGnkp`#uRM@&kv7;NvaZMgqO}`oYEvjrh|%+<3A1YR5yufgO*t71;3@oa(ea z20I=BZP;n2KerQ^?EGot{Be1n!9HW4hj@0L!EU-T4j(rTAJK=W=BD%F(%t!s)E$C; z;xnQ-j~z_G67V^;u#E$NfaM^LEP=W9_(lf$iUInP23@LxJb5t#34m~r02T{f8}x7t zP7{7DmjxL3h+`U*(asoXa~dtW^Nr#ry1%8@cfPH#0~6pNQD6Ns=5f^EZ$EU}9!V5` zAl0UuhWdd;xD|SSNUa>m%?#vC1LULxArL9$(cOnKQ(!Os)rj;ZL&e#$iZtN}WJ={s zSz#{yWPDz}z_WagGgKN~S$Yzi9MjRRZ=ovXTRH>46HR#|l-P9x)gr+_`)FIUc+oV1okC9a1*5qt=SgfY6+w3KN>VJ z(8o016FkH%eMRf0URJj#`RR?xe$u2;jl;s~&lb#?Lcywq@Z zLfyTIQlp}~Id5}h6HLn!$(4gmBRl>+kQ-%3w+r<4Cy9kl=m5tPoM2pvzNTCoK2xgH zup|98aw=b{FdE%d#`H~No+7+(#n^0H2SWP$z%HdVxjNL_GdhYe|fCrZPgaHH`g~xZ$6Fxs0^Vu bZ>!|`^?{PB1^FodAFO`?gmXDibrk>rLefc; literal 1168 zcmV;B1aJF6RzVoh z1*^Rt$bR&C?~s_HDE66%Ki+j+B+%P09&J6*Nhbweb4*bO;bhux6Dxvmv5 z*bND=VAsn3wlbMQ|D2(JO6oJ{F$QeJ3w;KA`N~i}Whg(bmFE`c%i=QJrHeEe056Lf z(Ne@dreGCB96Q*>F+jla5l4=|Tt|Gh0DaZ~eM*C_j)Q#RVgV8Z;UfVYmIN-aaSBc| zew~2D7(|F;8rRXz8)%CfZFJ*X#m(sUu3q2xZj2w803V6w=GQP!PYwS5W7i5vX82uc zHs3VEeCBn=L%=*B)>#@03IZH}fKWUprqBR=ubMNXE4O9VK$OESA7?q%nV1iJ2dKG9h-{9L8m z-p#=R07xRy)@l76G%SK|`Ua;d`6@FV4nXFBr$=?z2Ls$%?4+)Ot3=$IHBLM9r@mWt$qOK1=< z4k8u|C<_S+>eX`W3!_?Y9y5EXoI2l_Fm)c}9Z)NShUGb9tI{L&fzLIK{FpI|;ApsA z1(>n9$apLF(18t0`?x98pgwV!EvEa&L{{zP@WA(&L8g3(G|bn2Gr}4$u+M8+IdC}0 zH@Zso*~V4`XZ@-3;Blje{i{Y=`LZ4jzprHSgucI0>HD(%8PDNg?