sourcelibs/Radix/src/Radix.xtl
1⍝# Radix: an eight-digit calculator in any radix (hexadecimal, octal, decimal): the specification HEX1000's ROM program must meet
2⍝# Import it with an alias of your choice: "rx:" u_se< "Radix".
3⍝# Put libs/Radix/src on XETAL_PATH ("just path"); the reference is libs/Radix/docs.
4⍝# Names with l: are exported; the others are private to this file.
5⍝#
6⍝# A number is eight digits in radix r (16, 8 or 10), as a calculator
7⍝# chip keeps it: a whole number from 0 to r^8 - 1. Arithmetic wraps
8⍝# like an odometer, modulo r^8: in hexadecimal FFFFFFFF + 1 is 0 and
9⍝# 5 - 7 is FFFFFFFE (the r's complement a digit-serial subtract
10⍝# leaves). The radix is the first argument of every function, bound
11⍝# once by applying the function to it: u:k_ey := rx:k_ey 16.
12
13⍝## Numbers as digits
14
15⍝# Width: the digits a number has (the display's).
16⍝# >> "rx:" u_se< "Radix"
17⍝# >> rx:Width
18⍝# 8
19ˡWidth ← 8
20
21⍝# m_odulus r: r^8, one more than the largest number.
22⍝# >> "rx:" u_se< "Radix"
23⍝# >> rx:m_odulus 16
24⍝# 4294967296
25ˡm̲odulus ← { r → r ^ ˡWidth }
26
27⍝# r d_igits n: the eight digits of n, most significant first (the
28⍝# display's order); n is wrapped first.
29⍝# >> "rx:" u_se< "Radix"
30⍝# >> 16 rx:d_igits 255
31⍝# 0 0 0 0 0 0 15 15
32ˡd̲igits ← { r n → (ˡWidth r̲eshape r) e̲ncode n m̲od ˡm̲odulus r }
33
34⍝# r v_alue ds: the number eight digits (or any digits) stand for.
35⍝# >> "rx:" u_se< "Radix"
36⍝# >> 16 rx:v_alue 1 0 0
37⍝# 256
38ˡv̲alue ← { r ds → r d̲ecode ds }
39
40⍝# r w_rap n: n modulo r^8 (an odometer).
41⍝# >> "rx:" u_se< "Radix"
42⍝# >> 16 rx:w_rap -1
43⍝# 4294967295
44ˡw̲rap ← { r n → n m̲od ˡm̲odulus r }
45
46⍝## The keys
47
48⍝# Key codes: the digits 0 to 15 are themselves, then + - * = and X
49⍝# (clear: C is a hexadecimal digit); Keys is the text whose place,
50⍝# less 1, is the code.
51Keys ← ⎕D c̲at (6 t̲ake ⎕A) c̲at "+-*=X"
52
53⍝# KeyPlus: the code of +.
54⍝# >> "rx:" u_se< "Radix"
55⍝# >> rx:KeyPlus
56⍝# 16
57ˡKeyPlus ← 16
58
59⍝# KeyMinus: the code of -.
60⍝# >> "rx:" u_se< "Radix"
61⍝# >> rx:KeyMinus
62⍝# 17
63ˡKeyMinus ← 17
64
65⍝# KeyTimes: the code of *.
66⍝# >> "rx:" u_se< "Radix"
67⍝# >> rx:KeyTimes
68⍝# 18
69ˡKeyTimes ← 18
70
71⍝# KeyEquals: the code of =.
72⍝# >> "rx:" u_se< "Radix"
73⍝# >> rx:KeyEquals
74⍝# 19
75ˡKeyEquals ← 19
76
77⍝# KeyClear: the code of X, clear.
78⍝# >> "rx:" u_se< "Radix"
79⍝# >> rx:KeyClear
80⍝# 20
81ˡKeyClear ← 20
82
83⍝# k_eys text: the key codes of a text such as "12+34=" (spaces are
84⍝# skipped; any other character gives 21, which k_ey ignores).
85⍝# >> "rx:" u_se< "Radix"
86⍝# >> rx:k_eys "12+34="
87⍝# 1 2 16 3 4 19
88ˡk̲eys ← { t → (Keys i̲ndexOf (t ≠ f̲irst " ") r̲eplicate t) − 1 }
89
90⍝## The calculator: a step per key
91
92⍝# The state, five numbers: the accumulator, the entry, the pending
93⍝# operation (0 none, 1 +, 2 -, 3 *), the phase (0 after an operator,
94⍝# 1 entering digits, 2 after =) and the number displayed.
95⍝# >> "rx:" u_se< "Radix"
96⍝# >> rx:Reset
97⍝# 0 0 0 0 0
98ˡReset ← 0 0 0 0 0
99
100ʰa̲cc ← { s → 1 s̲elect s }
101ʰe̲ntry ← { s → 2 s̲elect s }
102ʰo̲p ← { s → 3 s̲elect s }
103ʰp̲hase ← { s → 4 s̲elect s }
104
105⍝# r a_lu ops: the operation ops[1] (1 +, 2 -, 3 *) of ops[2] and
106⍝# ops[3], wrapped (the operands as one vector: a function has two
107⍝# sides).
108⍝# >> "rx:" u_se< "Radix"
109⍝# >> 16 rx:a_lu 2 5 7
110⍝# 4294967294
111ˡa̲lu ← { r ops →
112 op ← f̲irst ops
113 a ← 2 s̲elect ops
114 b ← 3 s̲elect ops
115 r ˡw̲rap '+ r̲/ (op = 1 2 3) × (a + b) c̲at (a − b) c̲at r ʰt̲imes a c̲at b
116}
117
118⍝# r t_imes a b: a * b wrapped, without overflow: b split at r^4, so no
119⍝# partial product reaches r^12 (FFFFFFFF * FFFFFFFF itself is past a
120⍝# 64-bit Int).
121ʰt̲imes ← { r ab →
122 a ← f̲irst ab
123 b ← f̲irst 1 d̲rop ab
124 half ← r ^ 4
125 r ˡw̲rap (half × r ˡw̲rap a × b d̲iv half) + a × b m̲od half
126}
127
128⍝# The pending operation settled: the accumulator after it takes the
129⍝# entry (the entry itself when nothing is pending; the accumulator
130⍝# unchanged when no digit was entered since).
131ʰs̲ettle ← { r s →
132 1 ≠ ʰp̲hase s ? ʰa̲cc s
133 0 = ʰo̲p s ? ʰe̲ntry s
134 r ˡa̲lu (ʰo̲p s) c̲at (ʰa̲cc s) c̲at ʰe̲ntry s
135}
136
137⍝# r k_ey s k: the state after the key k (a step: the machine's ROM
138⍝# program must leave the same display). A digit not below the radix,
139⍝# or one that would make a ninth digit, is ignored.
140⍝# >> "rx:" u_se< "Radix"
141⍝# >> u:k_ey := rx:k_ey 16
142⍝# >> rx:Reset u:k_ey 10
143⍝# 0 10 0 1 10
144ˡk̲ey ← { r s k →
145 k ≥ 21 ? s
146 k = ˡKeyClear ? ˡReset
147 k = ˡKeyEquals ? (r ʰs̲ettle s) c̲at 0 0 2 c̲at r ʰs̲ettle s
148 k ≥ ˡKeyPlus ? (r ʰs̲ettle s) c̲at 0 c̲at (k − 15) c̲at 0 c̲at r ʰs̲ettle s
149 k ≥ r ? s
150 fresh ← 2 = ʰp̲hase s
151 old ← (n̲ot fresh) × (1 = ʰp̲hase s) × ʰe̲ntry s
152 old ≥ r ^ ˡWidth − 1 ? s
153 entry ← k + r × old
154 ((n̲ot fresh) × ʰa̲cc s) c̲at entry c̲at ((n̲ot fresh) × ʰo̲p s) c̲at 1 c̲at entry
155}
156
157⍝# d_isplay s: the number displayed.
158⍝# >> "rx:" u_se< "Radix"
159⍝# >> rx:d_isplay 70 0 0 2 70
160⍝# 70
161ˡd̲isplay ← { s → 5 s̲elect s }
162
163⍝# r s_creen s: the display as eight characters: the digits, leading
164⍝# zeros blank (zero itself shows one 0).
165⍝# >> "rx:" u_se< "Radix"
166⍝# >> 16 rx:s_creen 0 0 0 0 305419896
167⍝# 12345678
168ˡs̲creen ← { r s →
169 ds ← r ˡd̲igits ˡd̲isplay s
170 blank ← ('∧ s̲\ 0 = 7 t̲ake ds) c̲at 0
171 (1 + (1 − blank) × 1 + ds) s̲elect " " c̲at 16 t̲ake Keys
172}
173
174⍝# r c_alc text: the screen after the keys of a text, from Reset:
175⍝# 16 rx:c_alc "12+34=" is " 46".
176⍝# >> "rx:" u_se< "Radix"
177⍝# >> 16 rx:c_alc "5-7="
178⍝# FFFFFFFE
179ˡc̲alc ← { r t →
180 s ← r ˡr̲un ˡk̲eys t
181 r ˡs̲creen s
182}
183
184⍝# r r_un keys: the state after every key, from Reset (the harness: a
185⍝# loop over the keys, as Clocked's).
186⍝# >> "rx:" u_se< "Radix"
187⍝# >> 16 rx:r_un rx:k_eys "12+34="
188⍝# 70 0 0 2 70
189ˡr̲un ← { r keys →
190 (n, s) ← (t̲ally keys) '{ (i, s) → (i + 1, (r ˡk̲ey s)_ i s̲elect keys) } p̲ower (1, ˡReset)
191 s
192}