m -↩ 3 16 | (d-o) +´ ⌊ off +⌾(¯1⊸⊑) dur × (100 DivMod y-f) ∾ ⟨m+12×f⟩ 17 | } 18 | To ⇐ { 𝕊 t: 19 | c‿y‿m ← dur { d‿m ← 𝕨 DivMod 𝕩+t ⋄ t↩⌊m ⋄ d }¨ off 20 | m -↩ 12×10≤m 21 | ⟨(100×c)+y+m<0, 3+m, 1+t⟩ 22 | } 23 | } 24 | 25 | timestamp ← { 26 | d ← 24 × ×˜ m←60 27 | From ⇐ { 28 | (d × date.From 3↑𝕩) + m⊸×⊸+˜´⌽3↓𝕩 29 | } 30 | To ⇐ { 31 | day‿sec ← d DivMod 𝕩 32 | (date.To day) ∾ m|⌊∘÷⟜m⍟(⌽↕3)sec 33 | } 34 | } 35 | 36 | ToTimestamp‿FromTimestamp ← timestamp.To‿timestamp.From 37 | -------------------------------------------------------------------------------- /Beacon/Utilities/stdlib/hashmap.bqn: -------------------------------------------------------------------------------- 1 | # keys HashMap vals: return a mutable hash map h implementing 2 | # h.Count any: return number of keys defined by h 3 | # h.Has key: return 1 if key is present and 0 otherwise 4 | # [default] h.Get key: return corresponding value, default if not found 5 | # key h.Set val: set value (may overwrite); return h 6 | # h.Delete key: remove corresponding value; return h 7 | 8 | # 𝕗_hash is a function returning index in (2⋆n)-length table 9 | ⟨_hash⟩ ← { 10 | base ← 2 ⋆ b←32 11 | _hash ⇐ { bits _𝕣: 12 | ! 1≤bits 13 | d‿m←b(⌈∘÷˜⋈|)bits 14 | mod ← (0⊸× 1+⊢ 29 | table ↩ hlen ⥊ ¯1 30 | } 31 | ST ← {table 𝕨⌾(𝕩⊸⊑)↩⋄@} 32 | Count ⇐ {𝕊:len} 33 | 34 | # Lookup and modify 35 | Find ← { 𝕊 key: 36 | T ← ("Not found!"!¯1⊸≠)⊸⊢ ⊑⟜table 37 | table ⊑˜ Next•_while_(key≢keys⊑˜T) Ind key 38 | } 39 | Get ⇐ { 40 | 𝕊 key: vals ⊑˜ Find key ; 41 | d 𝕊 key: Next•_while_(¯1⊸≠◶⟨0,⊢{d↩𝕨⋄𝕩}⍟(¬⊢)key≢⊑⟜keys⟩⊑⟜table) Ind key ⋄ d 42 | } 43 | tombstone ← {⇐} 44 | Delete ⇐ { 𝕊 key: 45 | keys tombstone⌾((Find key)⊸⊑)↩ 46 | DecLen 1 47 | self 48 | } 49 | Has ⇐ { 𝕊 key: 50 | h←1 ⋄ Next•_while_(¯1⊸≠◶⟨{𝕊:h↩0},key≢⊑⟜keys⟩⊑⟜table) Ind key ⋄ h 51 | } 52 | DefKey ← { 𝕊 key: 53 | Next•_while_(¯1⊸≠◶⟨0,"Duplicate key"!key≢⊑⟜keys⟩⊑⟜table) Ind key 54 | } 55 | Set ⇐ { key 𝕊 val: 56 | New ← {len ST 𝕨 ⋄ keys∾↩ ‿<){ 16 | ! 2==𝕩 17 | ! ∧´⥊ (𝕩=0) ≥ 𝔽⌜´↕¨≢𝕩 18 | } 19 | Determinant ⇐ ×´Di 20 | # Inverse by recursive blocking 21 | p‿c ← {𝕏˜}¨⍟l ⋈‿≍ 22 | Inverse ⇐ { 23 | 1≥≠𝕩 ? ÷𝕩 ; 24 | l ← ≠𝕩 25 | m ← ⌈l÷2 26 | ai‿di ← Inverse¨ m‿m(↑P↓)𝕩 27 | b ← (m P m-l)↑𝕩 28 | x ← - ai MP b MP di 29 | z ← 0 ⥊˜ (l-m) P m 30 | ∾ (ai P x) C (z P di) 31 | } 32 | # Forward or backward substitution 33 | s←¯1⋆¬l ⋄ j←l-1 ⋄ rev ← l⊑{𝔽⌾⌽}‿{𝔽} 34 | Solve ⇐ { t 𝕊 b: 35 | ! t=○≠b 36 | >{ 37 | new←b÷○(j⊸⊏)𝕩 38 | b↩b-⟜(×⌜⟜new)○(s⊸↓)𝕩 39 | new 40 | }¨∘((s×↕∘≠)↓¨⊢)_rev <˘⍉t 41 | } 42 | } 43 | uTri‿lTri ← LUTri¨ ↕2 44 | 45 | # LU decomposition with pivoting 46 | lup ← { 47 | Compose ⇐ { 𝕊 p‿l‿u: p⊸⊏˘ l MP u } 48 | Verify ⇐ { 𝕊 p‿l‿u: 49 | ! ≤´ m‿n ← ≢u 50 | ! n‿n ≡ ≢l 51 | ! (∧p) ≡ ↕n 52 | uTri.Verify u 53 | lTri.Verify l 54 | ! ∧´ 1 = Di l 55 | } 56 | Inverse ⇐ { 𝕊 p‿l‿u: (⍋p) ⊏ (uTri.Inverse u) MP lTri.Inverse l } 57 | Solve ⇐ { p‿l‿u 𝕊 𝕩: (⍋p) ⊏ u uTri.Solve l lTri.Solve 𝕩 } 58 | Determinant ⇐ { 𝕊 p‿l‿u: 59 | s ← ¯1⋆+´∾(≠↑↑)⊸>p # Sign of permutation 60 | s × uTri.Determinant u 61 | } 62 | 63 | # Recursive method 64 | Decompose ⇐ (0<≠)◶{ ⟨↕¯1⊑≢𝕩 , 0‿0⥊0 , 𝕩⟩ }‿{ LU: 65 | 1 iter+↩1 ? 71 | ¯∞ < ⊑1⊑mins ? 72 | bx ← bounds - ⊑⊑mins 73 | (2×tol) < -⊸⌈´bx ? 74 | 75 | # Compute sign s, distance d, saved distance e 76 | s←@ ⋄ s‿d‿e ↩ { 77 | tol 2×a ? # Movement needs to decrease enough 83 | tol < -⊸⌊´ bx-dn ? # Stay away from bounds 84 | ⟨×dn,a,d⟩ 85 | ; 86 | # Non-interpolation method: golden split of smaller half 87 | a ← | b ← -⊸<´⊸⊑ bx 88 | ⟨×b,phi2×a,a⟩ 89 | } 90 | 91 | x ← ⊑⊑mins 92 | uf ← F u ← x + s×tol⌈d # Move at least tol from x 93 | 94 | i ← 0 < p ← ⊑ (1⊑mins) ⍋ uf # Position of u 95 | bounds ↩ (i⊑x‿u)˙⌾((i=0 iter+↩1 ? 128 | fi ← fp 129 | 130 | # Minimize in each direction successively, and record change in f 131 | df ← { f0←fp ⋄ p‿fp↩Min p‿𝕩 ⋄ f0-fp }¨ dp 132 | 133 | # Relative tolerance 134 | (2×|fi-fp) > tol × fi +○| fp ? 135 | 136 | dt ← p - pt 137 | fe ← Fn pe ← p + dt # Extrapolate 138 | pt ↩ p # Starting point for next dt 139 | 140 | # Now fi → fp → fe are successive values separated by dt 141 | # Consider adding dt to the direction set 142 | # Replace the direction of largest increase to maintain independence 143 | { fe < fi ? 144 | mdf ← ⌈´df # Largest decrease 145 | d2f ← fi+fe - 2×fp # Approximate second derivative wrt dt 146 | (2×d2f × ×˜ (fp+mdf)-fi) < mdf × ×˜ fe-fi ? 147 | p‿fp ↩ Min p‿dt 148 | dp dt⌾((⊑df⊐mdf)⊸⊑)↩ 149 | ;@} 150 | 1;0 151 | }@ 152 | 153 | p‿fp 154 | } 155 | 156 | _powell ← { 157 | tol ← 𝕨⊣1e¯10 158 | step ← ÷4 159 | ltol ← 1e¯4 160 | Powell_sub ⟨𝔽, 𝔽 _linmin {p‿d:𝕩⋄⟨p,step×d,ltol⟩}, 𝕩, tol, 50×≠𝕩⟩ 161 | } 162 | -------------------------------------------------------------------------------- /Beacon/Utilities/stdlib/perlin.bqn: -------------------------------------------------------------------------------- 1 | # Perlin noise generators in 1 and n dimensions 2 | 3 | S ← ט × 3-2⊸× 4 | 5 | Noise1 ← { ⟨bk,g1p⟩𝕊𝕩: 6 | (¬⊸≍ S 1|𝕩) +˝∘× g1p ⊏˜ bk | 0‿1+⌜⌊𝕩 7 | } 8 | 9 | # n-dimensional noise, where n is ≠𝕩 10 | NoiseN ← { ⟨bk,p,g⟩𝕊𝕩: 11 | f‿m ← (⌊⋈1⊸|) 𝕩 ⋄ d←G n←≠𝕩 12 | b ← +⌜⟜(⊏⟜p¨)˝ bk|f+⌜0‿1 13 | a ← (↕n⥊2) m⊸-⊸(+´×)⟜(<⊏¨d˙)¨ b 14 | <⁼ a ¬⊸⋈⊸(+˝×)´ ⌽ S m 15 | } 16 | 17 | # Generate random seeding values 18 | MakeData ← {𝕤 19 | bk ⇐ 16⋆2 20 | Sh ← (2×1+bk)⊸⥊ 21 | Rand ← -⟜¬ •rand.Range⟜0 22 | p ⇐ Sh •rand.Deal bk 23 | gc ← ⟨⟩‿⟨Sh Rand bk⟩ 24 | g1p ⇐ p⊏⊑1⊑gc 25 | GG ← (Sh¨ <˘÷⎉1·<+˝⌾(ט)) ·Rand ⋈⟜bk 26 | G ⇐ { 27 | (≠gc) {gc ∾↩ GG¨ 𝕨+↕𝕩¬𝕨}⍟≤ 𝕩 28 | 𝕩⊑gc 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Beacon/Utilities/stdlib/polynomial.bqn: -------------------------------------------------------------------------------- 1 | ⟨ 2 | Trim, Add, Sub, Mul 3 | Derivative, Integral 4 | Eval, _fn 5 | LaguerreRoots, AberthRoots, WeierstrassRoots 6 | ⟩⇐ 7 | 8 | # Arithmetic for real polynomials 9 | Trim ← (∨`⌾⌽0⊸≠)⊸/ 10 | Add ← +´ ((⌈´≠¨)↑¨⊢)⊘(⌈○≠↑¨⋈) # Like +´⊘+ 11 | Sub ← -´ ⌈○≠ ↑¨ ⋈ 12 | Mul ← +´¨ +⌜○(↕≠) ⊔ ×⌜ 13 | 14 | Derivative ← 1 ↓ ↕∘≠⊸× 15 | Integral ← {𝕊:(<𝕨⊣0)∾𝕩÷1+↕≠𝕩; 𝕊⁼:Derivative𝕩} 16 | 17 | _while_ ← {𝔽⍟𝔾∘𝔽_𝕣_𝔾∘𝔽⍟𝔾𝕩} 18 | 19 | complex ← { 20 | M ⇐ -´∘×⋈+´∘×⟜⌽ 21 | D ⇐ (-⌾(1⊸⊑) ÷ +˝∘(ט)){𝔽⊘(M⟜𝔽)} 22 | A2⇐ +´∘ט 23 | A ⇐ √A2 24 | Sqrt ⇐ { 0a‿b←𝕩 ⋄ -⍟t∘⌽⍟s (÷⟜2⋈b⊸÷) √2×m+|a ; 0‿0 } 25 | _poly ⇐ {+⟜(𝕩⊸M)´𝕗} 26 | _polyd_ ⇐ {(𝕘‿2⥊0) (»+𝕩⊸M˘∘⊢)´ 𝕗} 27 | _polyd_ek_ ⇐ { 28 | ek←0 ⋄ ax←A𝕩 29 | v ← (𝕘‿2⥊0) {ek↩(A⊏𝕩)+ax×ek⋄𝕩}∘(»+𝕩⊸M˘∘⊢)´ 𝕗 30 | v‿ek 31 | } 32 | } 33 | CPoly ← ⋈⟜0⍟(0==)¨ 34 | 35 | # Evaluate polynomial 36 | _fn ← {(CPoly𝕗)complex._poly} 37 | Eval ← {𝕨_fn 𝕩} 38 | 39 | # Analytical solutions to low-order polynomials 40 | exact ← { 41 | M‿D‿Sqrt ← complex 42 | Low ← !∘"Input must have degree at least 1" 43 | Lin ← -∘D´⊢ 44 | Quad ← { 𝕨 𝕊 c‿b‿a: 45 | b ÷↩ ¯2 46 | 0‿0⊸≢◶⟨⋈˜,c⊸D⋈D⟜a⟩ b(0≤+´∘×)◶⟨+,-⟩ Sqrt (M˜b) - a M c 47 | } 48 | _with ⇐ {(4⌊≠∘⊢)◶Low‿Low‿Lin‿Quad‿𝔽} 49 | } 50 | 51 | # Return all complex roots of little-endian polynomial 𝕩, using 52 | # simultaneous root-finding methods 53 | # 𝕨 gives the number of iterations 54 | # Result is a list of real‿imag pairs 55 | _allRoots_ ← { GetStep _𝕣_ deriv: 56 | M‿D‿A2‿_polyd_ek_ ← complex 57 | max_iter ← 100 58 | { 59 | eps ← 𝕨⊣1e¯16 60 | step ← GetStep n ← 1-˜≠𝕩 61 | Poly ← (M¨⟜(D¨¯1⊸⊏)𝕩) _polyd_ek_ deriv 62 | r ← <˘⍉> 1‿0 M⍟(↕n)˜ 4‿9÷10 # Initial roots: 0.4i0.9⋆↕n 63 | i ← 0 64 | ⊢_while_ {𝕤 65 | p‿ek ← Poly r 66 | ∨´ (eps × ×˜ek) ≤ A2 ⊏p ? 67 | r Step˜↩ p 68 | "Failed to converge" ! max_iter > i+↩1 69 | 1;0 70 | }@ 71 | <˘⍉> r 72 | }exact._with {𝕨𝔽CPoly𝕩} 73 | } 74 | ⟨WeierstrassRoots⇐WR, AberthRoots⇐AR⟩ ← { 75 | M‿D ← complex 76 | WR ⇐ { ⊢ - ⊏ ⊸ D ⟜(M˝·⍉(1‿0×⌜=⟜<↕𝕩)+·><⊸-¨) } _allRoots_ 1 77 | AR ⇐ { ⊢ - D˝⊸(⊣D 1‿0+M)⟜(¯1‿0+˝¨(⋈⟜0=⌜˜↕𝕩)D∘+-⌜˜¨) } _allRoots_ 2 78 | } 79 | 80 | # Find a root of the given polynomial with Laguerre's method 81 | _laguerre_ ← {poly _𝕣_ eps root: 82 | M‿D‿A‿A2‿Sqrt‿_polyd_ek_ ← complex 83 | Jump ← (•MakeRand 1).Range∘0 ⋄ jump_i ← 10 84 | 85 | nk2 ← 2 ÷ nrn ← ¬ rn ← ÷ 1-˜≠poly 86 | F ← (CPoly poly) _polyd_ek_ 3 87 | max_iter ← 200 88 | i ← 0 89 | ⊢_while_ {𝕤 90 | i < max_iter ? 91 | ⟨p‿dp‿d2p_h, ek⟩ ← <˘⌾⊑ F root 92 | 0 < a2p ← A2 p ? 93 | crit ← eps × ×˜ek # Square of stopping criterion 94 | stop ← { a2p ≥ crit ? 0 # keep going 95 | ; a2p ≥ 0.01×crit ? 1 # finish this iteration, then stop 96 | ; 2 } # return immediately 97 | 2 > stop ? 98 | dx ← { 99 | # Division by zero: jump in random direction 100 | 0‿0 ≡ dp ? (1 + A root) × (•math.Sin⋈•math.Cos) (2×π) × Jump i ; 101 | # Otherwise, Laguerre step 102 | fac ← p‿d2p_h M¨ ⊑) Sqrt 1‿0 - nk2 × M´ fac 104 | } 105 | (root -↩ dx) ≢ root ? 106 | ¬stop ? 107 | i+↩1 108 | # Randomly reduce length every few steps 109 | { 0=jump_i|i ? root +↩ dx × Jump i÷jump_i ;@} 110 | 1;0 111 | }@ 112 | root 113 | } 114 | 115 | # Find all roots, with linear/quadratic formulas and Laguerre 116 | _laguerreRootsPolish ← { 117 | eps ← 𝕨⊣1e¯16 118 | ⟨M⟩ ← complex 119 | Solve ← { 120 | root ← 𝕩 _laguerre_ eps 0‿0 121 | root <⊸∾ Solve M⟜root⊸+`⌾⌽ 1↓𝕩 122 | } exact._with 123 | {𝕨 _laguerre_ eps¨⌾(1⊸↓) 𝕩}⍟𝕗⟜Solve exact._with CPoly 𝕩 124 | } 125 | LaguerreRoots ← 1 _laguerreRootsPolish 126 | -------------------------------------------------------------------------------- /Beacon/Utilities/stdlib/primes.bqn: -------------------------------------------------------------------------------- 1 | ⟨ 2 | PrimesTo 3 | PrimesIn, SieveSegment 4 | NextPrime, PrevPrime 5 | IsPrime 6 | Pi 7 | NthPrime 8 | Factor, FactorExponents 9 | FactorCounts 10 | Totient 11 | ⟩⇐ 12 | 13 | # Wheel factorization 14 | MakeWheel ← { 15 | len ⇐ ×´primes ⇐ 𝕩 # Primes in wheel and wheel length 16 | mask ⇐ ∧´(len⥊0<↕)¨primes 17 | numc ⇐ ≠ coprime ⇐ /mask 18 | 19 | # Get prime mask on segment [start,end) with prime divisors pd 20 | # Assumes pd doesn't intersect primes, or [start,end) 21 | Sieve ⇐ {pd 𝕊 start‿end: 22 | w←coprime,wn←numc,wl←len 23 | bc‿ep ← wl (⌊∘÷˜ ⋈ w⍋|) (𝕩-1)÷⌜pd 24 | 25 | # Do first partial wheels together 26 | m ← ≠˝bc 27 | U←{⟨r←𝕩-𝕨,(⊒/r)+r/𝕨⟩} #{⟨r←𝕩-𝕨,((↕·⊑¯1⊸↑)+r/𝕨-»)+`r⟩} 28 | fr‿fk ← U⟜((m×wn)⊸⌈)˝ ep 29 | j ← (fr/pd) × (fr/wl×⊏bc) + fk⊏w 30 | 31 | # Then the remainder for each prime spanning multiple wheels 32 | Rem ← {p‿s‿e‿o: # prime, start, end, overflow 33 | pl←p×wl ⋄ pw←start-˜p×w 34 | ⟨⥊(pl×s+↕e-s)+⌜pw, (pl×e)+o↑pw⟩ 35 | } 36 | b ← Rem˘ ⍉>m⊸/¨⟨pd,1+⊏bc,1⊏bc,1⊏ep⟩ 37 | 38 | i ← ∾ ⟨j-start⟩∾⥊b 39 | 0¨⌾(i⊸⊏) (end-start)⥊start⌽mask 40 | } 41 | } 42 | 43 | # Prime state 44 | next ← 11 45 | ⟨Sieve⟩ ← wheel ← MakeWheel 2‿3‿5‿7 46 | wlen ← ≠ wheel.primes 47 | primes ← wheel.primes 48 | Extend ← { 49 | E ← { 50 | next ↩ ⌈ 𝕩 ⌊ ((2⋆24)⊸+ ⌊ ט) old←next 51 | primes ∾↩ old + / (wlen↓primes(⍋↑⊣)√next) Sieve old‿next 52 | 𝕊⍟(next⊸<) 𝕩 53 | } 54 | next E∘(2⊸×⊸⌈)⍟< 𝕩 55 | } 56 | 57 | CheckNum ← "Argument must be a non-negative number" ! (1=•Type)◶⟨0,0⊸≤⟩ 58 | CheckNat ← "Argument must be a natural number" ! (1=•Type)◶⟨0,|∘⌊⊸=⟩ 59 | 60 | PrimesTo ← { 61 | CheckNum 𝕩 62 | Extend 𝕩 63 | primes (⍋↑⊣) 𝕩 64 | } 65 | 66 | _getSegment ← {ind _𝕣: 67 | PRange ← { primes (⊣⊏˜·(⊣+↕∘-˜)´⍋) 𝕩-1 } 68 | sn ← {⊑∘⊢+/∘𝕏}⍟𝕗 sieve 69 | pr ← {-˜´ ↑ ·/⁼𝕏-⊑}⍟(¬𝕗) prange 70 | S ← {pd 𝕊 s‿e: 71 | n ← next⌊e 72 | (PR s‿n) ∾ <´◶⟨⟨⟩,primes⊸SN⟩ n‿e 73 | } 74 | # IsPrime¨⊸(𝕗◶⊣‿/) start(⊣+↕∘-˜)end 75 | {𝕊 start‿end: 76 | CheckNat¨ 𝕩 77 | "Range must be ordered" ! ≤´𝕩 78 | (wlen ↓ PrimesTo √end-1) (next≤start)◶S‿SN 𝕩 79 | } 80 | } 81 | SieveSegment ← 0 _getSegment # IsPrime¨ (⊣+↕∘-˜)´ 82 | PrimesIn ← 1 _getSegment # IsPrime¨⊸/ (⊣+↕∘-˜)´ 83 | 84 | NextPrime ← { (0<≠)◶⟨𝕊1⊑R, ⊑ ⟩ PrimesIn r← 𝕩+1‿65 }⚇0 85 | PrevPrime ← { !2<𝕩 ⋄ (0<≠)◶⟨𝕊0⊑R,¯1⊑⊢⟩ PrimesIn r←1⌈𝕩-64‿0 }⚇0 86 | 87 | IsPrime ← { 88 | CheckNat 𝕩 89 | p ← wheel.primes 90 | c ← 0=p|𝕩 91 | (∨´c)◶⟨(ט¯1⊑p)⊸<◶⟨1⊸≠,MillerRabin⟩, c⊑∘/⟜p⊸=⊢ ⟩ 𝕩 92 | }⚇0 93 | 94 | # Compute n|𝕨×𝕩 in high precision 95 | _modMul ← { n _𝕣: 96 | # Split each argument into two 26-bit numbers, with the remaining 97 | # mantissa bit encoded in the sign of the lower-order part. 98 | q←1+2⋆27 99 | Split ← { h←(q×𝕩)(⊣--)𝕩 ⋄ ⟨𝕩-h,h⟩ } 100 | # The product, and an error relative to precise split multiplication. 101 | Mul ← × (⊣ ⋈ -⊸(+´)) ·⥊×⌜○Split 102 | ((n×<⟜0)⊸+ -⟜n+⊢)´ n | Mul 103 | } 104 | MillerRabin ← { 𝕊 n: 105 | # n = 1 + d×2⋆s 106 | s ← 0 {𝕨 2⊸|◶⟨+⟜1𝕊2⌊∘÷˜⊢,⊣⟩ 𝕩} n-1 107 | d ← (n-1) ÷ 2⋆s 108 | 109 | # Arithmetic mod n 110 | Mul ← n _modMul 111 | Pow ← Mul{𝔽´𝔽˜⍟(/2|⌊∘÷⟜2⍟(↕1+·⌊2⋆⁼⊢)𝕩)𝕨} 112 | 113 | # Miller-Rabin test 114 | MR ← { 115 | 1 =𝕩 ? 𝕨≠s ; 116 | (n-1)=𝕩 ? 0 ; 117 | 𝕨≤1 ? 1 ; 118 | (𝕨-1) 𝕊 Mul˜𝕩 119 | } 120 | C ← { 𝕊a: s MR a Pow d } # Is composite 121 | 0 {𝕨<⟜≠◶⟨1,C∘⊑◶⟨+⟜1⊸𝕊,0⟩⟩𝕩} Witnesses 𝕩 122 | } 123 | 124 | witnesses ← { (1↓𝕨)⊸⍋⌾< ⊑ 𝕩˙ }˝ ⍉∘‿2⥊ ⟨ 125 | 0 , ⟨1948244569546278⟩ 126 | 212321 , 15‿5511855321103177 127 | 624732421 , 15‿7363882082‿992620450144556 128 | 273919523041 , 2‿2570940‿211991001‿3749873356 129 | 47636622961201 , 2‿2570940‿880937‿610386380‿4130785767 130 | 3770579582154547 , 2‿325‿9375‿28178‿450775‿9780504‿1795265022 131 | ⟩ 132 | 133 | # Number of primes less than or equal to 𝕩 134 | Pi ← ((0=≡) ⊑∘⊢⍟⊣ {𝕩 LargePi∘⊣¨⌾((next<𝕩)⊸/) primes⍋𝕩}⌾⥊)∘⌊⚇1 135 | LargePi ← { 136 | # Meissel-Lehmer algorithm 137 | Extend 2×√𝕩 # Need one past √𝕩 138 | a‿b‿c ← primes ⍋ 4‿2‿3 √ 𝕩 139 | 140 | ph ← -´ +´¨ 2⌊∘÷˜1+ (↓⥊𝕩) (⊢∾⟜(0⊸<⊸/)¨·⌽⌊∘÷˜)´ 1↓a↑primes # φ(𝕩,a) 141 | 142 | p ← a↓b↑primes 143 | w ← 𝕩 ÷ p 144 | v ← w ↑˜ ca←c-a 145 | j ← (primes ⍋ √v) - a+↕ca 146 | r ← +´Pi w ∾ (j/v)÷(⊒⊸+/j)⊏p 147 | is ← (b+a-2) × (b¬a) ÷ 2 148 | js ← +´ j × (a+↕≠j)+(j-1)÷2 149 | ph + is + js - r 150 | } 151 | 152 | NthPrime ← {primes≠⊸≤◶⟨⊑˜,NP⟩𝕩}⚇0 153 | NP ← { 154 | a ← 2⌈⌈ (1-˜⋆⁼⍟2(++-⟜2⊸÷)⋆⁼)⊸× 𝕩 # Approximation 155 | d ← 𝕩 - Pi a-1 # Primes remaining 156 | p ← ⋆⁼a # Number→distance multiplier 157 | e ← 8××d-0.5 # Go a few primes further 158 | a { 𝕩 (≥⟜-∧<)⟜≠◶⟨n𝕊⊣-×⊸×⟜≠,⊑⟩ PrimesIn ∧𝕨⋈n←𝕨+⌊p×𝕩+e } d 159 | } 160 | 161 | # Prime factors and exponents 162 | FactorExponents ← { 163 | CheckNat 𝕩 164 | 2⊸⌊◶⟨!∘"Can't factor 0", 2‿0⊸⥊, IsPrime◶⟨FactorTrial, ≍˘⋈⟜1⟩⟩ 𝕩 165 | }⚇0 166 | FactorTrial ← { 𝕊n: 167 | r ← 0‿2⥊0 # Transposed result 168 | Div ← {𝕊p: # Add exponents of p to result 169 | D←0=p|⊢ 170 | e←0 ⋄ n↩{e+↩1⋄𝕊⍟D𝕩÷p}n 171 | r∾↩p‿e 172 | } 173 | Try ← { 174 | 𝕩 ⌊↩ 1+⌊√n 175 | 𝕨<𝕩 ? 176 | Div¨ (0=|⟜n)⊸/ PrimesIn 𝕨‿𝕩 177 | {r∾↩⍉PollardFactors n⋄n↩1⋄𝕩}⍟¬ 𝕩 ⟜0)⊣)𝕩} 189 | # Return a divisor of n other than 1. Can be n if the algorithm fails. 190 | # c should be in ↕n but not 0 or n-2. 191 | PollardRho ← { c 𝕊 n: 192 | x←ys←y ← 2 193 | q ← 1 194 | Mul ← n _modMul 195 | Adv ← {n-˜⍟≤c+Mul˜𝕩} 196 | NextY ← {𝕤⋄ |x-(y Adv↩)} 197 | g ← { 𝕊 r: 198 | x ↩ y 199 | y Adv⍟r↩ 200 | k ← 0 201 | m ← 100 # Check GCD to see if we can stop every m iterations 202 | FindG ← {𝕤 203 | k0 ← k 204 | k ↩ r ⌊ k+m 205 | ys ↩ y 206 | q Mul⟜NextY⍟(k-k0)↩ 207 | n GCD q 208 | } _while_ {k 0. 9 | # Return a value at a distance of at most 𝕨⊣1e¯10 from a root of F. 10 | _ITP ← { 𝕨 F _𝕣 x: 11 | e ← 𝕨⊣1e¯10 # Tolerance 12 | w ← -˜´x # Initial width 13 | c ← 0.8÷w, n0←1 # c←k1×2⋆k2←2 14 | r0← (n0+⌈)⌾(2⋆⁼÷⟜e)w # Expected radius upper bound 15 | ! <○(0⊸<)´ y ← F¨x # Require bracketed and increasing 16 | xi←0 # Result 17 | ⊢_while_ {𝕤 18 | xh‿r ← (+⋈-˜)´x÷2 # Midpoint and radius 19 | xi↩xh ⋄ e ○(⌈`(1+↕∘≠)⊸×)⟜¬ isdig 95 | bm ← l/ic i 122 | f←l⥊1+0=↕3 123 | M←((+´f)⥊0<↕4) × f⊸/ 124 | a‿b←K64 l 125 | v←a (⌊∘÷˜ «⊸+○M b×|) i 126 | (v⊏b64)∾(3|-l)⥊'=' 127 | } 128 | FromBase64 ← {𝕊⁼𝕩: ToBase64𝕩; 129 | p←64>i←b64⊐𝕩 130 | "Invalid Base64 characters" ! ∧`⊸≡ p 131 | i↑˜↩l←+´p 132 | a‿b←K64 +´f←l⥊0<↕4 133 | @ + (256|a×(«f)/i) + b⌊∘÷˜f/i 134 | } 135 | 136 | # Parse numbers from a string, treating non-numeric characters as 137 | # separators 138 | # Natural numbers recognize digits only 139 | NN ← (>⟜«0⊸≤) / 0(0⊸≤××⟜10⊸+)`⊢ 140 | ToNats ← { NN 10⊸≤⊸(¬⊸×-⊣) 𝕩-'0' } 141 | 142 | # General numbers recognize digits and eE.¯-π∞ (mild extension of BQN) 143 | ToNums ← { 144 | T←⌈`× ⋄ I1T←(1+↕∘≠)⊸T 145 | cd←≠dig←('0'+↕10)∾"π∞" ⋄ val←(↕10)∾π‿1‿¯1 # ∞ as 1 to avoid ∞×0 146 | e‿d‿n‿p‿i←"e.¯π∞"=<𝕩 ⋄ e‿n∨↩"E-"=<𝕩 147 | m←d∨cd>j←dig⊐𝕩 148 | s←d∨c←e∨z←(∧`⌾⌽<»⊸<)zz←¬e∨n∨m 149 | "Negative sign in the middle of a number"! ∧´n≤1»c 150 | "Portion of a number is empty"! ∧´¬(1«s)∧n∨s 151 | "Ill-formed decimal or exponent use"! ∧´(0⊸=∨»⊸<)s/d+2×e 152 | "π and ∞ must occur alone"! ∧´(p∨i)≤1(»∧(p∧«e)∨«)zz∨n>»e 153 | f←(17≥¬(⊢-T)+`)⊸∧g←(«≤(d ○I1T¬)⊸∧m # No leading 0s; max 17 digits 154 | vs←1‿¯1⊏˜(r←>⟜»m)/»n # Negate if ¯ 155 | v←vs×NN val⊏˜(¬d)/(cd׬f)⌈j # Numeric values—mantissas and exponents 156 | vm←c/○(1⌾⊑)z # Mask of mantissas in v 157 | dp←vm/f(--»⊸-(<×⊢)⊏⟜(I1T«d)⊸-)○(/>⟜«)g # Decimal position 158 | q←10⋆|ee←dp-˜vm/«v׬vm # Power of 10 159 | q÷˜⌾((0>ee)⊸/)q×⌾((0 ○≠){𝕩𝔽¨𝔽`𝕨∾¨""<⊸»𝕩}˝ (=⌜⥊¨⊣)⟜⌽ 165 | Levenshtein ← ¯1⊑{𝕨((1⊸+⥊+)○≠(⌊`⊢⌊⊏⊸»∘⊢-0∾1+=⟜𝕩)´⌽∘⊣)𝕩} 166 | -------------------------------------------------------------------------------- /Beacon/Views/BuffersView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BuffersView.swift 3 | // Arrayground 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct BuffersView: View { 9 | @Binding var buffers: [String: [Entry]] 10 | @Binding var sel: String 11 | 12 | var body: some View { 13 | Section { 14 | Text("Buffers") 15 | .font(.system(.body, design: .monospaced, weight: .semibold)) 16 | .padding() 17 | 18 | ScrollView(.vertical) { 19 | VStack(alignment: .leading) { 20 | ForEach(Array(buffers.keys), id: \.self) { b in 21 | Button(action: { 22 | self.sel = b 23 | }) { 24 | HStack { 25 | Text(b) 26 | .font(.system(.body, design: .monospaced)) 27 | .padding() 28 | .background(self.sel == b ? Color.blue.opacity(0.1) : Color.gray.opacity(0.1)) // Change colour if selected 29 | .cornerRadius(8) 30 | Spacer() 31 | Button(action: { 32 | buffers[b] = nil 33 | }) { 34 | Image(systemName: "minus.circle.fill") 35 | .foregroundColor(.red) 36 | } 37 | .padding(.trailing) 38 | } 39 | } 40 | } 41 | }.padding(.horizontal) 42 | HStack { 43 | Spacer() 44 | Button(action: { 45 | sel = genRandBuffer(length: 8) 46 | buffers[sel] = [] 47 | }) { 48 | Image(systemName: "plus.circle.fill") 49 | .foregroundColor(.green) 50 | .font(.system(size: 36)) // Increase the size here 51 | } 52 | .padding(.trailing) 53 | Spacer() 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | // #Preview { 61 | // BuffersView(buffers: ["key1": "value1", "key2": "value2"], sel: .constant("1")) 62 | // } 63 | -------------------------------------------------------------------------------- /Beacon/Views/ConfigView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigView.swift 3 | // Arrayground 4 | // 5 | 6 | import SwiftUI 7 | 8 | enum Appearance: Int { 9 | case system = 0 10 | case light = 1 11 | case dark = 2 12 | } 13 | 14 | enum AppFont: Int { 15 | case bqn386 = 0 16 | case iosevka = 1 17 | case apl = 2 18 | } 19 | 20 | enum Language: Int, Codable { 21 | case bqn = 0 22 | case k = 1 23 | } 24 | 25 | func languageToString(l: Language) -> String { 26 | if l == .bqn { 27 | return "BQN" 28 | } else { 29 | return "K" 30 | } 31 | } 32 | 33 | enum Behavior: Int { 34 | case inlineEdit = 0 35 | case duplicate = 1 36 | } 37 | 38 | struct ConfigView: View { 39 | @AppStorage("scheme") private var scheme: Appearance = .system 40 | @AppStorage("font") private var font: AppFont = .bqn386 41 | @AppStorage("lang") private var lang: Language = .k 42 | @AppStorage("editType") private var editType: Behavior = .duplicate 43 | 44 | var body: some View { 45 | NavigationView { 46 | VStack { 47 | List { 48 | Section { 49 | HStack { 50 | Text("Language") 51 | Picker(selection: $lang) { 52 | Text("BQN") 53 | .tag(Language.bqn) 54 | Text("K") 55 | .tag(Language.k) 56 | } label: { 57 | Text("") 58 | } 59 | .pickerStyle(.menu) 60 | } 61 | } header: { 62 | Text("Language Settings") 63 | }.headerProminence(.increased) 64 | Section { 65 | HStack { 66 | Text("Input Click Behavior") 67 | Picker(selection: $editType) { 68 | Text("Inline edit") 69 | .tag(Behavior.inlineEdit) 70 | Text("Duplicate") 71 | .tag(Behavior.duplicate) 72 | } label: { 73 | Text("") 74 | } 75 | .pickerStyle(.menu) 76 | } 77 | } header: { 78 | Text("App Behavior") 79 | }.headerProminence(.increased) 80 | Section { 81 | HStack { 82 | Text("Colorscheme") 83 | Picker(selection: $scheme) { 84 | Text("System") 85 | .tag(Appearance.system) 86 | Text("Light") 87 | .tag(Appearance.light) 88 | Text("Dark") 89 | .tag(Appearance.dark) 90 | } label: { 91 | Text("") 92 | } 93 | } 94 | .pickerStyle(.menu) 95 | HStack { 96 | Text("Font") 97 | Picker(selection: $font) { 98 | Text("BQN386") 99 | .tag(AppFont.bqn386) 100 | Text("Iosevka") 101 | .tag(AppFont.iosevka) 102 | Text("APL") 103 | .tag(AppFont.apl) 104 | } label: { 105 | Text("") 106 | } 107 | .pickerStyle(.menu) 108 | } 109 | } header: { 110 | Text("Appearances") 111 | }.headerProminence(.increased) 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | #Preview { 119 | ConfigView() 120 | } 121 | -------------------------------------------------------------------------------- /Beacon/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // BQN 4 | // 5 | 6 | import Combine 7 | import CoreSpotlight 8 | import Foundation 9 | import MobileCoreServices 10 | import SwiftUI 11 | import UIKit 12 | 13 | struct ContentView: View { 14 | @State var input: String = "" 15 | @State var ephemerals: [Int: [String]] = [:] 16 | @State var showSettings: Bool = false 17 | @State var showHelp: Bool = false 18 | @State var showBuffers: Bool = false 19 | @State var curBuffer: String = "default" 20 | @State var inpPos: Int = -1 21 | @State var move: (Int) -> Void = { _ in } 22 | @AppStorage("lang") var lang: Language = .bqn 23 | @AppStorage("editType") var editType: Behavior = .inlineEdit 24 | @FocusState var isFocused: Bool 25 | @ObservedObject var viewModel: HistoryModel 26 | 27 | func onMySubmit(input: String) { 28 | switch input { 29 | case "clear": 30 | viewModel.clear(b: curBuffer) 31 | self.input = "" 32 | case _ where input.hasPrefix(#"\,"#): 33 | let components = input.components(separatedBy: " ") 34 | if let lastWord = components.last { 35 | curBuffer = lastWord 36 | } 37 | default: 38 | if !input.isEmpty { 39 | // FIXME, below is a hacky workaround for appstorage not syncing? 40 | let output = UserDefaults.standard.integer(forKey: "lang") == Language.bqn.rawValue 41 | ? e(input: input) 42 | : ke(input: input) 43 | 44 | let attr = CSSearchableItemAttributeSet(contentType: .item) 45 | attr.title = input 46 | attr.contentDescription = output 47 | attr.displayName = input 48 | let uid = UUID().uuidString 49 | let item = CSSearchableItem(uniqueIdentifier: uid, domainIdentifier: "arrscience.beacons", attributeSet: attr) 50 | CSSearchableIndex.default().indexSearchableItems([item]) 51 | 52 | viewModel.addMessage(with: input, out: output, lang: lang, for: curBuffer) 53 | } else { 54 | isFocused = false 55 | } 56 | self.input = "" 57 | } 58 | } 59 | 60 | var body: some View { 61 | ScrollViewReader { scrollView in 62 | VStack { 63 | ScrollView(.vertical) { 64 | VStack(spacing: 12) { 65 | if viewModel.history[curBuffer, default: []].isEmpty { 66 | VStack { 67 | Text("ngn/k, (c) 2019-2023") 68 | .font(Font.custom("BQN386 Unicode", size: 16)) 69 | .foregroundColor(.primary) 70 | Text("dzaima/cbqn, (c) 2019-2023") 71 | .font(Font.custom("BQN386 Unicode", size: 16)) 72 | .foregroundColor(.primary) 73 | } 74 | } else { 75 | ForEach(Array(viewModel.history[curBuffer, default: []].enumerated()), id: \.offset) { index, historyItem in 76 | HistoryView(index: index, historyItem: historyItem, curBuffer: curBuffer, onMySubmit: onMySubmit, input: $input, ephemerals: $ephemerals, editType: $editType, viewModel: viewModel) 77 | }.listRowBackground(Color.clear) 78 | .listRowSeparator(.hidden) 79 | } 80 | }.id("HistoryScrollView") 81 | .onChange(of: viewModel.history) { 82 | withAnimation { 83 | scrollView.scrollTo("HistoryScrollView", anchor: .bottom) 84 | } 85 | } 86 | } 87 | }.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)) 88 | .padding(.bottom, 5) 89 | 90 | ReplInput(text: $input, 91 | helpOpen: $showHelp, 92 | settingsOpen: $showSettings, 93 | buffersOpen: $showBuffers, 94 | lang: self.lang, onSubmit: { onMySubmit(input: self.input) }, 95 | font: Font.custom("BQN386 Unicode", size: 20)) 96 | .padding(.bottom, 4) 97 | .focused($isFocused) 98 | .onTapGesture { 99 | scrollView.scrollTo("HistoryScrollView", anchor: .bottom) 100 | } 101 | .onReceive(Just(input)) { newV in 102 | if lang == .bqn { 103 | let oldCurPos = self.inpPos 104 | newV.matchingStrings(regex: "\\\\[a-zA-Z0-9]").forEach { NSR in 105 | let range = Range(NSR[0], in: newV)! 106 | if let res = characterMap[String(newV[range])] { 107 | let mod = newV.replacingCharacters(in: range, with: String(res)) 108 | self.input = mod 109 | } 110 | self.move(oldCurPos) 111 | } 112 | } 113 | } 114 | } 115 | .padding(.top, 0.1) 116 | .sheet(isPresented: $showSettings) { 117 | ConfigView() 118 | .presentationDetents([.medium]) 119 | } 120 | .sheet(isPresented: $showHelp) { 121 | HelpView() 122 | .presentationDetents([.large]) 123 | } 124 | .sheet(isPresented: $showBuffers) { 125 | BuffersView(buffers: $viewModel.history, sel: self.$curBuffer) 126 | .presentationDetents([.medium]) 127 | } 128 | .onAppear(perform: initRepl) 129 | } 130 | 131 | func initRepl() { 132 | viewModel.load(Buffers.get()) 133 | kinit() 134 | repl_init() 135 | // below is adapted from https://codeberg.org/ngn/k/src/branch/master/repl.k 136 | let k_formatter = """ 137 | (opn;sem;cls):"(;)" 138 | lines:cols:80 139 | upd:(lines;cols) 140 | lim:{(x<#y)(x#)/y} 141 | dd:{(x<#y)(,[;".."](x-2)#)/y} 142 | fmt:{upd[];$[(::)~x;"";(1<#x)&|/`m`M`A=@x;mMA x;(dd[cols]`k@lim[cols]x),"\n"]} 143 | fmtx:{$[(::)~x;"";`k[x],"\n"]} 144 | mMA:{(P;f;q):((,"!/+(";dct;,")");(("+![";" +(");tbl;")]");(,,"(";lst;,")"))`m`M`A?t:@x 145 | w:cols-#*P;u:w-#q;h:lines-2 146 | x:$[h<(`M=t)+#x;,[;,".."](h-1)#f[w;u;h#x];f[w;u;x]] 147 | ,[;"\n"]@"\n"/@[;-1+#x;,;q]P[!#x],'x} 148 | lst:{[w;u;x](((-1+#x)#w),u)dd'`k'lim[cols]'x} 149 | dct:{[w;u;x]k:(|/#'k)$k:`k'!x;par'(((-1+#x)#w-3),u-3)dd'sem/'+(k;`k'.x)} 150 | tbl:{[w;u;x]h:`k'!x;d:`k''.x;W:(#'h)|/'#''d 151 | r:,$[`S~@!x;dd[w](""opn),(""sem)/;par@dd[w-2]@sem/]W$'h 152 | r,par'dd[w-2]'sem/'+@[W;&~^`i`d?_@'.x;-:]$'} 153 | cell:{$[|/`i`d=@y;-x;x]$z} 154 | par:{opn,x,cls} 155 | line0:{c:{0x07~*-2#*x}{(l;r):x;(1:1;r,,(-2_l))}/(x;());"\n"/(*|c),,*c} 156 | line1:{$[#x;;:0];.[`1:(fmt;fmtx)[" "~*x]@.:;,x;{`0:`err[]}]} 157 | line:line1@line0@ 158 | """ 159 | let _ = runCmd(kCmd, k_formatter) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Beacon/Views/DashboardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DashboardView.swift 3 | // Arrayground 4 | // 5 | 6 | import Charts 7 | import Combine 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct DataElement: Identifiable, Comparable { 12 | var id = UUID() 13 | let key: String 14 | let value: Int 15 | 16 | static func < (lhs: DataElement, rhs: DataElement) -> Bool { 17 | return lhs.key < rhs.key 18 | } 19 | 20 | static func == (lhs: DataElement, rhs: DataElement) -> Bool { 21 | return lhs.key == rhs.key 22 | } 23 | } 24 | 25 | struct Card: Identifiable, Codable { 26 | let id: UUID 27 | var snippet: String 28 | } 29 | 30 | func parseData(s: String) -> [String: Int] { 31 | if s != "" { 32 | let components = s.components(separatedBy: "!") 33 | let keysString = components[0].trimmingCharacters(in: CharacterSet(charactersIn: "()\"")) 34 | let valuesString = components[1] 35 | let keys = keysString.components(separatedBy: ";") 36 | let values = valuesString.split(separator: " ").map { Int($0) } 37 | assert(keys.count == values.count, "Keys and values count mismatch!") 38 | var dict: [String: Int] = [:] 39 | for (index, key) in keys.enumerated() { 40 | dict[key.replacingOccurrences(of: "\"", with: #""#)] = values[index] 41 | } 42 | return dict 43 | } else { 44 | return [:] 45 | } 46 | } 47 | 48 | class DashboardViewModel: ObservableObject { 49 | @Published var cards: [Card] = [] { 50 | didSet { 51 | let encoder = JSONEncoder() 52 | if let encoded = try? encoder.encode(cards) { 53 | UserDefaults.standard.set(encoded, forKey: "cards") 54 | } 55 | } 56 | } 57 | 58 | func removeCard(at index: Int) { 59 | cards.remove(at: index) 60 | } 61 | 62 | init() { 63 | if let cardsData = UserDefaults.standard.data(forKey: "cards") { 64 | let decoder = JSONDecoder() 65 | if let loadedCards = try? decoder.decode([Card].self, from: cardsData) { 66 | cards = loadedCards 67 | return 68 | } 69 | } 70 | cards = [Card(id: UUID(), snippet: "")] 71 | } 72 | } 73 | 74 | struct Dashboard: View { 75 | @StateObject var viewModel = DashboardViewModel() 76 | var body: some View { 77 | VStack { 78 | Text("Dashboard") 79 | .font(.largeTitle) 80 | .fontWeight(.bold) 81 | .padding(.bottom, 20) 82 | Spacer() 83 | ScrollView(.vertical, showsIndicators: false) { 84 | VStack { 85 | ForEach(viewModel.cards.indices, id: \.self) { index in 86 | CardView(card: $viewModel.cards[index], removeCard: { 87 | viewModel.removeCard(at: index) 88 | }) 89 | .padding(.horizontal) 90 | } 91 | 92 | Button(action: { 93 | viewModel.cards.append(Card(id: UUID(), snippet: "")) 94 | }) { 95 | HStack { Image(systemName: "plus") } 96 | .padding() 97 | .foregroundColor(.white) 98 | .background(Color.blue) 99 | .cornerRadius(10) 100 | } 101 | .padding(.horizontal) 102 | } 103 | } 104 | Spacer() 105 | } 106 | .padding() 107 | } 108 | } 109 | 110 | struct CardView: View { 111 | @Binding var card: Card 112 | let removeCard: () -> Void 113 | @State private var barSelection: String? 114 | @State private var isLoading: Bool = false 115 | @State private var isEditing: Bool = false 116 | @State var output: String = "" 117 | @State var snippet: String = "" 118 | var refreshRate: Double = 60.0 119 | var data: [String: Int] { return parseData(s: output) } 120 | let timer = Timer.publish(every: 15, on: .main, in: .common).autoconnect() 121 | 122 | var body: some View { 123 | VStack { 124 | ZStack { 125 | VStack { 126 | HStack { 127 | Spacer() 128 | Button(action: { self.removeCard() }) { 129 | Image(systemName: "xmark.circle.fill") 130 | .font(.title2) 131 | .foregroundColor(.red) 132 | } 133 | Button(action: { 134 | self.isEditing = true 135 | }) { 136 | Image(systemName: "pencil.circle.fill") 137 | .font(.title2) 138 | .foregroundColor(.green) 139 | } 140 | .padding(.trailing) 141 | } 142 | if self.isEditing || self.card.snippet.isEmpty { 143 | TextEditor(text: $snippet) 144 | .padding(.horizontal) 145 | .navigationTitle("snippet") 146 | .onChange(of: snippet) { 147 | if snippet.suffix(2) == "\n\n" { 148 | self.snippet = self.snippet.trimmingLastOccurrence(of: "\n\n") 149 | self.card.snippet = self.snippet 150 | self.isEditing = false 151 | self.refresh() 152 | } 153 | } 154 | .textFieldStyle(RoundedBorderTextFieldStyle()) 155 | .font(Font.system(.body, design: .monospaced)) 156 | .foregroundColor(.accentColor) // Text color 157 | .frame(width: 350, height: 350) 158 | .border(.yellow) 159 | .padding() 160 | } else { 161 | ZStack { 162 | if isLoading { 163 | ProgressView().frame(width: 350, height: 350) 164 | } 165 | VStack { 166 | VStack { 167 | if !output.contains("Error") && output.contains("!") { // FIXME: need a better check if the output is a dict 168 | Chart { 169 | ForEach(data.map { DataElement(key: $0.key, value: $0.value) }.sorted()) { item in 170 | BarMark( 171 | x: .value("Key", item.key), 172 | y: .value("Count", item.value) 173 | ) 174 | .cornerRadius(8) 175 | .foregroundStyle(by: .value("Result Color", item.key)) 176 | } 177 | if let barSelection { 178 | RuleMark(x: .value("Key", barSelection)) 179 | .foregroundStyle(.gray.opacity(0.35)) 180 | .zIndex(-10) 181 | .offset(yStart: -15) 182 | .annotation( 183 | position: .top, 184 | spacing: 0, 185 | overflowResolution: .init(x: .disabled, y: .disabled) 186 | ) { 187 | ChartPopOverView(xval: barSelection, yval: data[barSelection] ?? 0) 188 | } 189 | } 190 | } 191 | .chartXSelection(value: $barSelection) 192 | .chartLegend(position: .bottom, alignment: .leading, spacing: 25) 193 | .padding(.all, 15) 194 | .chartYAxis { 195 | AxisMarks(position: .leading) { _ in 196 | AxisValueLabel() 197 | } 198 | } 199 | .chartXAxis { 200 | AxisMarks(position: .bottom) { _ in 201 | AxisValueLabel() 202 | } 203 | } 204 | } else { 205 | Text(output) 206 | .font(.body) 207 | .padding() 208 | Spacer() 209 | } 210 | /* 211 | ProgressView(value: updater.timeRemaining, total: updater.refreshRate) 212 | .accentColor(.green) 213 | .frame(height: 8.0) 214 | .scaleEffect(x: 1, y: 2, anchor: .center) 215 | */ 216 | } 217 | .frame(width: 350, height: 350) 218 | } 219 | } 220 | // .border(.blue) 221 | } 222 | } 223 | .padding() 224 | } 225 | }.onAppear(perform: { 226 | self.snippet = card.snippet 227 | refresh() 228 | }).onReceive(timer) { _ in 229 | refresh() 230 | } 231 | } 232 | 233 | func refresh() { 234 | if !isEditing { 235 | Task.init { 236 | self.isLoading = true 237 | let snippets = snippet.split(separator: "\n") 238 | for snippet in snippets { 239 | let to = UserDefaults.standard.integer(forKey: "lang") == Language.bqn.rawValue 240 | ? e(input: String(snippet)) 241 | : ke(input: String(snippet)) 242 | if snippet == snippets.last { 243 | self.output = to 244 | } 245 | } 246 | self.isLoading = false 247 | } 248 | } 249 | } 250 | 251 | @ViewBuilder 252 | func ChartPopOverView(xval: String, yval: Int) -> some View { 253 | VStack(alignment: .leading, spacing: 6) { 254 | HStack(spacing: 4) { 255 | Text(String(yval)) 256 | .font(.title3) 257 | Text(xval) 258 | .font(.title3) 259 | .textScale(.secondary) 260 | } 261 | } 262 | .padding() 263 | .background(.cyan, in: .rect(cornerRadius: 8)) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /Beacon/Views/HelpView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelpView.swift 3 | // Arrayground 4 | // 5 | 6 | import Foundation 7 | import SwiftUI 8 | 9 | func toDict(h: String) -> [String: String] { 10 | var helpDict: [String: String] = [:] 11 | let components = h.components(separatedBy: "--------------------------------------------------------------------------------") 12 | for component in components.dropFirst() { 13 | let lines = component.components(separatedBy: "\n") 14 | let key = String(lines[1]) 15 | let value = lines[2...].joined(separator: "\n") 16 | helpDict[key] = value 17 | } 18 | return helpDict 19 | } 20 | 21 | // taken from ngn/k/repl.k 22 | let kh: String = """ 23 | -------------------------------------------------------------------------------- 24 | General 25 | Types: 26 | list atom 27 | `A generic list () ,"ab" (0;`1;"2";{3};%) 28 | `I `i int 0N -9223372036854775807 01b 29 | `F `f float -0w -0.0 0.0 0w 1.2e308 0n 30 | `C `c char "a" 0x6263 "d\\0\\"\\n\\r\\t" 31 | `S `s symbol ` `a `"file.txt" `b`cd`"ef" 32 | `M `m table&dict +`a`b!(0 1;"23") (0#`)!() 33 | `o lambda {1+x*y#z} {[a;b]+/*/2#,a,b} 34 | `p projection 1+ {z}[0;1] @[;i;;] 35 | `q composition *|: {1+x*y}@ 36 | `r derived verb +/ 2\\ {y,x}': 37 | `u monadic verb +: 0:: 38 | `v dyadic verb + 0: 39 | `w adverb ' /: 40 | `x external func 41 | -------------------------------------------------------------------------------- 42 | IO 43 | I/O verbs 44 | 0:x read lines 45 | x 0:y write lines 46 | 1:x read bytes 47 | x 1:y write bytes 48 | i close >fd 50 | 51 | x can be a file descriptor (int) or symbol or string such as 52 | "file.txt" 53 | "/path/to/file" 54 | "host:port" 55 | ":port" /host defaults to 127.0.0.1 56 | -------------------------------------------------------------------------------- 57 | Primitives 58 | Verbs: : + - * % ! & | < > = ~ , ^ # _ $ ? @ . 0: 1: 59 | notation: [c]har [i]nt [n]umber(int|float|char) [s]ymbol [a]tom [d]ict 60 | [f]unc(monad) [F]unc(dyad) [xyz]any 61 | special: var:y set a:1;a -> 1 62 | var::y global a:1;{a::2}[];a -> 2 63 | (v;..):y unpack (b;(c;d)):(2 3;4 5);c -> 4 64 | :x return {:x+1;2}[3] -> 4 65 | :[x;y;..] cond :[0;`a;"\\0";`b;`;`c;();`d;`e] -> `e 66 | o[..] recur {:[x<2;x;+/o'x-1 2]}9 -> 34 67 | [..] progn [0;1;2;3] -> 3 68 | 69 | :: self f:(::);f 12 -> 12 70 | : right f:(:);f[1;2] -> 2 "abc":'"d" -> "ddd" 71 | +x flip +("ab";"cd") -> ("ac";"bd") 72 | N+N add 1 2+3 -> 4 5 73 | -N negate - 1 2 -> -1 -2 74 | N-N subtract 1-2 3 -> -1 -2 75 | *x first *`a`b -> `a *(0 1;"cd") -> 0 1 76 | N*N multiply 1 2*3 4 -> 3 8 77 | %N sqrt %25 -> 5.0 %-1 -> 0n 78 | N%N divide 2 3%4 -> 0.5 0.75 79 | !i enum !3 -> 0 1 2 !-3 -> -3 -2 -1 80 | !I odometer !2 3 -> (0 0 0 1 1 1;0 1 2 0 1 2) 81 | !d keys !`a`b!0 1 -> `a`b 82 | !S ns keys a.b.c:1;a.b.d:2;!`a`b -> `c`d 83 | x!y dict `a`b!1 2 -> `a`b!1 2 84 | i!I div -10!1234 567 -> 123 56 85 | i!I mod 10!1234 567 -> 4 7 86 | &I where &3 -> 0 0 0 &1 0 1 4 2 -> 0 2 3 3 3 3 4 4 87 | &x deepwhere &(0 1 0;1 0 0;1 1 1) -> (0 1 2 2 2;1 0 0 1 2) 88 | N&N min/and 2&-1 3 -> -1 2 0 0 1 1&0 1 0 1 -> 0 0 0 1 89 | |x reverse |"abc" -> "cba" |12 -> 12 90 | N|N max/or 2|-1 3 -> 2 3 0 0 1 1|0 1 0 1 -> 0 1 1 1 91 |0 2 1 3 5 4 92 | >X descend >"abacus" -> 4 5 3 1 0 2 93 | i close >fd 95 | N1 0 96 | N>N more 0 1>0 2 -> 0 0 97 | =X group ="abracadabra" -> "abrcd"!(0 3 5 7 10;1 8;2 9;,4;,6) 98 | =i unitmat =3 -> (1 0 0;0 1 0;0 0 1) 99 | N=N equal 0 1 2=0 1 3 -> 1 1 0 100 | ~x not ~(0 2;``a;"a \\0";::;{}) -> (1 0;1 0;0 0 1;1;0) 101 | x~y match 2 3~2 3 -> 1 "4"~4 -> 0 0~0.0 -> 0 102 | ,x enlist ,0 -> ,0 ,0 1 -> ,0 1 ,`a!1 -> +(,`a)!,,1 103 | x,y concat 0,1 2 -> 0 1 2 "a",1 -> ("a";1) 104 | d,d merge (`a`b!0 1),`b`c!2 3 -> `a`b`c!0 2 3 105 | ^x null ^(" a";0 1 0N;``a;0.0 0n) -> (1 0;0 0 1;1 0;0 1) 106 | a^y fill 1^0 0N 2 3 0N -> 0 1 2 3 1 "b"^" " -> "b" 107 | X^y without "abracadabra"^"bc" -> "araadara" 108 | #x length #"abc" -> 3 #4 -> 1 #`a`b`c!0 1 0 -> 3 109 | i#y take 5#"abc" -> "abcab" -5#`a`b`c -> `b`c`a`b`c 110 | X#d take keys `c`d`f#`a`b`c`d!1 2 3 4 -> `c`d`f!3 4 0N 111 | I#y reshape 2 3#` -> (```;```) 112 | f#y replicate (3>#:')#(0;2 1 3;5 4) -> (0;5 4) {2}#"ab" -> "aabb" 113 | _n floor _12.34 -12.34 -> 12 -13 114 | _c lowercase _"Ab" -> "ab" 115 | i_Y drop 2_"abcde" -> "cde" -2_`a`b`c -> ,`a 116 | X_d drop keys `a`c_`a`b`c!0 1 2 -> (,`b)!,1 117 | I_Y cut 2 4 4_"abcde" -> ("cd";"";,"e") 118 | f_Y weed out (3>#:')_(0;2 1 3;5 4) -> ,2 1 3 119 | X_i delete "abcde"_2 -> "abde" 120 | $x string $(12;"ab";`cd;+) -> ("12";(,"a";,"b");"cd";,"+") 121 | i$C pad 5$"abc" -> "abc " -3$"a" -> " a" 122 | s$y cast `c$97 -> "a" `i$-1.2 -> -1 `$"a" -> `a 123 | s$y int `I$"-12" -> -12 124 | ?X distinct ?"abacus" -> "abcus" 125 | ?i uniform ?2 -> 0.6438163747387873 0.8852656305774402 /random 126 | X?y find "abcde"?"bfe" -> 1 0N 4 127 | i?x roll 3?1000 -> 11 398 293 1?0 -> ,-8164324247243690787 128 | i?x deal -3?1000 -> 11 398 293 /guaranteed distinct 129 | @x type @1 -> `i @"ab" -> `C @() -> `A @(@) -> `v 130 | x@y apply(1) {x+1}@2 -> 3 "abc"@1 -> "b" (`a`b!0 1)@`b -> 1 131 | .S get a:1;.`a -> 1 b.c:2;.`b`c -> 2 132 | .C eval ."1+2" -> 3 133 | .d values .`a`b!0 1 -> 0 1 134 | x.y apply(n) {x*y+1}. 2 3 -> 8 (`a`b`c;`d`e`f). 1 0 -> `d 135 | 136 | @[x;y;f] amend @["ABC";1;_:] -> "AbC" @[2 3;1;{-x}] -> 2 -3 137 | @[x;y;F;z] amend @["abc";1;:;"x"] -> "axc" @[2 3;0;+;4] -> 6 3 138 | .[x;y;f] drill .[("AB";"CD");1 0;_:] -> ("AB";"cD") 139 | .[x;y;F;z] drill .[("ab";"cd");1 0;:;"x"] -> ("ab";"xd") 140 | .[f;y;f] try .[+;1 2;"E:",] -> 3 .[+;1,`;"E:",] -> "E:'type\\n" 141 | ?[x;y;z] splice ?["abcd";1 3;"xyz"] -> "axyzd" 142 | -------------------------------------------------------------------------------- 143 | Special Symbols 144 | `j?C parse json `j?"{\\"a\\":1,\\"b\\":[true,\\"c\\"]}" -> `a`b!(1.0;(1;,"c")) 145 | `k@x pretty-print `k("ab";2 3) -> "(\\"ab\\";2 3)" 146 | `p@C parse k 147 | `hex@C hexadecimal `hex"ab" -> "6162" 148 | `pri@i primes `pri 20 -> 2 3 5 7 11 13 17 19 149 | `x@x fork-exec `x(("/bin/wc";"-l");"a\\nbc\\nd\\n") -> "3\\n" 150 | `t[] current time (microseconds) 151 | `argv[] list of cmd line args (also in global variable x) 152 | `env[] dict of env variables 153 | `prng[] `prng@I get/set pseudo-random number generator internal state 154 | s:`prng[];r:9?0;`prng s;r~9?0 -> 1 155 | `prng@0 use current time to set state 156 | `err@C throw error 157 | `sin@N trigonometry `sin 12.34 -> -0.22444212919135995 158 | `exp@N exponential `exp 1 -> 2.7182818284590455 159 | `ln@N logarithm `ln 2 -> 0.6931471805599453 160 | `exit@i exit 161 | -------------------------------------------------------------------------------- 162 | Adverbs 163 | f' each1 #'("abc";3 4 5 6) -> 3 4 164 | x F' each2 2 3#'"ab" -> ("aa";"bbb") 165 | X' binsearch 1 3 5 7 9'8 9 0 -> 3 4 -1 166 | F/ fold +/1 2 3 -> 6 167 | F\\ scan +\\1 2 3 -> 1 3 6 168 | x F/ seeded / 10+/1 2 3 -> 16 169 | x F\\ seeded \\ 10+\\1 2 3 -> 11 13 16 170 | i f/ n-do 5(2*)/1 -> 32 171 | i f\\ n-dos 5(2*)\\1 -> 1 2 4 8 16 32 172 | f f/ while (1<){:[2!x;1+3*x;-2!x]}/3 -> 1 173 | f f\\ whiles (1<){:[2!x;1+3*x;-2!x]}\\3 -> 3 10 5 16 8 4 2 1 174 | f/ converge {1+1.0%x}/1 -> 1.618033988749895 175 | f\\ converges (-2!)\\100 -> 100 50 25 12 6 3 1 0 176 | C/ join "ra"/("ab";"cadab";"") -> "abracadabra" 177 | C\\ split "ra"\\"abracadabra" -> ("ab";"cadab";"") 178 | I/ decode 24 60 60/1 2 3 -> 3723 2/1 1 0 1 -> 13 179 | I\\ encode 24 60 60\\3723 -> 1 2 3 2\\13 -> 1 1 0 1 180 | i': window 3':"abcdef" -> ("abc";"bcd";"cde";"def") 181 | i f': stencil 3{x,"."}':"abcde" -> ("abc.";"bcd.";"cde.") 182 | F': eachprior -':12 13 11 17 14 -> 12 1 -2 6 -3 183 | x F': seeded ': 10-':12 13 11 17 14 -> 2 1 -2 6 -3 184 | x F/: eachright 1 2*/:3 4 -> (3 6;4 8) 185 | x F\\: eachleft 1 2*\\:3 4 -> (3 4;6 8) 186 | -------------------------------------------------------------------------------- 187 | Grammar 188 | : SET RETURN :[c;t;f] COND 189 | + add flip 190 | - subtract negate ' each|slide|bin 191 | * multiply first / fold|join |dec|comment 192 | % divide sqrt \\ scan|split|enc|trace 193 | ! mod|dict enum|key ': eachprior 194 | & min|and where /: eachright 195 | | max|or reverse \\: eachleft 196 | < less ascend 197 | > more descend / 198 | = equal group multiline comment 199 | ~ match not \\ 200 | , concat enlist 201 | ^ without null 0: lines i/o 202 | # reshape length 1: bytes i/o 203 | _ drop|cut floor 204 | $ cast string 205 | ? find|rnd uniq ?[a;i;b] splice 206 | @ apply(1) type @[x;i;[f;]y] amend 207 | . apply(n) eval .[x;i;[f;]y] drill 208 | grammar: E:E;e|e e:nve|te| t:n|v v:tA|V n:t[E]|(E)|{E}|N 209 | limits: 8 args, 16 locals, 256 bytecode, 2048 stack 210 | """ 211 | 212 | struct HelpCard: View { 213 | let key: String 214 | let value: String 215 | @State var isExpanded: Bool = false 216 | 217 | var body: some View { 218 | VStack { 219 | Button(action: { self.isExpanded.toggle() }) { 220 | Text(key) 221 | .font(.headline) 222 | .frame(maxWidth: .infinity, alignment: .leading) 223 | .padding() 224 | } 225 | if isExpanded { 226 | Text(value) 227 | .font(.monospaced(.body)()) 228 | .lineLimit(nil) 229 | .fixedSize(horizontal: false, vertical: true) 230 | .padding() 231 | .frame(maxWidth: .infinity, alignment: .leading) 232 | } 233 | } 234 | .background(Color.gray.opacity(0.2)) 235 | .cornerRadius(8) 236 | .padding([.top, .horizontal]) 237 | } 238 | } 239 | 240 | struct HelpView: View { 241 | let helpDict: [String: String] 242 | 243 | init() { 244 | helpDict = toDict(h: kh) 245 | } 246 | 247 | var body: some View { 248 | ScrollView { 249 | ForEach(helpDict.keys.sorted(), id: \.self) { key in 250 | if let value = helpDict[key] { 251 | HelpCard(key: key, value: value) 252 | } 253 | } 254 | } 255 | } 256 | } 257 | 258 | #Preview { 259 | HelpView() 260 | } 261 | -------------------------------------------------------------------------------- /Beacon/Views/HistoryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryView.swift 3 | // Arrayground 4 | // 5 | 6 | import Foundation 7 | import SwiftUI 8 | 9 | struct HistoryView: View { 10 | var index: Int 11 | var historyItem: Entry 12 | var curBuffer: String 13 | var onMySubmit: (String) -> Void 14 | @Binding var input: String 15 | @Binding var ephemerals: [Int: [String]] 16 | @Binding var editType: Behavior 17 | @ObservedObject var viewModel: HistoryModel 18 | @Environment(\.colorScheme) var scheme: ColorScheme 19 | 20 | var body: some View { 21 | VStack(alignment: .leading) { 22 | if editType == Behavior.inlineEdit { 23 | TextField("Source", text: Binding( 24 | get: { 25 | if index < viewModel.history[curBuffer, default: []].count { 26 | return self.viewModel.history[curBuffer, default: []][index].src 27 | } else { 28 | return "" 29 | } 30 | }, 31 | set: { 32 | if index < viewModel.history[curBuffer, default: []].count { 33 | self.viewModel.history[curBuffer, default: []][index].src = $0 34 | ephemerals[index, default: []].append($0) 35 | } 36 | } 37 | )) 38 | .onSubmit { 39 | onMySubmit(self.viewModel.history[curBuffer, default: []][index].src) 40 | for k in ephemerals.keys { 41 | self.viewModel.history[curBuffer, default: []][k].src = ephemerals[k, default: []].first! 42 | } 43 | ephemerals = [:] // reset all virtual textfield edits 44 | } 45 | .font(Font.custom("BQN386 Unicode", size: 18)) 46 | .foregroundColor(.blue) 47 | } else if editType == Behavior.duplicate { 48 | VStack(spacing: 1) { 49 | ForEach(Array(historyItem.tokens.enumerated()), id: \.offset) { _, line in 50 | HStack(spacing: 0) { 51 | ForEach(line, id: \.id) { token in 52 | let isDarkTheme = (scheme == .dark) 53 | let col = historyItem.lang == Language.k ? colorK(token.type, isDarkTheme) : colorBQN(token.type) 54 | Text(token.value) 55 | .lineLimit(1) // HACK to stop long tokens from messing alignment 56 | .foregroundColor(col) 57 | .font(Font.custom("BQN386 Unicode", size: 18)) 58 | .onTapGesture { 59 | self.input = historyItem.src 60 | } 61 | } 62 | }.frame(maxWidth: .infinity, alignment: .leading) 63 | } 64 | } 65 | } 66 | Text("\(trimLongText(historyItem.out))") 67 | .foregroundStyle(.primary.opacity(0.8)) 68 | .font(Font.custom("BQN386 Unicode", size: 18)) 69 | .foregroundColor(historyItem.out.starts(with: "Error:") || historyItem.out.starts(with: "\"Error:") ? .red : .primary) 70 | .multilineTextAlignment(.leading) 71 | .onTapGesture { 72 | self.input = historyItem.out 73 | } 74 | }.frame(maxWidth: .infinity, alignment: .leading) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Fonts/BQN386.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x86y/arrayground/996108fc1d4024570b9cf75fdabec0ea3ba85184/Fonts/BQN386.ttf -------------------------------------------------------------------------------- /Keyboard/BQNInputSetProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BQNInputSetProvider.swift 3 | // KeyboardUnicode 4 | // 5 | // Created by Daniel Saidi on 2022-02-04. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import KeyboardKit 10 | 11 | /** 12 | This demo-specific input set provider can be used to create 13 | a custom, Unicode-based input set. 14 | 15 | ``KeyboardViewController`` registers it to show how you can 16 | register and use a custom input set provider. 17 | 18 | Note that for some Unicode keyboards, it makes little sense 19 | to have a numeric and a symbolic keyboard. If so, you could 20 | create a custom layout that removes all these switcher keys. 21 | */ 22 | class BQNSP: InputSetProvider { 23 | let baseProvider = EnglishInputSetProvider() 24 | 25 | var alphabeticInputSet: AlphabeticInputSet { 26 | AlphabeticInputSet(rows: [ 27 | // .init(lowercased: "‿[{+-×÷}]", uppercased: "‿[{+-×÷}]"), 28 | .init(lowercased: "⌽𝕨∊↑∧⊔⊏⊐π←", uppercased: "⌽𝕎⍷↑∧⍋⊔⊑⊐π←"), 29 | .init(lowercased: "⍉𝕤↕𝕗𝕘⊸∘○⟜", uppercased: "⍉𝕊↕𝔽𝔾«∘⌾»"), 30 | .init( 31 | phoneLowercased: "⥊𝕩↓∨⌊≡∾", 32 | phoneUppercased: "⋈𝕏↓⍒⌈≢≤", 33 | padLowercased: "⥊𝕩↓∨⌊≡∾≤≍", 34 | padUppercased: "⋈𝕏↓⍒⌈≢≍≥≥" 35 | ), 36 | ]) 37 | } 38 | 39 | var numericInputSet: NumericInputSet { 40 | NumericInputSet(rows: [ 41 | .init(chars: "1234567890"), 42 | .init(phone: "˘¨⁼⌜´)&@”", pad: "1"), 43 | .init(phone: ".,?!’", pad: "%-+=/;:!?"), 44 | ]) 45 | } 46 | 47 | var symbolicInputSet: SymbolicInputSet { 48 | baseProvider.symbolicInputSet 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Keyboard/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | -------------------------------------------------------------------------------- /Keyboard/KbViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KbViewController.swift 3 | // KeyboardKit 4 | // 5 | 6 | import KeyboardKit 7 | import SwiftUI 8 | 9 | class KbViewController: KeyboardInputViewController { 10 | override func viewDidLoad() { 11 | keyboardContext.setLocale(.english) 12 | keyboardContext.keyboardDictationReplacement = .keyboardType(.emojis) 13 | autocompleteProvider = FakeAutocompleteProvider() 14 | keyboardStyleProvider = DemoKeyboardAppearance(keyboardContext: keyboardContext) 15 | keyboardActionHandler = DemoKeyboardActionHandler(inputViewController: self) 16 | keyboardLayoutProvider = DemoKeyboardLayoutProvider( 17 | keyboardContext: keyboardContext, 18 | inputSetProvider: inputSetProvider 19 | ) 20 | super.viewDidLoad() 21 | } 22 | 23 | override func viewWillSetupKeyboard() { 24 | super.viewWillSetupKeyboard() 25 | setup { KeyboardView(controller: $0) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Keyboard/KeyboardActionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardActionHandler.swift 3 | // 4 | 5 | import KeyboardKit 6 | import UIKit 7 | 8 | class DemoKeyboardActionHandler: StandardKeyboardActionHandler { 9 | // MARK: - Overrides 10 | 11 | override func action( 12 | for gesture: KeyboardGesture, 13 | on action: KeyboardAction 14 | ) -> KeyboardAction.GestureAction? { 15 | let standard = super.action(for: gesture, on: action) 16 | switch gesture { 17 | case .longPress: return longPressAction(for: action) ?? standard 18 | default: return standard 19 | } 20 | } 21 | 22 | // MARK: - Custom actions 23 | 24 | func longPressAction(for action: KeyboardAction) -> KeyboardAction.GestureAction? { 25 | switch action { 26 | case let .image(_, _, imageName): return { [weak self] _ in self?.saveImage(named: imageName) } 27 | default: return nil 28 | } 29 | } 30 | 31 | func tapAction(for action: KeyboardAction) -> KeyboardAction.GestureAction? { 32 | switch action { 33 | case let .image(_, _, imageName): return { [weak self] _ in self?.copyImage(named: imageName) } 34 | default: return nil 35 | } 36 | } 37 | 38 | // MARK: - Functions 39 | 40 | func alert(_: String) { 41 | print("Implement alert functionality if you want, or just place a breakpoint here.") 42 | } 43 | 44 | func copyImage(named imageName: String) { 45 | guard let image = UIImage(named: imageName) else { return } 46 | guard keyboardContext.hasFullAccess else { return alert("You must enable full access to copy images.") } 47 | guard image.copyToPasteboard() else { return alert("The image could not be copied.") } 48 | alert("Copied to pasteboard!") 49 | } 50 | 51 | func saveImage(named imageName: String) { 52 | guard let image = UIImage(named: imageName) else { return } 53 | guard keyboardContext.hasFullAccess else { return alert("You must enable full access to save images.") } 54 | image.saveToPhotos(completion: handleImageDidSave) 55 | alert("Saved to photos!") 56 | } 57 | } 58 | 59 | private extension DemoKeyboardActionHandler { 60 | func handleImageDidSave(withError error: Error?) { 61 | if error == nil { alert("Saved!") } 62 | else { alert("Failed!") } 63 | } 64 | } 65 | 66 | private extension UIImage { 67 | func copyToPasteboard(_ pasteboard: UIPasteboard = .general) -> Bool { 68 | guard let data = pngData() else { return false } 69 | pasteboard.setData(data, forPasteboardType: "public.png") 70 | return true 71 | } 72 | } 73 | 74 | private extension UIImage { 75 | func saveToPhotos(completion: @escaping (Error?) -> Void) { 76 | ImageService.default.saveImageToPhotos(self, completion: completion) 77 | } 78 | } 79 | 80 | private class ImageService: NSObject { 81 | public typealias Completion = (Error?) -> Void 82 | 83 | public private(set) static var `default` = ImageService() 84 | 85 | private var completions = [Completion]() 86 | 87 | public func saveImageToPhotos(_ image: UIImage, completion: @escaping (Error?) -> Void) { 88 | completions.append(completion) 89 | UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveImageToPhotosDidComplete), nil) 90 | } 91 | 92 | @objc func saveImageToPhotosDidComplete(_: UIImage, error: NSError?, contextInfo _: UnsafeRawPointer) { 93 | guard completions.count > 0 else { return } 94 | completions.removeFirst()(error) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Keyboard/KeyboardAppearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardAppearance.swift 3 | // Keyboard 4 | // 5 | // 6 | 7 | import KeyboardKit 8 | import SwiftUI 9 | 10 | class DemoKeyboardAppearance: StandardKeyboardStyleProvider { 11 | override func buttonStyle( 12 | for action: KeyboardAction, 13 | isPressed: Bool 14 | ) -> KeyboardStyle.Button { 15 | var style = super.buttonStyle(for: action, isPressed: isPressed) 16 | // style.cornerRadius = 10 17 | // style.backgroundColor = .blue 18 | // style.foregroundColor = .yellow 19 | if action != KeyboardAction.space && action != KeyboardAction.backspace && action != KeyboardAction.primary(.return) { 20 | style.font = KeyboardFont.custom("BQN386 Unicode", size: 24) 21 | } 22 | return style 23 | } 24 | 25 | override var inputCalloutStyle: KeyboardStyle.InputCallout { 26 | var style = super.inputCalloutStyle 27 | style.font = KeyboardFont.custom("BQN386 Unicode", size: 24) 28 | return style 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Keyboard/KeyboardLayoutProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardLayoutProvider.swift 3 | // 4 | // 5 | 6 | import KeyboardKit 7 | 8 | class DemoKeyboardLayoutProvider: StandardKeyboardLayoutProvider { 9 | override func keyboardLayout(for context: KeyboardContext) -> KeyboardLayout { 10 | let layout = super.keyboardLayout(for: context) 11 | guard layout.hasRows && context.hasMultipleLocales else { return layout } 12 | layout.tryInsertLocaleSwitcher() 13 | return layout 14 | } 15 | } 16 | 17 | private extension KeyboardContext { 18 | var hasMultipleLocales: Bool { 19 | locales.count > 1 20 | } 21 | } 22 | 23 | private extension KeyboardLayout { 24 | var bottomRowIndex: Int { 25 | itemRows.count - 1 26 | } 27 | 28 | var hasRows: Bool { 29 | itemRows.count > 0 30 | } 31 | 32 | var localeSwitcherTemplate: KeyboardLayoutItem? { 33 | itemRows[bottomRowIndex].first { $0.action.isSystemAction } 34 | } 35 | 36 | func tryInsertLocaleSwitcher() { 37 | guard let template = localeSwitcherTemplate else { return } 38 | let switcher = KeyboardLayoutItem(action: .nextLocale, size: template.size, insets: template.insets) 39 | itemRows.insert(switcher, after: .space, atRow: bottomRowIndex) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Keyboard/KeyboardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardView.swift 3 | // KeyboardKitDemo 4 | // 5 | 6 | import KeyboardKit 7 | import SwiftUI 8 | 9 | struct KeyboardView: View { 10 | @EnvironmentObject 11 | private var autocompleteContext: AutocompleteContext 12 | unowned var controller: KeyboardInputViewController 13 | @EnvironmentObject 14 | private var keyboardContext: KeyboardContext 15 | @EnvironmentObject 16 | private var keyboardTextContext: KeyboardTextContext 17 | 18 | var body: some View { 19 | VStack(spacing: 0) { 20 | if keyboardContext.keyboardType != .emojis { 21 | autocompleteToolbar 22 | } 23 | SystemKeyboard( 24 | controller: controller, 25 | autocompleteToolbar: .none 26 | ) 27 | } 28 | } 29 | } 30 | 31 | // MARK: - Private Views 32 | 33 | private extension KeyboardView { 34 | var autocompleteToolbar: some View { 35 | AutocompleteToolbar( 36 | suggestions: autocompleteContext.suggestions, 37 | locale: keyboardContext.locale, 38 | suggestionAction: controller.insertAutocompleteSuggestion 39 | ).font(Font.custom("BQN386 Unicode", size: 24)).opacity(keyboardContext.prefersAutocomplete ? 1 : 0) // Still allocate height 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Keyboard/KeyboardViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardViewController.swift 3 | // 4 | 5 | import KeyboardKit 6 | import SwiftUI 7 | 8 | class KeyboardViewController: KbViewController { 9 | override func viewDidLoad() { 10 | inputSetProvider = BQNSP() 11 | keyboardFeedbackSettings.enableHapticFeedback() 12 | super.viewDidLoad() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |5 | 28 |NSExtension 6 |7 | 23 |NSExtensionAttributes 8 |9 | 18 |IsASCIICapable 10 |11 | PrefersRightToLeft 12 |13 | PrimaryLanguage 14 |en-US 15 |RequestsOpenAccess 16 |17 | NSExtensionPointIdentifier 19 |com.apple.keyboard-service 20 |NSExtensionPrincipalClass 21 |$(PRODUCT_MODULE_NAME).KeyboardViewController 22 |UIAppFonts 24 |25 | 27 |Fonts/BQN386.ttf 26 |iBeacon
2 |3 | A (mostly) SwiftUI application to interface with CBQN and ngn/k libraries into a (mostly) working repl. 4 |
5 | 6 | Features: 7 | - ngn/k support 8 | - BQN support (custom keyboard shipped with the app, \KEY combination also works for transliteration) 9 | - bqnlibs shipped out of the box 10 | - Buffer manager 11 | - Select between k/bqn and color themes by typing `config` 12 | - Type `\\:` to see all k help pages and `\:`, `\h`, `\'`,\\\`
, `\+` to see the single help pages 13 | - Toolbar for adverbs/modifiers and buffers/help/setting buttons 14 | 15 |16 |
22 |17 |
18 |
19 |
20 |
21 |
23 |
26 | 27 | [WIP] Desired features: 28 | - Always blinking cursor on the main input 29 | - Better BQN keyboard design 30 | - Http client capabilities 31 | - Explore more native API's 32 | - Sliding through the buffers with gestures 33 | --------------------------------------------------------------------------------24 |
25 |