1 //
2 //  Nga.swift
3 //
4 //  Nga is the virtual machine at the heart
5 //  of RETRO. It provides a MISC based dual
6 //  stack processor that's capable of hosting
7 //  RETRO or other environments.
8 //
9 //  This is a reimplementation in Swift. I've
10 //  held off doing this until now, but as I
11 //  plan to continue using Apple computers and
12 //  iPads, it's becoming clear that they see
13 //  this as the future, and it'll become more
14 //  difficult to justify holding out on
15 //  Objective-C going forward.
16 //
17 //  So here we go...
18 //
19 //  Copyright 2020, Charles Childers
20 //
21 
22 import Foundation
23 
24 // Limits
25 let imageSize: Int = 512000
26 let stackSize: Int = 2048
27 let CELLMAX = Int64.max - 1
28 let CELLMIN = Int64.min
29 
30 // Notes:
31 //   While Nga is designed as a 32-bit system,
32 //   this implementation is 64-bit internally
33 //   to allow greater numeric range.
34 
35 // I'm adding some extensions to existing types
36 // for easier access to individual characters
37 // in strings and bytes in integers.
38 
39 extension StringProtocol {
40     var asciiValues: [UInt8] { compactMap(\.asciiValue) }
41 }
42 
43 extension FixedWidthInteger where Self: SignedInteger {
44     var bytes: [Int8] {
45         var _endian = littleEndian
46         let bytePtr = withUnsafePointer(to: &_endian) {
47             $0.withMemoryRebound(to: Int8.self, capacity: MemoryLayout<Self>.size) {
48                 UnsafeBufferPointer(start: $0, count: MemoryLayout<Self>.size)
49             }
50         }
51         return [Int8](bytePtr)
52     }
53 }
54 
55 // I implement a stack class here for handling
56 // the details of the stacks. Stacks in Nga are
57 // LIFO, holding only signed integer values.
58 
59 class Stack {
60     private var data = [Int64](repeating: 0, count: stackSize)
61     private var sp: Int = 0
62 
tosnull63     public func tos() -> Int64 {
64         return data[sp]
65     }
66 
nosnull67     public func nos() -> Int64 {
68         return data[sp - 1]
69     }
70 
pushnull71     public func push(_ n: Int64) {
72         sp += 1
73         data[sp] = n
74     }
75 
popnull76     public func pop() -> Int64 {
77         let v = data[sp]
78         data[sp] = 0
79         sp -= 1
80         return v
81     }
82 
dropnull83     public func drop() {
84         data[sp] = 0
85         sp -= 1
86     }
87 
swapnull88     public func swap() {
89         let a = tos()
90         let b = nos()
91         data[sp] = b
92         data[sp - 1] = a
93     }
94 
depthnull95     public func depth() -> Int {
96         return sp
97     }
98 
itemnull99     public func item(_ n: Int) -> Int64 {
100         return data[n]
101     }
102 }
103 
104 // Instruction Pointer and Memory
105 // I'm adding some extra padding at EOM for now.
106 // This may be changed in the future.
107 
108 var ip: Int = 0
109 var memory = [Int64](repeating: 0, count: imageSize + 1024)
110 
111 // Create the stacks
112 
113 var data = Stack()
114 var address = Stack()
115 
116 // Now, I implement the instructions. Each gets
117 // a dedicated function.
118 
inst_nonull119 func inst_no() {
120 }
121 
inst_linull122 func inst_li() {
123     ip += 1
124     data.push(memory[ip])
125 }
126 
inst_dunull127 func inst_du() {
128     data.push(data.tos())
129 }
130 
inst_drnull131 func inst_dr() {
132     data.drop()
133 }
134 
inst_swnull135 func inst_sw() {
136     data.swap()
137 }
138 
inst_punull139 func inst_pu() {
140     address.push(data.pop())
141 }
142 
inst_ponull143 func inst_po() {
144     data.push(address.pop())
145 }
146 
inst_junull147 func inst_ju() {
148     ip = Int(data.pop() - 1)
149 }
150 
inst_canull151 func inst_ca() {
152     address.push(Int64(ip))
153     ip = Int(data.pop() - 1)
154 }
155 
inst_ccnull156 func inst_cc() {
157     let dest = data.pop()
158     let flag = data.pop()
159     if (flag != 0) {
160         address.push(Int64(ip))
161         ip = Int(dest - 1)
162     }
163 }
164 
inst_renull165 func inst_re() {
166     ip = Int(address.pop())
167 }
168 
inst_eqnull169 func inst_eq() {
170     let tos = data.pop()
171     let nos = data.pop()
172     data.push((nos == tos) ? -1 : 0)
173 }
174 
inst_nenull175 func inst_ne() {
176     let tos = data.pop()
177     let nos = data.pop()
178     data.push((nos != tos) ? -1 : 0)
179 }
180 
inst_ltnull181 func inst_lt() {
182     let tos = data.pop()
183     let nos = data.pop()
184     data.push((nos < tos) ? -1 : 0)
185 }
186 
inst_gtnull187 func inst_gt() {
188     let tos = data.pop()
189     let nos = data.pop()
190     data.push((nos > tos) ? -1 : 0)
191 }
192 
inst_fenull193 func inst_fe() {
194     let target = data.pop()
195     switch (target) {
196     case -1:
197         data.push(Int64(data.depth()))
198     case -2:
199         data.push(Int64(address.depth()))
200     case -3:
201         data.push(Int64(imageSize))
202     case -4:
203         data.push(CELLMIN)
204     case -5:
205         data.push(CELLMAX)
206     default:
207         data.push(memory[Int(target)])
208     }
209 }
210 
inst_stnull211 func inst_st() {
212     let addr = data.pop()
213     let value = data.pop()
214     memory[Int(addr)] = value;
215 }
216 
inst_adnull217 func inst_ad() {
218     let tos = data.pop()
219     let nos = data.pop()
220     data.push(nos + tos)
221 }
222 
inst_sunull223 func inst_su() {
224     let tos = data.pop()
225     let nos = data.pop()
226     data.push(nos - tos)
227 }
228 
inst_munull229 func inst_mu() {
230     let tos = data.pop()
231     let nos = data.pop()
232     data.push(nos * tos)
233 }
234 
inst_dinull235 func inst_di() {
236     let a = data.pop()
237     let b = data.pop()
238     data.push(b % a)
239     data.push(b / a)
240 }
241 
inst_annull242 func inst_an() {
243     let tos = data.pop()
244     let nos = data.pop()
245     data.push(tos & nos)
246 }
247 
inst_ornull248 func inst_or() {
249     let tos = data.pop()
250     let nos = data.pop()
251     data.push(nos | tos)
252 }
253 
inst_xonull254 func inst_xo() {
255     let tos = data.pop()
256     let nos = data.pop()
257     data.push(nos ^ tos)
258 }
259 
inst_shnull260 func inst_sh() {
261     let tos = data.pop()
262     let nos = data.pop()
263     if (tos < 0) {
264         data.push(nos << (tos * -1))
265     }
266     else {
267         if (nos < 0 && tos > 0) {
268             data.push(nos >> tos | ~(~0 >> tos))
269         }
270         else {
271             data.push(nos >> tos)
272         }
273     }
274 }
275 
inst_zrnull276 func inst_zr() {
277     if (data.tos() == 0) {
278         data.drop()
279         ip = Int(address.pop())
280     }
281 }
282 
inst_hanull283 func inst_ha() {
284     ip = imageSize;
285 }
286 
inst_ienull287 func inst_ie() {
288     data.push(1)
289 }
290 
inst_iqnull291 func inst_iq() {
292     data.drop()
293     data.push(0)
294     data.push(0)
295 }
296 
inst_iinull297 func inst_ii() {
298     data.drop()
299     let v = UnicodeScalar(Int(data.pop())) ?? UnicodeScalar(32)
300     print(Character(v!), terminator: "")
301 }
302 
303 // With those done, I turn to handling the
304 // instructions.
305 
306 // In C, I use a jump table for this. I don't
307 // know how to do this in Swift.
308 
ngaProcessOpcodenull309 func ngaProcessOpcode(opcode: Int64) {
310     switch opcode {
311     case  0:
312         ()
313     case  1:
314         inst_li()
315     case  2:
316         inst_du()
317     case  3:
318         inst_dr()
319     case  4:
320         inst_sw()
321     case  5:
322         inst_pu()
323     case  6:
324         inst_po()
325     case  7:
326         inst_ju()
327     case  8:
328         inst_ca()
329     case  9:
330         inst_cc()
331     case  10:
332         inst_re()
333     case  11:
334         inst_eq()
335     case  12:
336         inst_ne()
337     case  13:
338         inst_lt()
339     case  14:
340         inst_gt()
341     case  15:
342         inst_fe()
343     case  16:
344         inst_st()
345     case  17:
346         inst_ad()
347     case  18:
348         inst_su()
349     case  19:
350         inst_mu()
351     case  20:
352         inst_di()
353     case  21:
354         inst_an()
355     case  22:
356         inst_or()
357     case  23:
358         inst_xo()
359     case  24:
360         inst_sh()
361     case  25:
362         inst_zr()
363     case  26:
364         inst_ha()
365     case  27:
366         inst_ie()
367     case  28:
368         inst_iq()
369     case  29:
370         inst_ii()
371     default:
372         inst_ha()
373     }
374 }
375 
376 // Each opcode can have up to four instructions
377 // This validates them, letting us know if it's
378 // safe to run them.
379 
ngaValidatePackedOpcodesnull380 func ngaValidatePackedOpcodes(_ opcode: Int64) -> Bool {
381     var valid = true
382     for inst in Int32(opcode).bytes {
383         if !(inst >= 0 && inst <= 29) {
384             valid = false
385         }
386     }
387     return valid
388 }
389 
390 // This will process an opcode bundle
391 
ngaProcessPackedOpcodesnull392 func ngaProcessPackedOpcodes(_ opcode: Int64) {
393     for inst in Int32(opcode).bytes {
394         ngaProcessOpcode(opcode: Int64(inst))
395     }
396 }
397 
398 // For debugging purposes
399 
dumpnull400 func dump() {
401     for i in 0 ... data.depth() {
402         if i == data.depth() {
403             print("TOS")
404         }
405         print(Int(data.item(i)))
406     }
407 }
408 
409 // Interfacing
410 
extractStringnull411 func extractString(at: Int) -> String {
412     var s: String = ""
413     var next = at
414     while memory[next] != 0 {
415         let value = Int(memory[next])
416         let u = UnicodeScalar(value)
417         let char = Character(u!)
418         s.append(char)
419         next += 1
420     }
421     return s
422 }
423 
injectStringnull424 func injectString(_ s: String, to: Int) {
425     var i: Int = 0
426     for ch in s.asciiValues {
427         let n = Int64(ch)
428         memory[to + i] = n
429         memory[to + i + 1] = 0
430         i += 1
431     }
432 }
433 
434 // Run through an image, quiting when EOM
435 // is reached
436 
executenull437 func execute(_ word: Int) {
438     address.push(0)
439     ip = word
440     while ip < imageSize {
441 //        print("ip:", ip, "bundle", memory[ip], "insts", Int32(memory[ip]).bytes)
442         let opcode = memory[ip]
443         if ngaValidatePackedOpcodes(opcode) {
444             ngaProcessPackedOpcodes(opcode)
445         } else {
446             print("Error: invalid opcode")
447             ip = imageSize
448         }
449         ip += 1
450         if address.depth() == 0 {
451             ip = imageSize
452         }
453     }
454 }
455 
processnull456 func process() {
457     ip = 0
458     while ip < imageSize - 1 {
459         ngaProcessPackedOpcodes(memory[ip])
460         ip += 1
461     }
462 }
463 
464 // Tests
465 
466 loadImage()
467 
468 // Display the dictionary
469 var i = memory[2]
470 var interpret: Int = 0
471 while i != 0 {
472     let name = extractString(at: Int(i + 3))
473 //    print(name)
474     if name == "interpret" {
475         interpret = Int(memory[Int(i) + 1])
476     }
477     i = memory[Int(i)]
478 }
479 
480 
481 var done = false
482 print("RETRO (using nga.swift)")
483 while !done {
484     let code = readLine()
485     if code == "bye" {
486         done = true
487     } else {
488         injectString(code ?? "()", to: 1025)
489         data.push(1025)
490         execute(interpret)
491     }
492 }
493 dump()
494 
495