├── .gitignore ├── LICENSE ├── README.md ├── debug.go ├── dev.go ├── go.mod ├── go.sum ├── main.go ├── sym.go ├── uxn ├── exec.go ├── exec_test.go ├── op.go ├── op_test.go └── stack.go └── varvara ├── console.go ├── controller.go ├── datetime.go ├── file.go ├── gui.go ├── input.go ├── mouse.go ├── screen.go ├── system.go └── varvara.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | nux 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nux 2 | 3 | A Go implementation of the [Uxn](https://100r.co/site/uxn.html) virtual 4 | machine, by [nf](https://nf.wh3rd.net/). 5 | 6 | ## Features 7 | 8 | - Full support for Varvara's System, Console, Screen, 9 | Controller, Mouse, File, and Datetime devices. 10 | - Live-reloading and rebuilding of uxntal source (`-dev`). 11 | - An interactive debugger (`-debug`). 12 | - Runs on macOS, Linux, and Windows (mostly tested/developed on macOS). 13 | 14 | ## Todo 15 | 16 | - Implement the Audio device. 17 | 18 | ## Known issues 19 | 20 | - The File device is not well-tested, and likely has bugs. 21 | - The button events of the Mouse device somehow misfire. 22 | - Included source files are not watched by the `-dev` feature. 23 | - The GUI doesn't always shut down when exiting the debugger. 24 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "sort" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/gdamore/tcell/v2" 13 | "github.com/rivo/tview" 14 | 15 | "github.com/nf/nux/uxn" 16 | "github.com/nf/nux/varvara" 17 | ) 18 | 19 | const helpText = ` 20 | nux debugger commands and keyboard shortcuts: 21 | 22 | reset (F4) 23 | Halt the uxn program, reset it to its initial state from ROM, 24 | and run it. This unpauses uxn if it was paused, but preserves 25 | any break or debug points that are set. 26 | cont (F5) 27 | Resume uxn exeuction, if paused. 28 | step (F6) 29 | Pause uxn (if not already paused) and execute one instruction. 30 | halt (F7) 31 | Halt the uxn program. 32 | break ... 33 | Set break points at the given references (memory address or 34 | label). When uxn reaches the break point it pauses execution. 35 | rmbreak ... 36 | Unset the break points at the given references. 37 | watch[2] ... 38 | Add a watch for the given references. If the "watch2" variant 39 | is used then each reference is treated as a short, not a byte. 40 | rmwatch ... 41 | Remove any watches for the given references. 42 | mem [ref] 43 | View memory at the given reference, 44 | or PC if not reference given. 45 | exit (^C) 46 | Exit nux. 47 | help 48 | Print these instructions. :) 49 | 50 | Refrences may use a "*" suffix to select all labels that match a prefix. 51 | 52 | Commands may be abbreviated using just their first character ("r" for "reset", 53 | etc), with the exceptions of rmb/rmbreak, w2/watch2, and rmw/rmwatch. 54 | 55 | F8, F9, and F10 switch the main panel view between standard output (the 56 | default), the state log, and the memory viewer. 57 | ` 58 | 59 | type Debugger struct { 60 | Runner *varvara.Runner // Must be set before calling Run. 61 | Log io.Writer 62 | 63 | log *tview.TextView 64 | watch *tview.TextView 65 | ops *tview.TextView 66 | tick *tview.TextView 67 | state *tview.TextView 68 | stateLog *tview.TextView 69 | memory *tview.TextView 70 | input *tview.InputField 71 | right *tview.Flex 72 | cols *tview.Flex 73 | rows *tview.Flex 74 | app *tview.Application 75 | 76 | mu sync.Mutex 77 | syms *symbols 78 | breaks []symbol 79 | watches []watch 80 | started time.Time 81 | lastState time.Time 82 | memAddr *uint16 83 | } 84 | 85 | type watch struct { 86 | symbol 87 | short bool 88 | last uint16 89 | changed time.Time 90 | } 91 | 92 | func NewDebugger() *Debugger { 93 | d := &Debugger{ 94 | log: tview.NewTextView(). 95 | SetMaxLines(1000). 96 | ScrollToEnd(), 97 | watch: tview.NewTextView(). 98 | SetWrap(false). 99 | SetDynamicColors(true). 100 | SetTextAlign(tview.AlignRight), 101 | ops: tview.NewTextView(). 102 | SetWrap(false). 103 | SetDynamicColors(true), 104 | tick: tview.NewTextView(). 105 | SetWrap(false). 106 | SetTextAlign(tview.AlignRight), 107 | state: tview.NewTextView(). 108 | SetWrap(false). 109 | SetDynamicColors(true), 110 | stateLog: tview.NewTextView(). 111 | SetMaxLines(300). 112 | ScrollToEnd(). 113 | SetDynamicColors(true), 114 | memory: tview.NewTextView(). 115 | SetWrap(false). 116 | SetDynamicColors(true), 117 | input: tview.NewInputField(), 118 | right: tview.NewFlex(). 119 | SetDirection(tview.FlexRow), 120 | cols: tview.NewFlex(), 121 | rows: tview.NewFlex(). 122 | SetDirection(tview.FlexRow), 123 | app: tview.NewApplication(), 124 | } 125 | d.Log = d.log 126 | d.log.SetChangedFunc(func() { d.app.Draw() }) 127 | d.watch.SetBackgroundColor(tcell.ColorDarkBlue) 128 | d.tick.SetBackgroundColor(tcell.ColorDarkBlue) 129 | d.state.SetBackgroundColor(tcell.ColorDarkGrey) 130 | d.right. 131 | AddItem(d.watch, 0, 1, false). 132 | AddItem(d.tick, 2, 0, false) 133 | d.rows. 134 | AddItem(d.cols, 0, 1, false). 135 | AddItem(d.state, 3, 0, false). 136 | AddItem(d.input, 1, 0, true) 137 | d.app.SetRoot(d.rows, true) 138 | 139 | const ( 140 | logVisible = iota 141 | stateLogVisible 142 | memoryVisible 143 | ) 144 | setMainWindow := func(mode int) { 145 | switch mode { 146 | case logVisible: 147 | d.cols.Clear(). 148 | AddItem(d.ops, 35, 0, false). 149 | AddItem(d.log, 0, 1, false). 150 | AddItem(d.right, 25, 0, false) 151 | case stateLogVisible: 152 | d.cols.Clear(). 153 | AddItem(d.stateLog, 0, 1, false). 154 | AddItem(d.ops, 35, 0, false). 155 | AddItem(d.right, 25, 0, false) 156 | case memoryVisible: 157 | d.cols.Clear(). 158 | AddItem(d.ops, 35, 0, false). 159 | AddItem(d.memory, 0, 1, false). 160 | AddItem(d.right, 25, 0, false) 161 | } 162 | } 163 | setMainWindow(logVisible) 164 | 165 | d.app.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey { 166 | switch e.Key() { 167 | case tcell.KeyF4: 168 | d.Runner.Debug("reset", 0) 169 | case tcell.KeyF5: 170 | d.Runner.Debug("cont", 0) 171 | case tcell.KeyF6: 172 | d.Runner.Debug("step", 0) 173 | case tcell.KeyF7: 174 | d.Runner.Debug("halt", 0) 175 | case tcell.KeyF8: 176 | setMainWindow(logVisible) 177 | case tcell.KeyF9: 178 | setMainWindow(stateLogVisible) 179 | case tcell.KeyF10: 180 | setMainWindow(memoryVisible) 181 | default: 182 | return e 183 | } 184 | return nil 185 | }) 186 | d.input.SetAutocompleteFunc(func(t string) (entries []string) { 187 | if cmd, ref, ok := strings.Cut(t, " "); ok && ref != "" { 188 | others := "" 189 | switch cmd { 190 | case "m", "mem": 191 | // Only one arg permitted. 192 | case "b", "break", "rmb", "rmbreak", 193 | "w", "watch", "w2", "watch2", "rmw", "rmwatch": 194 | // Support completing later args. 195 | if i := strings.LastIndexByte(ref, ' '); i >= 0 { 196 | others = ref[:i+1] 197 | ref = ref[i+1:] 198 | } 199 | default: 200 | return 201 | } 202 | for _, s := range d.symbols().withLabelPrefix(ref) { 203 | entries = append(entries, cmd+" "+others+s.label) 204 | } 205 | return 206 | } 207 | return 208 | }) 209 | d.input.SetAutocompletedFunc(func(t string, index, src int) bool { 210 | if src != tview.AutocompletedNavigate { 211 | d.input.SetText(t) 212 | } 213 | return src == tview.AutocompletedEnter || src == tview.AutocompletedClick 214 | }) 215 | d.input.SetDoneFunc(func(key tcell.Key) { 216 | if key != tcell.KeyEnter { 217 | return 218 | } 219 | cmd := strings.TrimSpace(d.input.GetText()) 220 | if cmd == "" { 221 | return 222 | } 223 | d.input.SetText("") 224 | cmd, arg, _ := strings.Cut(cmd, " ") 225 | cmd, ok := parseCommand(cmd) 226 | if !ok { 227 | log.Printf("bad command %q", cmd) 228 | return 229 | } 230 | switch cmd { 231 | case "exit": 232 | d.app.Stop() 233 | case "help": 234 | log.Print(helpText) 235 | case "mem": 236 | args := strings.Fields(arg) 237 | switch len(args) { 238 | case 0: 239 | d.mu.Lock() 240 | d.memAddr = nil 241 | d.mu.Unlock() 242 | setMainWindow(memoryVisible) 243 | case 1: 244 | switch syms := d.symbols().resolve(arg); len(syms) { 245 | case 0: 246 | log.Printf("unknown reference %q", arg) 247 | case 1: 248 | addr := syms[0].addr 249 | d.mu.Lock() 250 | d.memAddr = &addr 251 | d.mu.Unlock() 252 | setMainWindow(memoryVisible) 253 | default: 254 | log.Printf("mem does not accept wildcards") 255 | } 256 | default: 257 | log.Printf("mem only takes one argument") 258 | } 259 | case "break", "rmbreak", "watch", "watch2", "rmwatch": 260 | args := strings.Fields(arg) 261 | for _, arg := range args { 262 | syms := d.symbols().resolve(arg) 263 | if len(syms) == 0 { 264 | log.Printf("unknown reference %q", arg) 265 | continue 266 | } 267 | for i := range syms { 268 | s := syms[i] 269 | switch cmd { 270 | case "break": 271 | d.Runner.Debug("break", s.addr) 272 | d.addBreak(s) 273 | case "rmbreak": 274 | d.Runner.Debug("rmbreak", s.addr) 275 | if d.rmBreak(s) { 276 | log.Printf("break removed: %s", s) 277 | } 278 | continue 279 | case "watch", "watch2": 280 | d.addWatch(s, strings.HasSuffix(cmd, "2")) 281 | case "rmwatch": 282 | if d.rmWatch(s) { 283 | log.Printf("watch removed: %s", s) 284 | } 285 | continue 286 | } 287 | log.Printf("%s set: %s", cmd, s) 288 | } 289 | } 290 | default: 291 | d.Runner.Debug(cmd, 0) 292 | } 293 | }) 294 | return d 295 | } 296 | 297 | func parseCommand(in string) (string, bool) { 298 | if out, ok := map[string]string{ 299 | "help": "help", "exit": "exit", 300 | "h": "halt", "halt": "halt", 301 | "r": "reset", "reset": "reset", 302 | "s": "step", "step": "step", 303 | "c": "cont", "cont": "cont", 304 | "b": "break", "break": "break", 305 | "rmb": "rmbreak", "rmbreak": "rmbreak", 306 | "w": "watch", "watch": "watch", 307 | "w2": "watch2", "watch2": "watch2", 308 | "rmw": "rmwatch", "rmwatch": "rmwatch", 309 | "m": "mem", "mem": "mem", 310 | }[in]; ok { 311 | return out, true 312 | } 313 | return in, false 314 | } 315 | 316 | func (d *Debugger) addBreak(s symbol) { 317 | d.rmBreak(s) 318 | d.mu.Lock() 319 | defer d.mu.Unlock() 320 | d.breaks = append(d.breaks, s) 321 | } 322 | 323 | func (d *Debugger) rmBreak(s symbol) (removed bool) { 324 | d.mu.Lock() 325 | defer d.mu.Unlock() 326 | for i, v := range d.breaks { 327 | if v == s { 328 | d.breaks = append(d.breaks[:i], d.breaks[i+1:]...) 329 | return true 330 | } 331 | } 332 | return false 333 | } 334 | 335 | func (d *Debugger) addWatch(s symbol, short bool) { 336 | d.rmWatch(s) // Prevent duplicate entries. 337 | d.mu.Lock() 338 | defer d.mu.Unlock() 339 | d.watches = append(d.watches, watch{symbol: s, short: short}) 340 | } 341 | 342 | func (d *Debugger) rmWatch(s symbol) (removed bool) { 343 | d.mu.Lock() 344 | defer d.mu.Unlock() 345 | for i, w := range d.watches { 346 | if w.symbol == s { 347 | d.watches = append(d.watches[:i], d.watches[i+1:]...) 348 | return true 349 | } 350 | } 351 | return false 352 | } 353 | 354 | func (d *Debugger) symbols() *symbols { 355 | d.mu.Lock() 356 | defer d.mu.Unlock() 357 | return d.syms 358 | } 359 | 360 | func (d *Debugger) SetSymbols(s *symbols) { 361 | d.mu.Lock() 362 | defer d.mu.Unlock() 363 | d.syms = s 364 | 365 | // Rewrite watch addresses as they may have changed. 366 | for i := 0; i < len(d.watches); { 367 | w := &d.watches[i] 368 | if w.label == "" { 369 | // Preserve unlabeled addresses. 370 | i++ 371 | continue 372 | } 373 | ss := s.withLabel(w.label) 374 | if len(ss) == 0 { 375 | // Remove labels that are now missing. 376 | d.watches = append(d.watches[:i], d.watches[i+1:]...) 377 | continue 378 | } 379 | w.addr = ss[0].addr 380 | i++ 381 | } 382 | // Adjust break points if they have changed. 383 | for i := len(d.breaks) - 1; i >= 0; i-- { 384 | bs := &d.breaks[i] 385 | if bs.label == "" { 386 | // Can't rewrite unlabeled breaks. 387 | continue 388 | } 389 | if ss := s.withLabel(bs.label); len(ss) == 0 { 390 | d.Runner.Debug("rmbreak", bs.addr) 391 | d.breaks = append(d.breaks[:i], d.breaks[i+1:]...) 392 | } else if ss[0].addr != bs.addr { 393 | d.Runner.Debug("break", ss[0].addr) 394 | d.Runner.Debug("rmbreak", bs.addr) 395 | bs.addr = ss[0].addr 396 | } 397 | } 398 | } 399 | 400 | func (d *Debugger) Run() error { 401 | t := time.NewTicker(30 * time.Millisecond) 402 | defer t.Stop() 403 | go func() { 404 | for { 405 | <-t.C 406 | d.app.QueueUpdateDraw(func() { 407 | d.tick.SetText(d.tickContent()) 408 | }) 409 | } 410 | }() 411 | return d.app.Run() 412 | } 413 | 414 | func (d *Debugger) StateFunc(m *uxn.Machine, k varvara.StateKind) { 415 | d.mu.Lock() 416 | now := time.Now() 417 | d.lastState = time.Now() 418 | if d.started.IsZero() { 419 | d.started = now 420 | } 421 | if k == varvara.HaltState { 422 | d.started = time.Time{} 423 | } 424 | updateWatches(now, d.watches, m) 425 | 426 | breaks := append([]symbol(nil), d.breaks...) 427 | sort.Slice(breaks, func(i, j int) bool { return breaks[i].addr < breaks[j].addr }) 428 | memAddr := m.PC 429 | if d.memAddr != nil { 430 | memAddr = *d.memAddr 431 | } 432 | var ( 433 | ops = opsText(d.syms, breaks, m) 434 | memory = memoryText(d.syms, breaks, m, memAddr) 435 | watch = watchText(m.PC, d.breaks, d.watches) 436 | state string 437 | ) 438 | if k != varvara.ClearState && k != varvara.QuietState { 439 | state = stateText(d.syms, m, k) 440 | } 441 | d.mu.Unlock() 442 | 443 | d.app.QueueUpdate(func() { 444 | switch k { 445 | case varvara.DebugState, varvara.ClearState: 446 | d.state.SetTextColor(tcell.ColorBlack) 447 | d.state.SetBackgroundColor(tcell.ColorDarkGrey) 448 | case varvara.BreakState: 449 | d.state.SetTextColor(tcell.ColorYellow) 450 | d.state.SetBackgroundColor(tcell.ColorDarkBlue) 451 | case varvara.PauseState: 452 | d.state.SetTextColor(tcell.ColorWhite) 453 | d.state.SetBackgroundColor(tcell.ColorDarkBlue) 454 | case varvara.HaltState: 455 | d.state.SetTextColor(tcell.ColorWhite) 456 | d.state.SetBackgroundColor(tcell.ColorDarkRed) 457 | } 458 | d.watch.SetText(watch).ScrollToEnd() 459 | d.ops.SetText(ops).ScrollToBeginning() 460 | d.memory.SetText(memory).ScrollToBeginning() 461 | if k != varvara.QuietState { 462 | d.stateLog.Write([]byte(d.state.GetText(false))) 463 | d.state.SetText(state) 464 | } 465 | }) 466 | } 467 | 468 | func stateText(syms *symbols, m *uxn.Machine, k varvara.StateKind) string { 469 | var ( 470 | op = uxn.Op(m.Mem[m.PC]) 471 | pcSym string 472 | sym string 473 | ) 474 | if s := syms.forAddr(m.PC); len(s) > 0 { 475 | pcSym = s[0].String() + " -> " 476 | } 477 | if addr, ok := m.OpAddr(m.PC); ok { 478 | switch s := syms.forAddr(addr); len(s) { 479 | case 0: 480 | // None. 481 | case 1: 482 | sym = s[0].String() 483 | default: 484 | switch op.Base() { 485 | case uxn.DEO, uxn.DEI: 486 | sym = s[0].String() 487 | default: 488 | sym = s[len(s)-1].String() 489 | } 490 | } 491 | if sym != "" { 492 | switch op.Base() { 493 | case uxn.JCI, uxn.JMI, uxn.JSI: 494 | // Address doesn't come from stack. 495 | default: 496 | sym = stackColor1 + sym + "[-:-]" 497 | } 498 | } 499 | } 500 | kind := " " 501 | switch k { 502 | case varvara.BreakState: 503 | kind = "BREAK" 504 | case varvara.DebugState: 505 | kind = "DEBUG" 506 | case varvara.PauseState: 507 | kind = "PAUSE" 508 | case varvara.HaltState: 509 | kind = "HALT!" 510 | } 511 | var workOp, retOp uxn.Op 512 | if op.Base() == uxn.STH { 513 | workOp, retOp = op, op 514 | } else if op.Return() { 515 | retOp = op 516 | } else { 517 | workOp = op 518 | } 519 | return fmt.Sprintf("%s %.4x %- 6s %s%s\nws: %v\nrs: %v", 520 | kind, m.PC, op, pcSym, sym, 521 | formatStack(&m.Work, workOp), 522 | formatStack(&m.Ret, retOp)) 523 | } 524 | 525 | const ( 526 | stackColor1 = "[black:aqua]" 527 | stackColor2 = "[black:fuchsia]" 528 | stackColor3 = "[black:lime]" 529 | ) 530 | 531 | func formatStack(st *uxn.Stack, op uxn.Op) string { 532 | v1, v2, v3 := op.StackArgs() 533 | 534 | var b strings.Builder 535 | b.WriteByte('(') 536 | for i, v := range st.Bytes[:st.Ptr] { 537 | b.WriteByte(' ') 538 | if op > 0 { 539 | idx, pre, post := int(st.Ptr)-i, "", "" 540 | formatStackVal(idx, &pre, &post, v3, stackColor3) 541 | formatStackVal(idx, &pre, &post, v2, stackColor2) 542 | formatStackVal(idx, &pre, &post, v1, stackColor1) 543 | b.WriteString(pre) 544 | fmt.Fprintf(&b, "%.2x", v) 545 | b.WriteString(post) 546 | } else { 547 | fmt.Fprintf(&b, "%.2x", v) 548 | } 549 | } 550 | b.WriteByte(' ') 551 | b.WriteByte(')') 552 | return b.String() 553 | } 554 | 555 | func formatStackVal(i int, pre, post *string, v uxn.StackVal, color string) { 556 | if v.Index > 0 && (v.Index == i || v.Index-(v.Size-1) == i) { 557 | *pre, *post = color, "[-:-]" 558 | } 559 | } 560 | 561 | const ( 562 | beforeOps = 0x10 563 | totalOps = 0x40 564 | ) 565 | 566 | func opsText(syms *symbols, breaks []symbol, m *uxn.Machine) string { 567 | start := m.PC - beforeOps 568 | if m.PC < beforeOps { 569 | start = 0 570 | } 571 | end := start + totalOps 572 | 573 | opAddr, hasAddr := m.OpAddr(m.PC) 574 | opShort := opAddrShort(uxn.Op(m.Mem[m.PC])) 575 | 576 | var s strings.Builder 577 | for addr := start; addr < end; addr++ { 578 | b := m.Mem[addr] 579 | op := uxn.Op(b) 580 | label := "" 581 | beforeLabel := false 582 | ss := syms.forAddr(addr) 583 | if len(ss) == 0 && addr == start { 584 | beforeLabel = true 585 | ss = syms.beforeAddr(addr) 586 | } 587 | for _, s := range ss { 588 | if len(label) > 0 { 589 | label += " " 590 | } 591 | if !beforeLabel { 592 | if _, child, ok := strings.Cut(s.label, "/"); ok { 593 | label += "&" + child 594 | continue 595 | } 596 | } 597 | label += s.label 598 | } 599 | if len(label) > 16 { 600 | label = label[:16] 601 | } 602 | for len(breaks) > 0 && breaks[0].addr < addr { 603 | breaks = breaks[1:] 604 | } 605 | isBreak := len(breaks) > 0 && breaks[0].addr == addr 606 | lineColor := "" 607 | if addr == m.PC { 608 | lineColor = "-:darkblue" 609 | } else if hasAddr && (addr == opAddr || opShort && addr-1 == opAddr) { 610 | lineColor = "black:aqua" 611 | } 612 | if lineColor != "" { 613 | fmt.Fprintf(&s, "[%s] [%.4x] %.2x %- 6s %- 16s [-:-]\n", 614 | lineColor, addr, b, op, label) 615 | } else { 616 | hexColor := "grey" 617 | opColor := "-" 618 | if isMaybeLiteral(m, addr) { 619 | hexColor = "olive" 620 | opColor = "grey" 621 | } 622 | addrColor := "-" 623 | labelColor := "-" 624 | if isBreak { 625 | addrColor = "yellow" 626 | labelColor = "yellow" 627 | } 628 | if beforeLabel { 629 | labelColor = "grey" 630 | } 631 | fmt.Fprintf(&s, " [%s][%.4x][-] [%s]%.2x[-] [%s]%- 6s[-] [%s]%- 16s[-]\n", 632 | addrColor, addr, hexColor, b, opColor, op, labelColor, label) 633 | } 634 | } 635 | return s.String() 636 | } 637 | 638 | func isMaybeLiteral(m *uxn.Machine, addr uint16) bool { 639 | return uxn.Op(m.Mem[addr-1]) == uxn.LIT || 640 | uxn.Op(m.Mem[addr-1]) == uxn.LIT2 || 641 | uxn.Op(m.Mem[addr-2]) == uxn.LIT2 642 | } 643 | 644 | const ( 645 | beforeMem = 0x080 646 | totalMem = 0x200 647 | ) 648 | 649 | func memoryText(syms *symbols, breaks []symbol, m *uxn.Machine, targetAddr uint16) string { 650 | var ( 651 | from = targetAddr&0xfff0 - beforeMem 652 | to = from + totalMem 653 | ss = syms.byAddr 654 | ) 655 | 656 | opAddr, hasAddr := m.OpAddr(targetAddr) 657 | opShort := opAddrShort(uxn.Op(m.Mem[targetAddr])) 658 | 659 | var ( 660 | s strings.Builder 661 | b = make([]byte, 0x10*3+20) 662 | ) 663 | for addr := from; addr < to; addr++ { 664 | if addr&0xf == 0 { 665 | if addr != from { 666 | fmt.Fprintf(&s, "\n %s\n", b) 667 | for i := range b { 668 | b[i] = ' ' 669 | } 670 | } 671 | fmt.Fprintf(&s, " [%.4x]", addr) 672 | } 673 | if addr&0xf == 0x8 { 674 | fmt.Fprintf(&s, " ") 675 | } 676 | for len(breaks) > 0 && breaks[0].addr < addr { 677 | breaks = breaks[1:] 678 | } 679 | isBreak := len(breaks) > 0 && breaks[0].addr == addr 680 | hexCol := "grey" 681 | if addr == m.PC { 682 | if isBreak { 683 | hexCol = "yellow:darkblue" 684 | } else { 685 | hexCol = ":darkblue" 686 | } 687 | } else if addr == targetAddr { 688 | hexCol = "black:fuchsia" 689 | } else if hasAddr && (addr == opAddr || opShort && addr-1 == opAddr) { 690 | hexCol = "black:aqua" 691 | } else if isBreak { 692 | hexCol = "black:yellow" 693 | } else if targetAddr != m.PC { 694 | if m.Mem[addr] != 0 { 695 | hexCol = "olive" 696 | } 697 | } else if isMaybeLiteral(m, addr) { 698 | hexCol = "olive" 699 | } 700 | if hexCol == "" { 701 | fmt.Fprintf(&s, " %.2x", m.Mem[addr]) 702 | } else { 703 | fmt.Fprintf(&s, " [%s]%.2x[-:-]", hexCol, m.Mem[addr]) 704 | } 705 | for len(ss) > 0 && ss[0].addr < addr { 706 | ss = ss[1:] 707 | } 708 | if len(ss) > 0 && ss[0].addr == addr { 709 | i := addr & 0xf * 3 710 | if addr&0xf >= 0x8 { 711 | i++ 712 | } 713 | label, child, ok := strings.Cut(ss[0].label, "/") 714 | if ok { 715 | label = "&" + child 716 | } else { 717 | label = "@" + label 718 | } 719 | copy(b[i:], label+strings.Repeat(" ", 20)) 720 | } 721 | } 722 | return s.String() 723 | } 724 | 725 | func updateWatches(now time.Time, watches []watch, m *uxn.Machine) { 726 | for i := range watches { 727 | w := &watches[i] 728 | var v uint16 729 | if w.short { 730 | v = uint16(m.Mem[w.addr])<<8 + uint16(m.Mem[w.addr+1]) 731 | } else { 732 | v = uint16(m.Mem[w.addr]) 733 | } 734 | 735 | if w.last != v { 736 | w.changed = now 737 | w.last = v 738 | } 739 | } 740 | } 741 | 742 | func watchText(pc uint16, breaks []symbol, watches []watch) string { 743 | var b strings.Builder 744 | for _, s := range breaks { 745 | if b.Len() > 0 { 746 | b.WriteByte('\n') 747 | } 748 | if pc == s.addr { 749 | fmt.Fprint(&b, "[yellow]") 750 | } 751 | fmt.Fprintf(&b, "%s [%.4x] brk!", s.label, s.addr) 752 | if pc == s.addr { 753 | fmt.Fprint(&b, "[-]") 754 | } 755 | } 756 | for _, w := range watches { 757 | if b.Len() > 0 { 758 | b.WriteByte('\n') 759 | } 760 | fmt.Fprintf(&b, "%s [%.4x] ", w.label, w.addr) 761 | if w.short { 762 | fmt.Fprintf(&b, "%.4x", w.last) 763 | } else { 764 | fmt.Fprintf(&b, " %.2x", w.last) 765 | } 766 | } 767 | return b.String() 768 | } 769 | 770 | func (d *Debugger) tickContent() string { 771 | d.mu.Lock() 772 | defer d.mu.Unlock() 773 | 774 | var b strings.Builder 775 | now := time.Now() 776 | if age := now.Sub(d.lastState).Truncate(time.Second); age > 0 { 777 | fmt.Fprintf(&b, "%s since state\n", age) 778 | } else { 779 | b.WriteByte('\n') 780 | } 781 | if !d.started.IsZero() { 782 | fmt.Fprintf(&b, "%s since start\n", now.Sub(d.started).Truncate(time.Second)) 783 | } else { 784 | b.WriteByte('\n') 785 | } 786 | return b.String() 787 | } 788 | 789 | func opAddrShort(op uxn.Op) bool { 790 | if op.Short() { 791 | switch op.Base() { 792 | case uxn.LDR, uxn.STR, uxn.LDA, uxn.STA, uxn.LDZ, uxn.STZ: 793 | return true 794 | } 795 | } 796 | return false 797 | } 798 | -------------------------------------------------------------------------------- /dev.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "time" 11 | 12 | "github.com/howeyc/fsnotify" 13 | 14 | "github.com/nf/nux/varvara" 15 | ) 16 | 17 | func devMode(enableGUI, enableDebug bool, talFile string) error { 18 | talFile = filepath.Clean(talFile) 19 | 20 | watcher, err := fsnotify.NewWatcher() 21 | if err != nil { 22 | return err 23 | } 24 | defer watcher.Close() 25 | if err := watcher.Watch(filepath.Dir(talFile)); err != nil { 26 | return err 27 | } 28 | tmp, err := os.MkdirTemp("", "nux-dev-*") 29 | if err != nil { 30 | return err 31 | } 32 | defer os.RemoveAll(tmp) 33 | romFile := filepath.Join(tmp, filepath.Base(talFile)+".rom") 34 | 35 | var ( 36 | runner *varvara.Runner 37 | debug *Debugger 38 | ) 39 | if enableDebug { 40 | debug = NewDebugger() 41 | runner = varvara.NewRunner(enableGUI, true, debug.StateFunc) 42 | runner.SetOutput(debug.Log) 43 | debug.Runner = runner 44 | 45 | log.SetPrefix("") 46 | log.SetOutput(debug.log) 47 | go func() { 48 | if err := debug.Run(); err != nil { 49 | log.Fatalf("debug: %v", err) 50 | } 51 | log.SetOutput(os.Stderr) 52 | log.SetPrefix("nux: ") 53 | runner.Debug("exit", 0) 54 | }() 55 | } else { 56 | runner = varvara.NewRunner(enableGUI, true, nil) 57 | } 58 | 59 | romCh := make(chan []byte) 60 | go func() { 61 | started := false 62 | run := time.After(1 * time.Millisecond) 63 | for { 64 | select { 65 | case <-run: 66 | log.Printf("dev: build %s", filepath.Base(talFile)) 67 | var out io.Writer = os.Stderr 68 | if debug != nil { 69 | out = debug.Log 70 | } 71 | rom, err := devBuild(out, talFile, romFile) 72 | if err != nil { 73 | log.Printf("dev: %v", err) 74 | break 75 | } 76 | if debug != nil { 77 | syms, err := parseSymbols(romFile + ".sym") 78 | if err != nil { 79 | log.Printf("dev: reading symbols: %v", err) 80 | break 81 | } 82 | debug.SetSymbols(syms) 83 | } 84 | if !started { 85 | log.Printf("dev: start") 86 | romCh <- rom 87 | started = true 88 | } else { 89 | log.Printf("dev: reset") 90 | runner.Swap(rom) 91 | } 92 | case ev := <-watcher.Event: 93 | if ev.Name == talFile && !ev.IsAttrib() { 94 | run = time.After(100 * time.Millisecond) 95 | } 96 | case err := <-watcher.Error: 97 | log.Printf("dev: watcher: %v", err) 98 | } 99 | } 100 | }() 101 | code := runner.Run((<-romCh)) 102 | return fmt.Errorf("dev: exit code: %d", code) 103 | } 104 | 105 | func devBuild(out io.Writer, talFile, romFile string) ([]byte, error) { 106 | cmd := exec.Command("uxnasm", talFile, romFile) 107 | cmd.Stdout = out 108 | cmd.Stderr = out 109 | if err := cmd.Run(); err != nil { 110 | return nil, fmt.Errorf("uxnasm: %v", err) 111 | } 112 | return os.ReadFile(romFile) 113 | } 114 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nf/nux 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gdamore/tcell/v2 v2.6.0 7 | github.com/howeyc/fsnotify v0.9.0 8 | github.com/rivo/tview v0.0.0-20230406072732-e22ce9588bb4 9 | golang.org/x/exp/shiny v0.0.0-20230321023759-10a507213a29 10 | golang.org/x/image v0.6.0 11 | golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c 12 | ) 13 | 14 | require ( 15 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b // indirect 16 | github.com/gdamore/encoding v1.0.0 // indirect 17 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect 18 | github.com/jezek/xgb v1.1.0 // indirect 19 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 20 | github.com/mattn/go-runewidth v0.0.14 // indirect 21 | github.com/rivo/uniseg v0.4.3 // indirect 22 | golang.org/x/sys v0.6.0 // indirect 23 | golang.org/x/term v0.5.0 // indirect 24 | golang.org/x/text v0.8.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b h1:a26Bdkl2B9PmYN6vGXnnfB2UGKjz0Moif1aEg+xTd7M= 2 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 3 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 4 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 5 | github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= 6 | github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= 7 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= 8 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 9 | github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY= 10 | github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= 11 | github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= 12 | github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 13 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 14 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 15 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 16 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 17 | github.com/rivo/tview v0.0.0-20230406072732-e22ce9588bb4 h1:zX+lRcFRPX1jn8A11jxT0dEQhkmUM7pec+9NLK8MiTQ= 18 | github.com/rivo/tview v0.0.0-20230406072732-e22ce9588bb4/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE= 19 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 20 | github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= 21 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 22 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 25 | golang.org/x/exp/shiny v0.0.0-20230321023759-10a507213a29 h1:uM92tP2dJQAC0zcyUIRXkokrkXj8fgt6GjCysDDaFh8= 26 | golang.org/x/exp/shiny v0.0.0-20230321023759-10a507213a29/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= 27 | golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= 28 | golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= 29 | golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4= 30 | golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= 31 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 32 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 33 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 34 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 35 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 36 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 47 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 49 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= 51 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 55 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 56 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 57 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 58 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 59 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 60 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 61 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 62 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 63 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Command nux executes Uxn ROMs on a Varvara machine. 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "runtime/pprof" 12 | 13 | "github.com/nf/nux/varvara" 14 | ) 15 | 16 | func main() { 17 | log.SetPrefix("nux: ") 18 | log.SetFlags(0) 19 | 20 | var ( 21 | cliFlag = flag.Bool("cli", false, "disable GUI features") 22 | devFlag = flag.Bool("dev", false, "enable developer mode (live re-build and run an untxal program)") 23 | debugFlag = flag.Bool("debug", false, "enable debugger (implies -dev)") 24 | 25 | cpuProfileFlag = flag.String("cpu_profile", "", "write CPU profile to `file`") 26 | ) 27 | 28 | flag.Usage = func() { 29 | fmt.Fprintf(os.Stderr, "usage: %s [-cli] \n", os.Args[0]) 30 | fmt.Fprintf(os.Stderr, " %s [-cli] <-dev | -debug> \n", os.Args[0]) 31 | flag.PrintDefaults() 32 | os.Exit(2) 33 | } 34 | flag.Parse() 35 | if flag.NArg() != 1 { 36 | flag.Usage() 37 | } 38 | 39 | if *devFlag || *debugFlag { 40 | if err := devMode(!*cliFlag, *debugFlag, flag.Arg(0)); err != nil { 41 | log.Fatal(err) 42 | } 43 | return 44 | } 45 | 46 | var cpuProfile io.Closer 47 | if prof := *cpuProfileFlag; prof != "" { 48 | f, err := os.Create(prof) 49 | if err != nil { 50 | log.Fatalf("creating CPU profile file: %v", err) 51 | } 52 | pprof.StartCPUProfile(f) 53 | cpuProfile = f 54 | } 55 | 56 | code, err := run(flag.Arg(0), !*cliFlag) 57 | 58 | if f := cpuProfile; f != nil { 59 | pprof.StopCPUProfile() 60 | f.Close() 61 | } 62 | 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | os.Exit(code) 67 | } 68 | 69 | func run(romFile string, guiEnabled bool) (int, error) { 70 | var ( 71 | rom []byte 72 | err error 73 | ) 74 | if filepath.Ext(romFile) == ".tal" { 75 | tmp, err := os.MkdirTemp("", "nux-build-*") 76 | if err != nil { 77 | return 0, err 78 | } 79 | defer os.RemoveAll(tmp) 80 | 81 | talFile := romFile 82 | romFile = filepath.Join(tmp, filepath.Base(talFile)+".rom") 83 | rom, err = devBuild(os.Stderr, talFile, romFile) 84 | } else { 85 | rom, err = os.ReadFile(romFile) 86 | } 87 | if err != nil { 88 | return 0, err 89 | } 90 | 91 | r := varvara.NewRunner(guiEnabled, false, nil) 92 | code := r.Run(rom) 93 | 94 | return code, nil 95 | } 96 | -------------------------------------------------------------------------------- /sym.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type symbol struct { 13 | addr uint16 14 | label string 15 | } 16 | 17 | func (s symbol) String() string { return fmt.Sprintf("%s (%.4x)", s.label, s.addr) } 18 | 19 | type symbols struct { 20 | byAddr []symbol 21 | byLabel []symbol 22 | } 23 | 24 | func (s *symbols) forAddr(addr uint16) []symbol { 25 | ss := s.byAddr 26 | i := sort.Search(len(ss), func(i int) bool { 27 | return ss[i].addr >= addr 28 | }) 29 | j := i 30 | for ; j < len(ss) && ss[j].addr == addr; j++ { 31 | } 32 | return ss[i:j] 33 | } 34 | 35 | func (s *symbols) beforeAddr(addr uint16) []symbol { 36 | ss := s.byAddr 37 | i := sort.Search(len(ss), func(i int) bool { 38 | return ss[i].addr >= addr 39 | }) 40 | if i == 0 { 41 | return nil 42 | } 43 | j := i - 1 44 | for prev := ss[j].addr; j > 0; j-- { 45 | if ss[j-1].addr != prev { 46 | break 47 | } 48 | } 49 | return ss[j:i] 50 | } 51 | 52 | func (s *symbols) withLabel(label string) []symbol { 53 | ss := s.byLabel 54 | i := sort.Search(len(ss), func(i int) bool { 55 | return ss[i].label >= label 56 | }) 57 | j := i 58 | for ; j < len(ss) && ss[j].label == label; j++ { 59 | } 60 | return ss[i:j] 61 | } 62 | 63 | func (s *symbols) withLabelPrefix(p string) []symbol { 64 | ss := s.byLabel 65 | i := sort.Search(len(ss), func(i int) bool { 66 | return ss[i].label >= p 67 | }) 68 | j := i 69 | for ; j < len(ss) && strings.HasPrefix(ss[j].label, p); j++ { 70 | } 71 | return ss[i:j] 72 | } 73 | 74 | func parseSymbols(symFile string) (*symbols, error) { 75 | b, err := os.ReadFile(symFile) 76 | if err != nil { 77 | return nil, err 78 | } 79 | var ss []symbol 80 | for len(b) > 0 { 81 | if len(b) < 3 { 82 | return nil, fmt.Errorf("invalid symbol at end of file %q", b) 83 | } 84 | s := symbol{addr: uint16(b[0])<<8 + uint16(b[1])} 85 | b = b[2:] 86 | i := bytes.IndexByte(b, 0) 87 | if i < 0 { 88 | return nil, fmt.Errorf("invalid symbol label at %.4x %q", s.addr, b) 89 | } 90 | s.label = string(b[:i]) 91 | b = b[i+1:] 92 | ss = append(ss, s) 93 | } 94 | var syms symbols 95 | sort.SliceStable(ss, func(i, j int) bool { 96 | return ss[i].addr < ss[j].addr 97 | }) 98 | syms.byAddr = append([]symbol(nil), ss...) 99 | sort.SliceStable(ss, func(i, j int) bool { 100 | return ss[i].label < ss[j].label 101 | }) 102 | syms.byLabel = append([]symbol(nil), ss...) 103 | return &syms, nil 104 | } 105 | 106 | func (sym *symbols) resolve(t string) []symbol { 107 | if i, err := strconv.ParseUint(t, 16, 16); err == nil { 108 | return []symbol{{addr: uint16(i)}} 109 | } 110 | t, wild := strings.CutSuffix(t, "*") 111 | if wild { 112 | return sym.withLabelPrefix(t) 113 | } 114 | return sym.withLabel(t) 115 | } 116 | -------------------------------------------------------------------------------- /uxn/exec.go: -------------------------------------------------------------------------------- 1 | // Package uxn provides an implementation of a Uxn CPU, called Machine, 2 | // that can be used to execute Uxn bytecode. 3 | package uxn 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | // Machine is an implementation of a Uxn CPU. 11 | type Machine struct { 12 | Mem [0x100000]byte 13 | PC uint16 14 | Work Stack 15 | Ret Stack 16 | Dev Device 17 | } 18 | 19 | // Device provides access to external systems connected to the Uxn CPU. 20 | type Device interface { 21 | In(port byte) (value byte) 22 | InShort(port byte) (value uint16) 23 | Out(port, value byte) 24 | OutShort(port byte, value uint16) 25 | } 26 | 27 | // NewMachine returns a Uxn CPU loaded with the given rom at 0x100. 28 | // If the rom is larger than 0xfeff bytes then Mem is grown to accommodate it, 29 | // even though the Uxn CPU cannot directly address the extra memory. 30 | func NewMachine(rom []byte) *Machine { 31 | m := &Machine{PC: 0x100} 32 | copy(m.Mem[0x100:], rom) 33 | return m 34 | } 35 | 36 | var ErrBRK = errors.New("BRK") 37 | 38 | // Exec executes the intsruction at m.PC. It returns ErrBRK if that instruction 39 | // is BRK, and otherwise only returns a non-nil error if it encounters a halt 40 | // condition. 41 | func (m *Machine) Exec() (err error) { 42 | var ( 43 | op = Op(m.Mem[m.PC]) 44 | opPC = m.PC 45 | ) 46 | defer func() { 47 | if e := recover(); e != nil { 48 | if code, ok := e.(HaltCode); ok { 49 | err = HaltError{ 50 | Addr: opPC, 51 | Op: op, 52 | HaltCode: code, 53 | } 54 | } else { 55 | panic(e) 56 | } 57 | } 58 | }() 59 | 60 | m.PC++ 61 | 62 | switch op { 63 | case BRK: 64 | return ErrBRK 65 | case JCI, JMI, JSI: 66 | m.PC += 2 67 | if op == JCI && m.Work.wrap().Pop() == 0 { 68 | return nil 69 | } 70 | if op == JSI { 71 | m.Ret.wrap().PushShort(m.PC) 72 | } 73 | m.PC += short(m.Mem[m.PC-2], m.Mem[m.PC-1]) 74 | return nil 75 | } 76 | 77 | var st *stackWrapper 78 | if op.Return() { 79 | st = m.Ret.keep(op.Keep()) 80 | } else { 81 | st = m.Work.keep(op.Keep()) 82 | } 83 | 84 | switch op.Base() { 85 | case LIT: 86 | st.Push(m.Mem[m.PC]) 87 | m.PC++ 88 | if op.Short() { 89 | st.Push(m.Mem[m.PC]) 90 | m.PC++ 91 | } 92 | case JMP, JSR: 93 | pc := m.PC 94 | if op.Short() { 95 | m.PC = st.PopShort() 96 | } else { 97 | m.PC += st.PopOffset() 98 | } 99 | if op.Base() == JSR { 100 | m.Ret.wrap().PushShort(pc) 101 | } 102 | case JCN: 103 | var addr uint16 104 | if op.Short() { 105 | addr = st.PopShort() 106 | } else { 107 | addr = m.PC + st.PopOffset() 108 | } 109 | if st.Pop() != 0 { 110 | m.PC = addr 111 | } 112 | case STH: 113 | var to *stackWrapper 114 | if op.Return() { 115 | to = m.Work.wrap() 116 | } else { 117 | to = m.Ret.wrap() 118 | } 119 | if op.Short() { 120 | to.PushShort(st.PopShort()) 121 | } else { 122 | to.Push(st.Pop()) 123 | } 124 | case LDZ: 125 | addr := st.Pop() 126 | st.Push(m.Mem[addr]) 127 | if op.Short() { 128 | st.Push(m.Mem[addr+1]) 129 | } 130 | case STZ: 131 | addr := st.Pop() 132 | if op.Short() { 133 | m.Mem[addr+1] = st.Pop() 134 | } 135 | m.Mem[addr] = st.Pop() 136 | case LDR: 137 | offs := st.PopOffset() 138 | st.Push(m.Mem[m.PC+offs]) 139 | if op.Short() { 140 | st.Push(m.Mem[m.PC+offs+1]) 141 | } 142 | case STR: 143 | offs := st.PopOffset() 144 | if op.Short() { 145 | m.Mem[m.PC+offs+1] = st.Pop() 146 | } 147 | m.Mem[m.PC+offs] = st.Pop() 148 | case LDA: 149 | addr := st.PopShort() 150 | st.Push(m.Mem[addr]) 151 | if op.Short() { 152 | st.Push(m.Mem[addr+1]) 153 | } 154 | case STA: 155 | addr := st.PopShort() 156 | if op.Short() { 157 | m.Mem[addr+1] = st.Pop() 158 | } 159 | m.Mem[addr] = st.Pop() 160 | case DEI: 161 | port := st.Pop() 162 | if op.Short() { 163 | st.PushShort(m.Dev.InShort(port)) 164 | } else { 165 | st.Push(m.Dev.In(port)) 166 | } 167 | case DEO: 168 | port := st.Pop() 169 | if op.Short() { 170 | m.Dev.OutShort(port, st.PopShort()) 171 | } else { 172 | m.Dev.Out(port, st.Pop()) 173 | } 174 | case SFT: 175 | sft := st.Pop() 176 | left, right := (sft&0xf0)>>4, sft&0x0f 177 | if op.Short() { 178 | st.PushShort((st.PopShort() >> right) << left) 179 | } else { 180 | st.Push((st.Pop() >> right) << left) 181 | } 182 | default: 183 | if op.Short() { 184 | execSimple(op, pushPopper[uint16](shortPushPopper{st})) 185 | } else { 186 | execSimple(op, pushPopper[byte](st)) 187 | } 188 | } 189 | 190 | return nil 191 | } 192 | 193 | func execSimple[T byte | uint16](op Op, s pushPopper[T]) { 194 | switch op.Base() { 195 | case INC: 196 | s.Push(s.Pop() + 1) 197 | case POP: 198 | s.Pop() 199 | case NIP: 200 | v := s.Pop() 201 | s.Pop() 202 | s.Push(v) 203 | case SWP: 204 | b, a := s.Pop(), s.Pop() 205 | s.Push(b) 206 | s.Push(a) 207 | case ROT: 208 | c, b, a := s.Pop(), s.Pop(), s.Pop() 209 | s.Push(b) 210 | s.Push(c) 211 | s.Push(a) 212 | case DUP: 213 | v := s.Pop() 214 | s.Push(v) 215 | s.Push(v) 216 | case OVR: 217 | b, a := s.Pop(), s.Pop() 218 | s.Push(a) 219 | s.Push(b) 220 | s.Push(a) 221 | case EQU: 222 | s.PushBool(s.Pop() == s.Pop()) 223 | case NEQ: 224 | s.PushBool(s.Pop() != s.Pop()) 225 | case GTH: 226 | s.PushBool(s.Pop() < s.Pop()) 227 | case LTH: 228 | s.PushBool(s.Pop() > s.Pop()) 229 | case ADD: 230 | s.Push(s.Pop() + s.Pop()) 231 | case SUB: 232 | b, a := s.Pop(), s.Pop() 233 | s.Push(a - b) 234 | case MUL: 235 | s.Push(s.Pop() * s.Pop()) 236 | case DIV: 237 | b, a := s.Pop(), s.Pop() 238 | if b == 0 { 239 | panic(DivideByZero) 240 | } 241 | s.Push(a / b) 242 | case AND: 243 | s.Push(s.Pop() & s.Pop()) 244 | case ORA: 245 | s.Push(s.Pop() | s.Pop()) 246 | case EOR: 247 | s.Push(s.Pop() ^ s.Pop()) 248 | default: 249 | panic(fmt.Errorf("internal error: %v not implemented", op)) 250 | } 251 | } 252 | 253 | // OpAddr returns the address associated with the operation at addr, either 254 | // from memory or the stack, and reports whether the operation has an 255 | // associated address (for example, JMP does while POP does not), and whether 256 | // there are enough bytes on the stack to provide an address (ie, it returns 257 | // false if executing the instruction would trigger a stack underflow). 258 | func (m *Machine) OpAddr(addr uint16) (uint16, bool) { 259 | switch op := Op(m.Mem[addr]); op.Base() { 260 | case JCI, JMI, JSI: 261 | return m.PC + short(m.Mem[m.PC+1], m.Mem[m.PC+2]) + 3, true 262 | case JMP, JCN, JSR, LDR, STR, LDA, STA, LDZ, STZ, DEI, DEO: 263 | var st *Stack 264 | if op.Return() { 265 | st = &m.Ret 266 | } else { 267 | st = &m.Work 268 | } 269 | switch op.Base() { 270 | case JMP, JCN, JSR: 271 | if op.Short() { // addr16 abs 272 | return st.PeekShort() 273 | } else { // addr8 rel 274 | offs, ok := st.PeekOffset() 275 | return m.PC + offs + 1, ok 276 | } 277 | case LDR, STR: // addr8 rel 278 | offs, ok := st.PeekOffset() 279 | return m.PC + offs + 1, ok 280 | case LDA, STA: // addr16 abs 281 | return st.PeekShort() 282 | case LDZ, STZ, DEI, DEO: // addr8 zero, device8 283 | addr, ok := st.Peek() 284 | return uint16(addr), ok 285 | } 286 | } 287 | return 0, false 288 | } 289 | 290 | // HaltError is returned by Exec if execution is halted by 291 | // the program for some reason. 292 | type HaltError struct { 293 | HaltCode 294 | Op Op 295 | Addr uint16 296 | } 297 | 298 | func (e HaltError) Error() string { 299 | return fmt.Sprintf("%s executing %s at %.4x", e.HaltCode, e.Op, e.Addr) 300 | } 301 | 302 | // HaltCode signifies the type of condition that halted execution. 303 | type HaltCode byte 304 | 305 | const ( 306 | Halt HaltCode = 0x00 307 | Underflow HaltCode = 0x01 308 | Overflow HaltCode = 0x02 309 | DivideByZero HaltCode = 0x03 310 | 311 | // Debug is treated differently by Exec, which will still return a 312 | // HaltError but the stacks are left unchanged and the program may 313 | // cotinue to execute normally. 314 | Debug HaltCode = 0xff 315 | ) 316 | 317 | func (c HaltCode) String() string { 318 | if s, ok := map[HaltCode]string{ 319 | Halt: "halt", 320 | Underflow: "stack underflow", 321 | Overflow: "stack overflow", 322 | DivideByZero: "division by zero", 323 | }[c]; ok { 324 | return s 325 | } 326 | return fmt.Sprintf("unknown (%.2x)", byte(c)) 327 | } 328 | 329 | func short(hi, lo byte) uint16 { 330 | return uint16(hi)<<8 + uint16(lo) 331 | } 332 | -------------------------------------------------------------------------------- /uxn/exec_test.go: -------------------------------------------------------------------------------- 1 | package uxn 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestNewMachine(t *testing.T) { 10 | for _, c := range []struct { 11 | romSize int 12 | }{ 13 | {0x00000}, 14 | {0x00001}, 15 | {0x0feff}, 16 | {0x0ff00}, 17 | {0x0ff01}, 18 | {0x1ff00}, 19 | } { 20 | t.Run(fmt.Sprintf("%.5x", c.romSize), func(t *testing.T) { 21 | m := NewMachine(bytes.Repeat([]byte{1}, c.romSize)) 22 | for i := range m.Mem { 23 | w := byte(0) 24 | if i >= 0x100 && i < 0x100+c.romSize { 25 | w = 1 26 | } 27 | if g := m.Mem[i]; g != w { 28 | t.Errorf("Mem[%.5x] == %.2x, want %.2x", i, g, w) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | 35 | func TestExec(t *testing.T) { 36 | c := newExecTestCase 37 | for i, c := range []*execTestCase{ 38 | c(INC).work(1, 2).want().work(1, 3), 39 | c(INC).work(0xff).want().work(0), 40 | c(INCk).work(1, 2).want().work(1, 2, 3), 41 | c(INC2).work(1, 2).want().work(1, 3), 42 | c(INC2).work(0, 0xff).want().work(1, 0), 43 | c(INC2).work(0xff, 0xff).want().work(0, 0), 44 | c(INC2k).work(1, 2).want().work(1, 2, 1, 3), 45 | c(INC2k).work(0, 0xff).want().work(0, 0xff, 1, 0), 46 | 47 | c(POP).work(1, 2).want().work(1), 48 | c(POPk).work(1, 2).want().work(1, 2), 49 | c(POP2).work(1, 2), 50 | c(POP2k).work(1, 2).want().work(1, 2), 51 | 52 | c(NIP).work(1, 2).want().work(2), 53 | c(NIP2).work(1, 2, 3, 4).want().work(3, 4), 54 | c(NIP2k).work(1, 2, 3, 4).want().work(1, 2, 3, 4, 3, 4), 55 | 56 | c(SWP).work(1, 2).want().work(2, 1), 57 | c(SWPk).work(1, 2).want().work(1, 2, 2, 1), 58 | c(SWP2).work(1, 2, 3, 4).want().work(3, 4, 1, 2), 59 | 60 | c(ROT).work(1, 2, 3).want().work(2, 3, 1), 61 | c(ROTk).work(1, 2, 3).want().work(1, 2, 3, 2, 3, 1), 62 | c(ROT2).work(1, 2, 3, 4, 5, 6).want().work(3, 4, 5, 6, 1, 2), 63 | c(ROT2k).work(1, 2, 3, 4, 5, 6).want().work(1, 2, 3, 4, 5, 6, 3, 4, 5, 6, 1, 2), 64 | 65 | c(DUP).work(1, 2).want().work(1, 2, 2), 66 | c(DUPk).work(1).want().work(1, 1, 1), 67 | c(DUP2).work(1, 2).want().work(1, 2, 1, 2), 68 | 69 | c(OVR).work(1, 2).want().work(1, 2, 1), 70 | c(OVRk).work(1, 2).want().work(1, 2, 1, 2, 1), 71 | c(OVR2).work(1, 2, 3, 4).want().work(1, 2, 3, 4, 1, 2), 72 | c(OVR2k).work(1, 2, 3, 4).want().work(1, 2, 3, 4, 1, 2, 3, 4, 1, 2), 73 | 74 | c(EQU).work(42, 42).want().work(1), 75 | c(EQU).work(1, 2).want().work(0), 76 | c(EQUk).work(42, 42).want().work(42, 42, 1), 77 | c(EQUk).work(1, 2).want().work(1, 2, 0), 78 | c(EQU2).work(1, 2, 1, 2).want().work(1), 79 | c(EQU2).work(1, 2, 3, 4).want().work(0), 80 | c(EQU2k).work(1, 2, 1, 2).want().work(1, 2, 1, 2, 1), 81 | c(EQU2k).work(1, 2, 3, 4).want().work(1, 2, 3, 4, 0), 82 | 83 | c(NEQ).work(42, 42).want().work(0), 84 | c(NEQ).work(1, 2).want().work(1), 85 | c(NEQk).work(42, 42).want().work(42, 42, 0), 86 | c(NEQk).work(1, 2).want().work(1, 2, 1), 87 | c(NEQ2).work(1, 2, 1, 2).want().work(0), 88 | c(NEQ2).work(1, 2, 3, 4).want().work(1), 89 | c(NEQ2k).work(1, 2, 1, 2).want().work(1, 2, 1, 2, 0), 90 | c(NEQ2k).work(1, 2, 3, 4).want().work(1, 2, 3, 4, 1), 91 | 92 | c(GTH).work(1, 2).want().work(0), 93 | c(GTH).work(1, 1).want().work(0), 94 | c(GTH).work(2, 1).want().work(1), 95 | c(GTHk).work(2, 1).want().work(2, 1, 1), 96 | c(GTH2).work(1, 2, 1, 3).want().work(0), 97 | c(GTH2).work(1, 2, 1, 2).want().work(0), 98 | c(GTH2).work(1, 3, 1, 2).want().work(1), 99 | c(GTH2k).work(1, 3, 1, 2).want().work(1, 3, 1, 2, 1), 100 | 101 | c(LTH).work(1, 2).want().work(1), 102 | c(LTH).work(1, 1).want().work(0), 103 | c(LTH).work(2, 1).want().work(0), 104 | c(LTHk).work(2, 1).want().work(2, 1, 0), 105 | c(LTH2).work(1, 2, 1, 3).want().work(1), 106 | c(LTH2).work(1, 2, 1, 2).want().work(0), 107 | c(LTH2).work(1, 3, 1, 2).want().work(0), 108 | c(LTH2k).work(1, 3, 1, 2).want().work(1, 3, 1, 2, 0), 109 | 110 | c(ADD).work(1, 2).want().work(3), 111 | c(SUB).work(3, 2).want().work(1), 112 | c(MUL).work(2, 3).want().work(6), 113 | c(DIV).work(6, 3).want().work(2), 114 | c(AND).work(0x99, 0xb8).want().work(0x98), 115 | c(ORA).work(0x36, 0x63).want().work(0x77), 116 | c(EOR).work(0x31, 0x13).want().work(0x22), 117 | 118 | c(JCI).mem(0x101, 2, 2).work(0).want().pc(0x103), 119 | c(JCI).mem(0x101, 7, 5).work(1).want().pc(0x808), 120 | 121 | c(JMI).mem(0x101, 7, 5).want().pc(0x808), 122 | 123 | c(JSI).mem(0x101, 7, 5).want().ret(1, 3).pc(0x808), 124 | 125 | c(LIT).mem(0x101, 1).want().work(1).pc(0x102), 126 | c(LIT2).mem(0x101, 1, 2).want().work(1, 2).pc(0x103), 127 | 128 | c(JMP).work(1).want().pc(0x102), 129 | c(JMPk).work(1).want().work(1).pc(0x102), 130 | c(JMP).work(rel(-2)).want().pc(0xff), 131 | c(JMP2).work(3, 4).want().pc(0x304), 132 | c(JMP2k).work(3, 4).want().work(3, 4).pc(0x304), 133 | 134 | c(JCN).work(0, 4).want(), 135 | c(JCN).work(1, 4).want().pc(0x105), 136 | c(JCNk).work(1, 4).want().work(1, 4).pc(0x105), 137 | c(JCN2).work(0, 2, 7).want(), 138 | c(JCN2).work(1, 2, 7).want().pc(0x207), 139 | c(JCN2k).work(1, 2, 7).want().work(1, 2, 7).pc(0x207), 140 | 141 | c(JSR).work(4).want().ret(1, 1).pc(0x105), 142 | c(JSRk).work(4).want().work(4).ret(1, 1).pc(0x105), 143 | c(JSR2).work(2, 7).want().ret(1, 1).pc(0x207), 144 | c(JSR2k).work(2, 7).want().work(2, 7).ret(1, 1).pc(0x207), 145 | 146 | c(STH).work(7).want().ret(7), 147 | c(STHr).ret(7).want().work(7), 148 | c(STHk).work(7).want().work(7).ret(7), 149 | c(STH2).work(7, 8).want().ret(7, 8), 150 | c(STH2r).ret(7, 8).want().work(7, 8), 151 | c(STH2k).work(7, 8).want().work(7, 8).ret(7, 8), 152 | 153 | c(LDZ).mem(0x71, 0x42).work(0x71).want().work(0x42), 154 | c(LDZ2).mem(0x71, 0x42, 0x69).work(0x71).want().work(0x42, 0x69), 155 | c(STZ).work(0x42, 0x71).want().mem(0x71, 0x42), 156 | c(STZ2).work(0x42, 0x69, 0x71).want().mem(0x71, 0x42, 0x69), 157 | 158 | c(LDR).mem(0xf1, 0x42).work(rel(-16)).want().work(0x42), 159 | c(LDR2).mem(0xf1, 0x42, 0x69).work(rel(-16)).want().work(0x42, 0x69), 160 | 161 | c(STR).work(0x42, rel(-16)).want().mem(0xf1, 0x42), 162 | c(STR2).work(0x42, 0x69, rel(-16)).want().mem(0xf1, 0x42, 0x69), 163 | 164 | c(LDA).mem(0x109, 0x42).work(0x01, 0x09).want().work(0x42), 165 | c(LDA2).mem(0x109, 0x42, 0x69).work(0x01, 0x09).want().work(0x42, 0x69), 166 | 167 | c(STA).work(0x42, 0x1, 0x09).want().mem(0x109, 0x42), 168 | c(STA2).work(0x42, 0x69, 0x1, 0x09).want().mem(0x109, 0x42, 0x69), 169 | 170 | c(SFT).work(9, 0x21).want().work(16), 171 | c(SFT2).work(1, 9, 0x21).want().work(2, 16), 172 | 173 | c(DIV).work(1, 2, 0).want().work(1). 174 | error(HaltError{HaltCode: DivideByZero, Op: DIV, Addr: 0x100}), 175 | c(POP).want(). 176 | error(HaltError{HaltCode: Underflow, Op: POP, Addr: 0x100}), 177 | c(POP2).work(42).want().work(). 178 | error(HaltError{HaltCode: Underflow, Op: POP2, Addr: 0x100}), 179 | c(POP2k).work(42).want().work(42). 180 | error(HaltError{HaltCode: Underflow, Op: POP2k, Addr: 0x100}), 181 | c(DUP).work(bytes.Repeat([]byte{7}, 255)...).want(). 182 | work(bytes.Repeat([]byte{7}, 255)...). 183 | error(HaltError{HaltCode: Overflow, Op: DUP, Addr: 0x100}), 184 | c(DUP2).work(bytes.Repeat([]byte{7}, 254)...).want(). 185 | work(bytes.Repeat([]byte{7}, 255)...). 186 | error(HaltError{HaltCode: Overflow, Op: DUP2, Addr: 0x100}), 187 | } { 188 | t.Run(fmt.Sprintf("%s_%d", Op(c.m.Mem[0x100]), i), func(t *testing.T) { 189 | if err := c.m.Exec(); err != c.err { 190 | t.Fatalf("got error %v, want %v", err, c.err) 191 | } 192 | if g, w := c.m.Work, c.w.Work; !stackEq(g, w) { 193 | t.Errorf("work stack is\n\t%v\nwant\n\t%v", g, w) 194 | } 195 | if g, w := c.m.Ret, c.w.Ret; !stackEq(g, w) { 196 | t.Errorf("return stack is %v, want %v", g, w) 197 | } 198 | if g, w := c.m.Mem, c.w.Mem; g != w { 199 | for i := 0; i < len(g) && i < len(w); i++ { 200 | if g[i] != w[i] { 201 | t.Errorf("memory[%.4x] = %.2x, want %.2x", i, g[i], w[i]) 202 | } 203 | } 204 | } 205 | if g, w := c.m.PC, c.w.PC; g != w { 206 | t.Errorf("PC is %x, want %x", g, w) 207 | } 208 | }) 209 | } 210 | } 211 | 212 | type execTestCase struct { 213 | m, w *Machine 214 | err error 215 | set *Machine 216 | } 217 | 218 | func newExecTestCase(op Op) *execTestCase { 219 | c := &execTestCase{} 220 | c.m = NewMachine([]byte{byte(op)}) 221 | c.w = NewMachine([]byte{byte(op)}) 222 | c.w.PC++ 223 | c.set = c.m 224 | return c 225 | } 226 | 227 | func (c *execTestCase) work(bytes ...byte) *execTestCase { 228 | setStack(&c.set.Work, bytes) 229 | return c 230 | } 231 | 232 | func (c *execTestCase) ret(bytes ...byte) *execTestCase { 233 | setStack(&c.set.Ret, bytes) 234 | return c 235 | } 236 | 237 | func (c *execTestCase) mem(addr uint16, bytes ...byte) *execTestCase { 238 | copy(c.set.Mem[addr:], bytes) 239 | if c.set == c.m { 240 | copy(c.w.Mem[addr:], bytes) 241 | } 242 | return c 243 | } 244 | 245 | func (c *execTestCase) pc(addr uint16) *execTestCase { 246 | c.set.PC = addr 247 | return c 248 | } 249 | 250 | func (c *execTestCase) want() *execTestCase { 251 | c.set = c.w 252 | return c 253 | } 254 | 255 | func (c *execTestCase) error(err error) *execTestCase { 256 | c.err = err 257 | return c 258 | } 259 | 260 | func setStack(s *Stack, bytes []byte) { 261 | for i, b := range bytes { 262 | s.Bytes[i] = b 263 | } 264 | s.Ptr = byte(len(bytes)) 265 | } 266 | 267 | func stackEq(a, b Stack) bool { 268 | ac := Stack{Ptr: a.Ptr} 269 | bc := Stack{Ptr: b.Ptr} 270 | copy(ac.Bytes[:], a.Bytes[:a.Ptr]) 271 | copy(bc.Bytes[:], b.Bytes[:a.Ptr]) 272 | return ac == bc 273 | } 274 | 275 | func rel(i int8) byte { return byte(i) } 276 | -------------------------------------------------------------------------------- /uxn/op.go: -------------------------------------------------------------------------------- 1 | package uxn 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Op represents a Uxn opcode. 8 | type Op byte 9 | 10 | // Short reports whether the opcode has the short flag set. 11 | func (b Op) Short() bool { return b&0x20 > 0 && b&0x9f > 0 } 12 | 13 | // Return reports whether the opcode has the return flag set. 14 | func (b Op) Return() bool { return b&0x40 > 0 && b&0x9f > 0 } 15 | 16 | // Keep reports whether the opcode has the keep flag set. 17 | func (b Op) Keep() bool { return b&0x80 > 0 && b&0x1f > 0 } 18 | 19 | // Base returns the opcode without any flags set. 20 | func (b Op) Base() Op { 21 | switch { 22 | case b&0x1f > 0: 23 | return b & 0x1f 24 | case b&0x9f == 0: 25 | return b 26 | case b&0x9f == 0x80: 27 | return LIT 28 | default: 29 | panic("unreachable") 30 | } 31 | } 32 | 33 | // StackVal holds the position and width of a value on the stack. 34 | type StackVal struct { 35 | Index int // from top of stack (1 is first, 0 is none) 36 | Size int // 1 for byte, 2 for short 37 | } 38 | 39 | // StackArgs reports the stack arguments consumed by Op. 40 | // If any of the args are zero then they not consumed. 41 | func (op Op) StackArgs() (a, b, c StackVal) { 42 | type v = StackVal 43 | w, t := 1, 1 44 | if op.Short() { 45 | w, t = 2, 2 46 | } 47 | switch op.Base() { 48 | case JMP, JSR, STH, INC, POP, DUP: 49 | a = v{w, t} 50 | case SWP, EQU, NEQ, GTH, LTH, ADD, SUB, MUL, DIV, AND, ORA, EOR: 51 | a = v{w, t} 52 | b = v{w * 2, t} 53 | case ROT: 54 | a = v{w, t} 55 | b = v{w * 2, t} 56 | c = v{w * 3, t} 57 | case NIP, OVR: 58 | a = v{w * 2, t} 59 | case JCN: 60 | a = v{w, t} 61 | b = v{w + 1, 1} 62 | case LDZ, LDR, DEI, JCI: 63 | a = v{1, 1} 64 | case STZ, STR, DEO, SFT: 65 | a = v{1, 1} 66 | b = v{1 + w, t} 67 | case LDA: 68 | a = v{2, 2} 69 | case STA: 70 | a = v{2, 2} 71 | b = v{2 + w, t} 72 | } 73 | return 74 | } 75 | 76 | const ( 77 | BRK Op = iota 78 | INC 79 | POP 80 | NIP 81 | SWP 82 | ROT 83 | DUP 84 | OVR 85 | EQU 86 | NEQ 87 | GTH 88 | LTH 89 | JMP 90 | JCN 91 | JSR 92 | STH 93 | LDZ 94 | STZ 95 | LDR 96 | STR 97 | LDA 98 | STA 99 | DEI 100 | DEO 101 | ADD 102 | SUB 103 | MUL 104 | DIV 105 | AND 106 | ORA 107 | EOR 108 | SFT 109 | JCI 110 | INC2 111 | POP2 112 | NIP2 113 | SWP2 114 | ROT2 115 | DUP2 116 | OVR2 117 | EQU2 118 | NEQ2 119 | GTH2 120 | LTH2 121 | JMP2 122 | JCN2 123 | JSR2 124 | STH2 125 | LDZ2 126 | STZ2 127 | LDR2 128 | STR2 129 | LDA2 130 | STA2 131 | DEI2 132 | DEO2 133 | ADD2 134 | SUB2 135 | MUL2 136 | DIV2 137 | AND2 138 | ORA2 139 | EOR2 140 | SFT2 141 | JMI 142 | INCr 143 | POPr 144 | NIPr 145 | SWPr 146 | ROTr 147 | DUPr 148 | OVRr 149 | EQUr 150 | NEQr 151 | GTHr 152 | LTHr 153 | JMPr 154 | JCNr 155 | JSRr 156 | STHr 157 | LDZr 158 | STZr 159 | LDRr 160 | STRr 161 | LDAr 162 | STAr 163 | DEIr 164 | DEOr 165 | ADDr 166 | SUBr 167 | MULr 168 | DIVr 169 | ANDr 170 | ORAr 171 | EORr 172 | SFTr 173 | JSI 174 | INC2r 175 | POP2r 176 | NIP2r 177 | SWP2r 178 | ROT2r 179 | DUP2r 180 | OVR2r 181 | EQU2r 182 | NEQ2r 183 | GTH2r 184 | LTH2r 185 | JMP2r 186 | JCN2r 187 | JSR2r 188 | STH2r 189 | LDZ2r 190 | STZ2r 191 | LDR2r 192 | STR2r 193 | LDA2r 194 | STA2r 195 | DEI2r 196 | DEO2r 197 | ADD2r 198 | SUB2r 199 | MUL2r 200 | DIV2r 201 | AND2r 202 | ORA2r 203 | EOR2r 204 | SFT2r 205 | LIT 206 | INCk 207 | POPk 208 | NIPk 209 | SWPk 210 | ROTk 211 | DUPk 212 | OVRk 213 | EQUk 214 | NEQk 215 | GTHk 216 | LTHk 217 | JMPk 218 | JCNk 219 | JSRk 220 | STHk 221 | LDZk 222 | STZk 223 | LDRk 224 | STRk 225 | LDAk 226 | STAk 227 | DEIk 228 | DEOk 229 | ADDk 230 | SUBk 231 | MULk 232 | DIVk 233 | ANDk 234 | ORAk 235 | EORk 236 | SFTk 237 | LIT2 238 | INC2k 239 | POP2k 240 | NIP2k 241 | SWP2k 242 | ROT2k 243 | DUP2k 244 | OVR2k 245 | EQU2k 246 | NEQ2k 247 | GTH2k 248 | LTH2k 249 | JMP2k 250 | JCN2k 251 | JSR2k 252 | STH2k 253 | LDZ2k 254 | STZ2k 255 | LDR2k 256 | STR2k 257 | LDA2k 258 | STA2k 259 | DEI2k 260 | DEO2k 261 | ADD2k 262 | SUB2k 263 | MUL2k 264 | DIV2k 265 | AND2k 266 | ORA2k 267 | EOR2k 268 | SFT2k 269 | LITr 270 | INCkr 271 | POPkr 272 | NIPkr 273 | SWPkr 274 | ROTkr 275 | DUPkr 276 | OVRkr 277 | EQUkr 278 | NEQkr 279 | GTHkr 280 | LTHkr 281 | JMPkr 282 | JCNkr 283 | JSRkr 284 | STHkr 285 | LDZkr 286 | STZkr 287 | LDRkr 288 | STRkr 289 | LDAkr 290 | STAkr 291 | DEIkr 292 | DEOkr 293 | ADDkr 294 | SUBkr 295 | MULkr 296 | DIVkr 297 | ANDkr 298 | ORAkr 299 | EORkr 300 | SFTkr 301 | LIT2r 302 | INC2kr 303 | POP2kr 304 | NIP2kr 305 | SWP2kr 306 | ROT2kr 307 | DUP2kr 308 | OVR2kr 309 | EQU2kr 310 | NEQ2kr 311 | GTH2kr 312 | LTH2kr 313 | JMP2kr 314 | JCN2kr 315 | JSR2kr 316 | STH2kr 317 | LDZ2kr 318 | STZ2kr 319 | LDR2kr 320 | STR2kr 321 | LDA2kr 322 | STA2kr 323 | DEI2kr 324 | DEO2kr 325 | ADD2kr 326 | SUB2kr 327 | MUL2kr 328 | DIV2kr 329 | AND2kr 330 | ORA2kr 331 | EOR2kr 332 | SFT2kr 333 | ) 334 | 335 | func (o Op) String() string { return opStrings[o] } 336 | 337 | var opStrings = strings.Fields(` 338 | BRK 339 | INC 340 | POP 341 | NIP 342 | SWP 343 | ROT 344 | DUP 345 | OVR 346 | EQU 347 | NEQ 348 | GTH 349 | LTH 350 | JMP 351 | JCN 352 | JSR 353 | STH 354 | LDZ 355 | STZ 356 | LDR 357 | STR 358 | LDA 359 | STA 360 | DEI 361 | DEO 362 | ADD 363 | SUB 364 | MUL 365 | DIV 366 | AND 367 | ORA 368 | EOR 369 | SFT 370 | JCI 371 | INC2 372 | POP2 373 | NIP2 374 | SWP2 375 | ROT2 376 | DUP2 377 | OVR2 378 | EQU2 379 | NEQ2 380 | GTH2 381 | LTH2 382 | JMP2 383 | JCN2 384 | JSR2 385 | STH2 386 | LDZ2 387 | STZ2 388 | LDR2 389 | STR2 390 | LDA2 391 | STA2 392 | DEI2 393 | DEO2 394 | ADD2 395 | SUB2 396 | MUL2 397 | DIV2 398 | AND2 399 | ORA2 400 | EOR2 401 | SFT2 402 | JMI 403 | INCr 404 | POPr 405 | NIPr 406 | SWPr 407 | ROTr 408 | DUPr 409 | OVRr 410 | EQUr 411 | NEQr 412 | GTHr 413 | LTHr 414 | JMPr 415 | JCNr 416 | JSRr 417 | STHr 418 | LDZr 419 | STZr 420 | LDRr 421 | STRr 422 | LDAr 423 | STAr 424 | DEIr 425 | DEOr 426 | ADDr 427 | SUBr 428 | MULr 429 | DIVr 430 | ANDr 431 | ORAr 432 | EORr 433 | SFTr 434 | JSI 435 | INC2r 436 | POP2r 437 | NIP2r 438 | SWP2r 439 | ROT2r 440 | DUP2r 441 | OVR2r 442 | EQU2r 443 | NEQ2r 444 | GTH2r 445 | LTH2r 446 | JMP2r 447 | JCN2r 448 | JSR2r 449 | STH2r 450 | LDZ2r 451 | STZ2r 452 | LDR2r 453 | STR2r 454 | LDA2r 455 | STA2r 456 | DEI2r 457 | DEO2r 458 | ADD2r 459 | SUB2r 460 | MUL2r 461 | DIV2r 462 | AND2r 463 | ORA2r 464 | EOR2r 465 | SFT2r 466 | LIT 467 | INCk 468 | POPk 469 | NIPk 470 | SWPk 471 | ROTk 472 | DUPk 473 | OVRk 474 | EQUk 475 | NEQk 476 | GTHk 477 | LTHk 478 | JMPk 479 | JCNk 480 | JSRk 481 | STHk 482 | LDZk 483 | STZk 484 | LDRk 485 | STRk 486 | LDAk 487 | STAk 488 | DEIk 489 | DEOk 490 | ADDk 491 | SUBk 492 | MULk 493 | DIVk 494 | ANDk 495 | ORAk 496 | EORk 497 | SFTk 498 | LIT2 499 | INC2k 500 | POP2k 501 | NIP2k 502 | SWP2k 503 | ROT2k 504 | DUP2k 505 | OVR2k 506 | EQU2k 507 | NEQ2k 508 | GTH2k 509 | LTH2k 510 | JMP2k 511 | JCN2k 512 | JSR2k 513 | STH2k 514 | LDZ2k 515 | STZ2k 516 | LDR2k 517 | STR2k 518 | LDA2k 519 | STA2k 520 | DEI2k 521 | DEO2k 522 | ADD2k 523 | SUB2k 524 | MUL2k 525 | DIV2k 526 | AND2k 527 | ORA2k 528 | EOR2k 529 | SFT2k 530 | LITr 531 | INCkr 532 | POPkr 533 | NIPkr 534 | SWPkr 535 | ROTkr 536 | DUPkr 537 | OVRkr 538 | EQUkr 539 | NEQkr 540 | GTHkr 541 | LTHkr 542 | JMPkr 543 | JCNkr 544 | JSRkr 545 | STHkr 546 | LDZkr 547 | STZkr 548 | LDRkr 549 | STRkr 550 | LDAkr 551 | STAkr 552 | DEIkr 553 | DEOkr 554 | ADDkr 555 | SUBkr 556 | MULkr 557 | DIVkr 558 | ANDkr 559 | ORAkr 560 | EORkr 561 | SFTkr 562 | LIT2r 563 | INC2kr 564 | POP2kr 565 | NIP2kr 566 | SWP2kr 567 | ROT2kr 568 | DUP2kr 569 | OVR2kr 570 | EQU2kr 571 | NEQ2kr 572 | GTH2kr 573 | LTH2kr 574 | JMP2kr 575 | JCN2kr 576 | JSR2kr 577 | STH2kr 578 | LDZ2kr 579 | STZ2kr 580 | LDR2kr 581 | STR2kr 582 | LDA2kr 583 | STA2kr 584 | DEI2kr 585 | DEO2kr 586 | ADD2kr 587 | SUB2kr 588 | MUL2kr 589 | DIV2kr 590 | AND2kr 591 | ORA2kr 592 | EOR2kr 593 | SFT2kr 594 | `) 595 | -------------------------------------------------------------------------------- /uxn/op_test.go: -------------------------------------------------------------------------------- 1 | package uxn 2 | 3 | import "testing" 4 | 5 | func TestOpBase(t *testing.T) { 6 | except := map[Op]Op{ 7 | JCI: JCI, 8 | JMI: JMI, 9 | JSI: JSI, 10 | LIT: LIT, 11 | LIT2: LIT, 12 | LITr: LIT, 13 | LIT2r: LIT, 14 | } 15 | for _, o := range allOps() { 16 | got := o.Base() 17 | want := o & 0x1f 18 | if w, ok := except[o]; ok { 19 | want = w 20 | } 21 | if got != want { 22 | t.Errorf("Base(%v) returned %v, want %v", o, got, want) 23 | } 24 | } 25 | } 26 | 27 | // Check that there are string versions for every opcode, 28 | // and that the flags correspond to the strings. 29 | func TestOpString(t *testing.T) { 30 | for _, o := range allOps() { 31 | got := o.String() 32 | want := o.Base().String() 33 | if o.Short() { 34 | want += "2" 35 | } 36 | if o.Keep() { 37 | want += "k" 38 | } 39 | if o.Return() { 40 | want += "r" 41 | } 42 | if got != want { 43 | t.Errorf("Op(%x).String() returned %q, want %q", byte(o), got, want) 44 | } 45 | } 46 | } 47 | 48 | func allOps() []Op { 49 | ops := make([]Op, 0x100) 50 | for i := range ops { 51 | ops[i] = Op(i) 52 | } 53 | return ops 54 | } 55 | -------------------------------------------------------------------------------- /uxn/stack.go: -------------------------------------------------------------------------------- 1 | package uxn 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Stack implements a Uxn CPU stack. 9 | type Stack struct { 10 | Bytes [255]byte 11 | Ptr byte 12 | } 13 | 14 | func (s *Stack) Peek() (byte, bool) { 15 | if s.Ptr == 0 { 16 | return 0, false 17 | } 18 | return s.Bytes[s.Ptr-1], true 19 | } 20 | 21 | func (s *Stack) PeekOffset() (uint16, bool) { 22 | b, ok := s.Peek() 23 | return uint16(int8(b)), ok 24 | } 25 | 26 | func (s *Stack) PeekShort() (uint16, bool) { 27 | if s.Ptr < 2 { 28 | return 0, false 29 | } 30 | return short(s.Bytes[s.Ptr-2], s.Bytes[s.Ptr-1]), true 31 | } 32 | 33 | func (s *Stack) wrap() *stackWrapper { return s.keep(false) } 34 | 35 | func (s *Stack) keep(keep bool) *stackWrapper { 36 | return &stackWrapper{Stack: s, keep: keep} 37 | } 38 | 39 | type stackWrapper struct { 40 | *Stack 41 | keep bool 42 | popped byte 43 | pushed bool 44 | } 45 | 46 | func (s *stackWrapper) Pop() byte { 47 | if s.pushed { 48 | panic("internal error: Pop after Push in StackWrapper") 49 | } 50 | if s.Ptr-s.popped == 0 { 51 | panic(Underflow) 52 | } 53 | if s.keep { 54 | s.popped++ 55 | } else { 56 | s.Ptr-- 57 | } 58 | return s.Bytes[s.Ptr-s.popped] 59 | } 60 | 61 | func (s *stackWrapper) Push(v byte) { 62 | if s.Ptr == 255 { 63 | panic(Overflow) 64 | } 65 | s.Bytes[s.Ptr] = v 66 | s.Ptr++ 67 | s.pushed = true 68 | } 69 | 70 | func (s *stackWrapper) PopShort() uint16 { 71 | return uint16(s.Pop()) + uint16(s.Pop())<<8 72 | } 73 | 74 | func (s *stackWrapper) PushShort(v uint16) { 75 | s.Push(byte(v >> 8)) 76 | s.Push(byte(v)) 77 | } 78 | 79 | func (s *stackWrapper) PopOffset() uint16 { 80 | return uint16(int8(s.Pop())) 81 | } 82 | 83 | func (s *stackWrapper) PushBool(b bool) { 84 | if b { 85 | s.Push(1) 86 | } else { 87 | s.Push(0) 88 | } 89 | } 90 | 91 | type shortPushPopper struct { 92 | *stackWrapper 93 | } 94 | 95 | func (s shortPushPopper) Pop() uint16 { return s.PopShort() } 96 | func (s shortPushPopper) Push(v uint16) { s.PushShort(v) } 97 | 98 | type pushPopper[T byte | uint16] interface { 99 | Pop() T 100 | Push(T) 101 | PushBool(bool) 102 | } 103 | 104 | var ( 105 | _ pushPopper[byte] = &stackWrapper{} 106 | _ pushPopper[uint16] = shortPushPopper{} 107 | ) 108 | 109 | func (s Stack) String() string { 110 | var b strings.Builder 111 | b.WriteByte('(') 112 | for _, v := range s.Bytes[:s.Ptr] { 113 | b.WriteByte(' ') 114 | fmt.Fprintf(&b, "%.2x", v) 115 | } 116 | b.WriteByte(' ') 117 | b.WriteByte(')') 118 | return b.String() 119 | } 120 | -------------------------------------------------------------------------------- /varvara/console.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | import ( 4 | "io" 5 | "log" 6 | ) 7 | 8 | type Console struct { 9 | Ready <-chan bool 10 | 11 | mem deviceMem 12 | input <-chan byte 13 | 14 | in io.Reader 15 | out, err io.Writer 16 | } 17 | 18 | func (c *Console) Vector() uint16 { return c.mem.short(0x0) } 19 | 20 | func (c *Console) In(p byte) byte { 21 | switch p { 22 | case 0x2: 23 | select { 24 | case b := <-c.input: 25 | c.mem[p] = b 26 | return b 27 | default: 28 | } 29 | } 30 | return c.mem[p] 31 | } 32 | 33 | func (c *Console) Out(p, b byte) { 34 | c.mem[p] = b 35 | switch p { 36 | case 0x01: 37 | if c.input == nil { 38 | var ( 39 | input = make(chan byte, 1) 40 | ready = make(chan bool) 41 | ) 42 | go c.readInput(input, ready) 43 | c.input, c.Ready = input, ready 44 | } 45 | case 0x08: 46 | c.out.Write([]byte{b}) 47 | case 0x09: 48 | c.err.Write([]byte{b}) 49 | } 50 | } 51 | 52 | func (c *Console) readInput(input chan<- byte, ready chan<- bool) { 53 | for { 54 | var b [1]byte 55 | if _, err := c.in.Read(b[:]); err != nil { 56 | log.Printf("reading stdin: %v", err) 57 | return 58 | } 59 | input <- b[0] 60 | ready <- true 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /varvara/controller.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | type Controller struct { 4 | inputDevice 5 | } 6 | 7 | type ControllerState struct { 8 | A, B, Select, Start bool 9 | Up, Down, Left, Right bool 10 | 11 | Key byte 12 | } 13 | 14 | func (c *Controller) Set(s *ControllerState) { 15 | u := false 16 | 17 | var b byte 18 | if s.A { 19 | b |= 0x01 20 | } 21 | if s.B { 22 | b |= 0x02 23 | } 24 | if s.Select { 25 | b |= 0x04 26 | } 27 | if s.Start { 28 | b |= 0x08 29 | } 30 | if s.Up { 31 | b |= 0x10 32 | } 33 | if s.Down { 34 | b |= 0x20 35 | } 36 | if s.Left { 37 | b |= 0x40 38 | } 39 | if s.Right { 40 | b |= 0x80 41 | } 42 | u = c.mem.setChanged(0x2, b) || u 43 | 44 | u = c.mem.setChanged(0x3, s.Key) || u 45 | 46 | if u { 47 | c.updated() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /varvara/datetime.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | import "time" 4 | 5 | type Datetime struct{} 6 | 7 | func (Datetime) In(p byte) byte { 8 | t := time.Now() 9 | switch p { 10 | case 0x0: 11 | return byte(t.Year() >> 8) 12 | case 0x1: 13 | return byte(t.Year()) 14 | case 0x2: 15 | return byte(t.Month()) - 1 // January is 0 16 | case 0x3: 17 | return byte(t.Day()) 18 | case 0x4: 19 | return byte(t.Hour()) 20 | case 0x5: 21 | return byte(t.Minute()) 22 | case 0x6: 23 | return byte(t.Second()) 24 | case 0x7: 25 | return byte(t.Weekday()) // Sunday is 0 26 | case 0x8: 27 | return byte((t.YearDay() - 1) >> 8) // 1 January is 0 28 | case 0x9: 29 | return byte(t.YearDay() - 1) 30 | case 0xa: 31 | if t.IsDST() { 32 | return 1 33 | } else { 34 | return 0 35 | } 36 | default: 37 | return 0 38 | } 39 | } 40 | 41 | func (Datetime) Out(p, b byte) {} 42 | -------------------------------------------------------------------------------- /varvara/file.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/fs" 8 | "log" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | type File struct { 16 | mem deviceMem 17 | main []byte // view of main memory 18 | 19 | append bool 20 | name string 21 | 22 | reader io.ReadCloser 23 | writer io.WriteCloser 24 | } 25 | 26 | func (f *File) setSuccess(v int) { f.mem.setShort(0x2, uint16(v)) } 27 | func (f *File) length() uint16 { return f.mem.short(0xa) } 28 | 29 | func (f *File) In(p byte) byte { 30 | switch p { 31 | case 0x8, 0x9: // name 32 | f.close() 33 | } 34 | return f.mem[p] 35 | } 36 | 37 | func (f *File) Out(p, b byte) { 38 | f.mem[p] = b 39 | switch p { 40 | 41 | case 0x5: // stat 42 | f.setSuccess(0) 43 | if f.length() == 0 { 44 | panic("file stat before setting length (or set zero length)") 45 | } 46 | if f.name == "" { 47 | panic("file stat before setting name") 48 | } 49 | fi, err := os.Stat(filepath.FromSlash(f.name)) 50 | if err != nil { 51 | log.Printf("stat file: %v", err) 52 | return 53 | } 54 | info := fileInfoBytes(fi) 55 | if len(info) > int(f.length()) { 56 | return 57 | } 58 | addr := f.mem.short(0x4) 59 | n := copy(f.main[addr:addr+f.length()], info) 60 | f.setSuccess(n) 61 | 62 | case 0x6: // delete 63 | if f.name == "" { 64 | panic("file delete before setting name") 65 | } 66 | if err := os.Remove(filepath.FromSlash(f.name)); err != nil { 67 | log.Printf("delete file: %v", err) 68 | } 69 | 70 | case 0x7: 71 | f.append = p == 0x01 72 | 73 | case 0x9: // name 74 | f.close() 75 | addr := f.mem.short(0x8) 76 | name, _, ok := bytes.Cut(f.main[addr:], []byte{0}) 77 | if !ok { 78 | panic("unterminated file name") 79 | } 80 | n := string(name) 81 | if n != "" { 82 | n = path.Clean(n) 83 | if path.IsAbs(n) || strings.HasPrefix(n, "../") { 84 | panic(fmt.Errorf("bad file name %q", n)) 85 | } 86 | } 87 | f.name = n 88 | 89 | case 0xd: // read 90 | f.setSuccess(0) 91 | if f.length() == 0 { 92 | panic("file read before setting length (or set zero length)") 93 | } 94 | if f.writer != nil { 95 | panic("file read after write; should re-open") 96 | } 97 | if f.reader == nil { 98 | if f.name == "" { 99 | panic("file read before setting name") 100 | } 101 | r, err := fileReader(filepath.FromSlash(f.name)) 102 | if err != nil { 103 | log.Printf("opening file: %v", err) 104 | return 105 | } 106 | f.reader = r 107 | } 108 | addr := f.mem.short(0xc) 109 | n, err := f.reader.Read(f.main[addr : addr+f.length()]) 110 | if err != nil && err != io.EOF { 111 | log.Printf("reading file: %v", err) 112 | return 113 | } 114 | f.setSuccess(n) 115 | 116 | case 0xf: // write 117 | f.setSuccess(0) 118 | if f.length() == 0 { 119 | panic("file write before setting length (or set zero length)") 120 | } 121 | if f.reader != nil { 122 | panic("file write after read; should re-open") 123 | } 124 | if f.writer == nil { 125 | if f.name == "" { 126 | panic("file write before setting name") 127 | } 128 | flag := os.O_WRONLY | os.O_CREATE 129 | if f.append { 130 | flag |= os.O_APPEND 131 | } 132 | fp, err := os.OpenFile(filepath.FromSlash(f.name), flag, 0644) 133 | if err != nil { 134 | log.Printf("opening file: %v", err) 135 | return 136 | } 137 | f.writer = fp 138 | } 139 | addr := f.mem.short(0xe) 140 | n, err := f.writer.Write(f.main[addr : addr+f.length()]) 141 | if err != nil { 142 | log.Printf("writing file: %v", err) 143 | return 144 | } 145 | f.setSuccess(n) 146 | } 147 | } 148 | 149 | func (f *File) close() { 150 | if w := f.writer; w != nil { 151 | if err := w.Close(); err != nil { 152 | log.Printf("closing file: %v", err) 153 | } 154 | f.writer = nil 155 | } 156 | if r := f.reader; r != nil { 157 | if err := r.Close(); err != nil { 158 | log.Printf("closing file: %v", err) 159 | } 160 | f.reader = nil 161 | } 162 | } 163 | 164 | func fileReader(name string) (io.ReadCloser, error) { 165 | fi, err := os.Stat(name) 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | if !fi.IsDir() { 171 | return os.Open(name) 172 | } 173 | 174 | des, err := os.ReadDir(name) 175 | if err != nil { 176 | return nil, err 177 | } 178 | var fis []fs.FileInfo 179 | for _, de := range des { 180 | fi, err := de.Info() 181 | if err != nil { 182 | return nil, err 183 | } 184 | fis = append(fis, fi) 185 | } 186 | r, w := io.Pipe() 187 | go func() { 188 | defer w.Close() 189 | for _, fi := range fis { 190 | w.Write(fileInfoBytes(fi)) 191 | } 192 | }() 193 | return io.NopCloser(r), nil 194 | } 195 | 196 | func fileInfoBytes(fi fs.FileInfo) []byte { 197 | var buf bytes.Buffer 198 | if fi.IsDir() { 199 | buf.WriteString("---- ") 200 | } else if size := fi.Size(); size > 0xffff { 201 | buf.WriteString("???? ") 202 | } else { 203 | fmt.Fprintf(&buf, "%.4x ", size) 204 | } 205 | buf.WriteString(filepath.ToSlash(fi.Name())) 206 | buf.WriteByte('\n') 207 | return buf.Bytes() 208 | } 209 | -------------------------------------------------------------------------------- /varvara/gui.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "log" 10 | "time" 11 | 12 | "golang.org/x/exp/shiny/driver" 13 | "golang.org/x/exp/shiny/screen" 14 | "golang.org/x/image/math/f64" 15 | "golang.org/x/mobile/event/key" 16 | "golang.org/x/mobile/event/lifecycle" 17 | "golang.org/x/mobile/event/mouse" 18 | "golang.org/x/mobile/event/paint" 19 | "golang.org/x/mobile/event/size" 20 | ) 21 | 22 | const debugGUI = false 23 | 24 | type Debugger interface { 25 | Debug(cmd string, addr uint16) 26 | } 27 | 28 | func NewGUI(v *Varvara, d Debugger) *GUI { 29 | up, done := make(chan bool), make(chan bool) 30 | return &GUI{ 31 | Update: up, doUpdate: up, 32 | UpdateDone: done, updateDone: done, 33 | v: v, 34 | debug: d, 35 | } 36 | } 37 | 38 | type GUI struct { 39 | Update chan<- bool 40 | UpdateDone <-chan bool 41 | 42 | doUpdate <-chan bool 43 | updateDone chan<- bool 44 | 45 | v *Varvara // only touch this in the update method! 46 | newV *Varvara // set after Swap, unset once swap happens 47 | 48 | debug Debugger 49 | 50 | ctrl ControllerState 51 | mouse MouseState 52 | 53 | // Screen 54 | wsize size.Event 55 | size image.Point 56 | fg, bg screen.Buffer 57 | tex screen.Texture 58 | xform f64.Aff3 // from varvara buffer to window buffer 59 | xformInv f64.Aff3 60 | ops int // updated to match v.scr.ops after copying fg/bg 61 | } 62 | 63 | // Swap replaces the Varvara attached to GUI with the given one. 64 | // This operation takes effect on the next update event. 65 | // Swap may only be called when a GUI update is not in progress. 66 | func (g *GUI) Swap(v *Varvara) { g.newV = v } 67 | 68 | type updateEvent struct{} 69 | 70 | var errCloseGUI = errors.New("close GUI") 71 | 72 | func (g *GUI) Run(exit <-chan bool) (err error) { 73 | defer close(g.updateDone) 74 | driver.Main(func(s screen.Screen) { 75 | var w screen.Window 76 | w, err = s.NewWindow(&screen.NewWindowOptions{Title: "nux"}) 77 | if err != nil { 78 | return 79 | } 80 | defer w.Release() 81 | defer g.release() 82 | 83 | go func() { 84 | t := time.NewTicker(time.Second / 60) 85 | defer t.Stop() 86 | for { 87 | select { 88 | case <-t.C: 89 | w.Send(updateEvent{}) 90 | case <-exit: 91 | return 92 | } 93 | } 94 | }() 95 | 96 | for err == nil { 97 | select { 98 | case <-exit: 99 | return 100 | default: 101 | } 102 | err = g.handle(s, w, w.NextEvent()) 103 | } 104 | }) 105 | if err == errCloseGUI { 106 | err = nil 107 | } 108 | return 109 | } 110 | 111 | func (g *GUI) handle(s screen.Screen, w screen.Window, e any) error { 112 | if debugGUI { 113 | switch e := e.(type) { 114 | case paint.Event: 115 | case updateEvent: 116 | default: 117 | format := "got %#v\n" 118 | if _, ok := e.(fmt.Stringer); ok { 119 | format = "got %v\n" 120 | } 121 | log.Printf(format, e) 122 | } 123 | } 124 | 125 | switch e := e.(type) { 126 | case size.Event: 127 | g.wsize = e 128 | if e.WidthPx+e.HeightPx == 0 { 129 | // Window closed. 130 | return errCloseGUI 131 | } 132 | if g.bg != nil { 133 | g.updateTransform() 134 | } 135 | 136 | case lifecycle.Event: 137 | if e.To == lifecycle.StageDead { 138 | return errCloseGUI 139 | } 140 | 141 | case key.Event: 142 | g.handleKey(e) 143 | 144 | case mouse.Event: 145 | g.handleMouse(e) 146 | 147 | case updateEvent: 148 | select { 149 | case <-g.doUpdate: 150 | err := g.update(s) 151 | g.updateDone <- true 152 | if err != nil { 153 | return fmt.Errorf("update: %v", err) 154 | } 155 | 156 | default: 157 | // uxn cpu is busy 158 | } 159 | g.paint(w) 160 | 161 | case error: 162 | log.Printf("gui: %v", e) 163 | } 164 | return nil 165 | } 166 | 167 | // update synchronizes state between gui and Varvara. 168 | // It must only be called when the Varvara CPU is not executing. 169 | func (g *GUI) update(s screen.Screen) (err error) { 170 | resetScreen := false 171 | if g.newV != nil { 172 | g.v = g.newV 173 | g.newV = nil 174 | g.ctrl = ControllerState{} 175 | g.mouse = MouseState{} 176 | resetScreen = true 177 | } 178 | 179 | g.v.cntrl.Set(&g.ctrl) 180 | g.v.mouse.Set(&g.mouse) 181 | 182 | // Screen 183 | g.size = image.Point{int(g.v.scr.Width()), int(g.v.scr.Height())} 184 | if g.size.X == 0 || g.size.Y == 0 { 185 | g.size = image.Point{0x100, 0x100} 186 | } 187 | if resetScreen || g.tex == nil || g.tex.Size() != g.size { 188 | g.release() 189 | g.fg, err = s.NewBuffer(g.size) 190 | if err != nil { 191 | return 192 | } 193 | g.bg, err = s.NewBuffer(g.size) 194 | if err != nil { 195 | return 196 | } 197 | g.tex, err = s.NewTexture(g.size) 198 | if err != nil { 199 | return 200 | } 201 | g.ops = -1 202 | g.updateTransform() 203 | } 204 | if o := g.v.scr.ops; g.ops != o { 205 | if m := g.v.scr.fg; m != nil && m.Bounds().Size() == g.size { 206 | copy(g.fg.RGBA().Pix, m.Pix) 207 | } 208 | if m := g.v.scr.bg; m != nil && m.Bounds().Size() == g.size { 209 | copy(g.bg.RGBA().Pix, m.Pix) 210 | } 211 | g.ops = o 212 | } 213 | return 214 | } 215 | 216 | func (g *GUI) updateTransform() { 217 | g.xform = paintTransform(g.wsize.Bounds(), g.bg.Bounds()) 218 | g.xformInv = invert(g.xform) 219 | } 220 | 221 | func (g *GUI) release() { 222 | if g.tex != nil { 223 | g.tex.Release() 224 | } 225 | if g.fg != nil { 226 | g.fg.Release() 227 | } 228 | if g.bg != nil { 229 | g.bg.Release() 230 | } 231 | } 232 | 233 | // paint draws bg and fg to the given window. 234 | func (g *GUI) paint(w screen.Window) { 235 | w.Fill(g.wsize.Bounds(), color.RGBA{0, 0, 0, 0}, draw.Src) 236 | if g.bg != nil { // fg, tex, and xform must also be set 237 | g.tex.Upload(image.Point{}, g.bg, g.bg.Bounds()) 238 | w.Draw(g.xform, g.tex, g.tex.Bounds(), draw.Src, nil) 239 | g.tex.Upload(image.Point{}, g.fg, g.fg.Bounds()) 240 | w.Draw(g.xform, g.tex, g.tex.Bounds(), draw.Over, nil) 241 | } 242 | w.Publish() 243 | } 244 | 245 | // paintTransform returns the affine transform that maps the pixels in the 246 | // source to the largest rectangle that fits inside the destination. 247 | func paintTransform(dst, src image.Rectangle) f64.Aff3 { 248 | var ( 249 | wx, wy = float64(dst.Dx()), float64(dst.Dy()) 250 | sx, sy = float64(src.Dx()), float64(src.Dy()) 251 | wr = float64(wx) / float64(wy) 252 | sr = float64(sx) / float64(sy) 253 | dx, dy float64 254 | ) 255 | if wr > sr { 256 | dx, dy = wy*sr, wy 257 | } else { 258 | dx, dy = wx, wx/sr 259 | } 260 | return f64.Aff3{ 261 | dx / sx, 0, (wx - dx) / 2, 262 | 0, dy / sy, (wy - dy) / 2, 263 | } 264 | } 265 | 266 | func (g *GUI) handleKey(e key.Event) { 267 | if e.Direction == key.DirPress { 268 | switch e.Code { 269 | case key.CodeF4: 270 | g.debug.Debug("reset", 0) 271 | return 272 | case key.CodeF5: 273 | g.debug.Debug("cont", 0) 274 | return 275 | case key.CodeF6: 276 | g.debug.Debug("step", 0) 277 | return 278 | case key.CodeF7: 279 | g.debug.Debug("halt", 0) 280 | return 281 | } 282 | } 283 | var ( 284 | s = &g.ctrl 285 | b = e.Direction == key.DirPress || e.Direction == 10 286 | ) 287 | switch e.Code { 288 | case key.CodeLeftControl: 289 | s.A = b 290 | case key.CodeLeftAlt: 291 | s.B = b 292 | case key.CodeLeftShift: 293 | s.Select = b 294 | case key.CodeHome: 295 | s.Start = b 296 | case key.CodeUpArrow: 297 | s.Up = b 298 | case key.CodeDownArrow: 299 | s.Down = b 300 | case key.CodeLeftArrow: 301 | s.Left = b 302 | case key.CodeRightArrow: 303 | s.Right = b 304 | } 305 | if 0 <= e.Rune && e.Rune < 0x80 { 306 | k := byte(e.Rune) 307 | if e.Modifiers&key.ModControl != 0 && 'A'-0x40 <= k && k <= 'Z'-0x40 { 308 | if e.Modifiers&key.ModShift != 0 { 309 | k += 0x40 310 | } else { 311 | k += 0x60 312 | } 313 | } 314 | s.Key = k 315 | } 316 | } 317 | 318 | func (g *GUI) handleMouse(e mouse.Event) { 319 | if g.bg == nil { 320 | // Screen not initialized; can't compute mouse x/y. 321 | return 322 | } 323 | var ( 324 | m = &g.mouse 325 | sx = float64(e.X) 326 | sy = float64(e.Y) 327 | t = g.xformInv 328 | ) 329 | m.X = clampInt16(t[0]*sx + t[1]*sy + t[2]) 330 | m.Y = clampInt16(t[3]*sx + t[4]*sy + t[5]) 331 | if e.Button >= 1 && e.Button <= 3 && e.Direction != mouse.DirNone { 332 | m.Button[e.Button-1] = e.Direction == mouse.DirPress 333 | } 334 | } 335 | 336 | func clampInt16(v float64) int16 { 337 | const max, min = 32767, -32768 338 | switch { 339 | case v > max: 340 | return max 341 | case v < min: 342 | return min 343 | default: 344 | return int16(v) 345 | } 346 | } 347 | 348 | func invert(m f64.Aff3) f64.Aff3 { 349 | m00 := +m[3*1+1] 350 | m01 := -m[3*0+1] 351 | m02 := +m[3*1+2]*m[3*0+1] - m[3*1+1]*m[3*0+2] 352 | m10 := -m[3*1+0] 353 | m11 := +m[3*0+0] 354 | m12 := +m[3*1+0]*m[3*0+2] - m[3*1+2]*m[3*0+0] 355 | 356 | det := m00*m11 - m10*m01 357 | 358 | return f64.Aff3{ 359 | m00 / det, 360 | m01 / det, 361 | m02 / det, 362 | m10 / det, 363 | m11 / det, 364 | m12 / det, 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /varvara/input.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | type inputDevice struct { 4 | Ready <-chan bool 5 | 6 | mem deviceMem 7 | ready chan bool 8 | } 9 | 10 | func (d *inputDevice) Vector() uint16 { return d.mem.short(0x0) } 11 | func (d *inputDevice) In(p byte) byte { return d.mem[p] } 12 | func (d *inputDevice) Out(p, b byte) { 13 | if p&0x1 == p && d.ready == nil { 14 | d.ready = make(chan bool, 1) 15 | d.Ready = d.ready 16 | } 17 | d.mem[p] = b 18 | } 19 | 20 | func (d *inputDevice) updated() { 21 | select { 22 | case d.ready <- true: 23 | default: 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /varvara/mouse.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | type Mouse struct { 4 | inputDevice 5 | } 6 | 7 | type MouseState struct { 8 | X, Y int16 9 | ScrollX, ScrollY int16 10 | Button [3]bool 11 | } 12 | 13 | func (m *Mouse) Set(s *MouseState) { 14 | u := false 15 | 16 | u = m.mem.setShortChanged(0x2, uint16(s.X)) || u 17 | u = m.mem.setShortChanged(0x4, uint16(s.Y)) || u 18 | u = m.mem.setShortChanged(0xa, uint16(s.ScrollX)) || u 19 | u = m.mem.setShortChanged(0xc, uint16(s.ScrollY)) || u 20 | 21 | var b byte 22 | if s.Button[0] { 23 | b |= 0x1 24 | } 25 | if s.Button[1] { 26 | b |= 0x2 27 | } 28 | if s.Button[2] { 29 | b |= 0x4 30 | } 31 | u = m.mem.setChanged(0x6, b) || u 32 | 33 | if u { 34 | m.updated() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /varvara/screen.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | ) 7 | 8 | type Screen struct { 9 | mem deviceMem 10 | main []byte // sprite data 11 | sys *System // r, g, b 12 | 13 | fg, bg *image.RGBA 14 | ops int // total count of draw operations 15 | } 16 | 17 | func (s *Screen) Vector() uint16 { return s.mem.short(0x0) } 18 | func (s *Screen) Width() uint16 { return s.mem.short(0x2) } 19 | func (s *Screen) Height() uint16 { return s.mem.short(0x4) } 20 | func (s *Screen) Auto() AutoByte { return AutoByte(s.mem[0x6]) } 21 | func (s *Screen) X() int16 { return int16(s.mem.short(0x8)) } 22 | func (s *Screen) Y() int16 { return int16(s.mem.short(0xa)) } 23 | func (s *Screen) Addr() uint16 { return s.mem.short(0xc) } 24 | 25 | func (s *Screen) setWidth(w uint16) { s.mem.setShort(0x2, w) } 26 | func (s *Screen) setHeight(h uint16) { s.mem.setShort(0x4, h) } 27 | func (s *Screen) setX(x int16) { s.mem.setShort(0x8, uint16(x)) } 28 | func (s *Screen) setY(y int16) { s.mem.setShort(0xa, uint16(y)) } 29 | func (s *Screen) setAddr(a uint16) { s.mem.setShort(0xc, a) } 30 | 31 | type AutoByte byte 32 | 33 | func (b AutoByte) X() bool { return b&0x01 != 0 } 34 | func (b AutoByte) Y() bool { return b&0x02 != 0 } 35 | func (b AutoByte) Addr() bool { return b&0x04 != 0 } 36 | func (b AutoByte) Count() int8 { return int8(b >> 4) } 37 | 38 | type drawOp byte 39 | 40 | func (b drawOp) Color() byte { return byte(b) & 0x03 } // pixel only 41 | func (b drawOp) Blend() byte { return byte(b) & 0x0f } // sprite only 42 | func (b drawOp) FlipX() bool { return b&0x10 != 0 } 43 | func (b drawOp) FlipY() bool { return b&0x20 != 0 } 44 | func (b drawOp) Foreground() bool { return b&0x40 != 0 } 45 | func (b drawOp) Fill() bool { return b&0x80 != 0 } // pixel only 46 | func (b drawOp) TwoBit() bool { return b&0x80 != 0 } // sprite only 47 | 48 | func (s *Screen) In(p byte) byte { 49 | return s.mem[p] 50 | } 51 | 52 | func (s *Screen) Out(p, v byte) { 53 | s.mem[p] = v 54 | 55 | switch p { 56 | default: 57 | return 58 | case 0xe: 59 | s.drawPixel(drawOp(v)) 60 | case 0xf: 61 | s.drawSprite(drawOp(v)) 62 | } 63 | s.ops++ 64 | } 65 | 66 | var transparent = color.RGBA{0, 0, 0, 0} 67 | 68 | func newImage(w, h uint16, c color.RGBA) *image.RGBA { 69 | m := image.NewRGBA(image.Rect(0, 0, int(w), int(h))) 70 | for b := m.Pix; len(b) >= 4; b = b[4:] { 71 | b[0] = c.R 72 | b[1] = c.G 73 | b[2] = c.B 74 | b[3] = c.A 75 | } 76 | return m 77 | } 78 | 79 | func (s *Screen) myImageFor(op drawOp) (*image.RGBA, [4]color.RGBA) { 80 | r, g, b := s.sys.Red(), s.sys.Green(), s.sys.Blue() 81 | theme := [4]color.RGBA{ 82 | {byte(r & 0xf000 >> 8), byte(g & 0xf000 >> 8), byte(b & 0xf000 >> 8), 0xff}, 83 | {byte(r & 0x0f00 >> 4), byte(g & 0x0f00 >> 4), byte(b & 0x0f00 >> 4), 0xff}, 84 | {byte(r & 0x00f0), byte(g & 0x00f0), byte(b & 0x00f0), 0xff}, 85 | {byte(r & 0x000f << 4), byte(g & 0x000f << 4), byte(b & 0x000f << 4), 0xff}, 86 | } 87 | size := image.Point{int(s.Width()), int(s.Height())} 88 | if s.fg == nil || s.fg.Bounds().Size() != size { 89 | s.fg = newImage(s.Width(), s.Height(), transparent) 90 | s.bg = newImage(s.Width(), s.Height(), theme[0]) 91 | } 92 | if op.Foreground() { 93 | return s.fg, theme 94 | } else { 95 | return s.bg, theme 96 | } 97 | } 98 | 99 | func (s *Screen) drawPixel(op drawOp) { 100 | m, theme := s.myImageFor(op) 101 | c := transparent 102 | if oc := op.Color(); oc > 0 || !op.Foreground() { 103 | c = theme[oc] 104 | } 105 | if op.Fill() { 106 | dx, dy := 1, 1 107 | if op.FlipX() { 108 | dx = -1 109 | } 110 | if op.FlipY() { 111 | dy = -1 112 | } 113 | size := m.Bounds().Size() 114 | for y := int(s.Y()); 0 <= y && y < size.Y; y += dy { 115 | for x := int(s.X()); 0 <= x && x < size.X; x += dx { 116 | m.SetRGBA(x, y, c) 117 | } 118 | } 119 | } else { 120 | m.SetRGBA(int(s.X()), int(s.Y()), c) 121 | } 122 | if s.Auto().X() { 123 | s.setX(s.X() + 1) 124 | } 125 | if s.Auto().Y() { 126 | s.setY(s.Y() + 1) 127 | } 128 | } 129 | 130 | func (s *Screen) drawSprite(op drawOp) { 131 | var ( 132 | m, theme = s.myImageFor(op) 133 | auto = s.Auto() 134 | addr = s.Addr() 135 | sx, sy = int(s.X()), int(s.Y()) // sprite top-left 136 | dx, dy = 1, 1 137 | // drawZero reports whether this blending mode should draw 138 | // color zero; if false, pixels of color zero are not set. 139 | drawZero = op.Blend() == 0 || op.Blend()%5 != 0 140 | sprite = s.main[addr:] 141 | ) 142 | if !op.FlipX() { 143 | dx = -1 144 | sx += 7 145 | } 146 | if op.FlipY() { 147 | dy = -1 148 | sy += 7 149 | } 150 | for i := auto.Count(); i >= 0; i-- { 151 | var x, y = sx, sy 152 | for j := 0; j < 8; j++ { 153 | pxA, pxB := sprite[j], sprite[j+8] 154 | for i := 0; i < 8; i++ { 155 | px := pxA & 1 156 | pxA >>= 1 157 | if op.TwoBit() { 158 | px |= pxB & 1 << 1 159 | pxB >>= 1 160 | } 161 | px = drawBlendingModes[px][op.Blend()] 162 | if drawZero || px > 0 { 163 | c := transparent 164 | if !op.Foreground() || px > 0 { 165 | c = theme[px] 166 | } 167 | m.Set(x, y, c) 168 | } 169 | x += dx 170 | } 171 | x += -dx * 8 172 | y += dy 173 | } 174 | if auto.X() { 175 | sy += 8 * dy 176 | } 177 | if auto.Y() { 178 | sx += 8 * -dx 179 | } 180 | if auto.Addr() { 181 | if op.TwoBit() { 182 | addr += 0x10 183 | } else { 184 | addr += 0x08 185 | } 186 | sprite = s.main[addr:] 187 | } 188 | } 189 | if auto.X() { 190 | s.setX(s.X() + 8*int16(-dx)) 191 | } 192 | if auto.Y() { 193 | s.setY(s.Y() + 8*int16(dy)) 194 | } 195 | if auto.Addr() { 196 | s.setAddr(addr) 197 | } 198 | } 199 | 200 | var drawBlendingModes = [4][16]byte{ 201 | {0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0}, 202 | {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}, 203 | {1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1}, 204 | {2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2}} 205 | -------------------------------------------------------------------------------- /varvara/system.go: -------------------------------------------------------------------------------- 1 | package varvara 2 | 3 | import ( 4 | "github.com/nf/nux/uxn" 5 | ) 6 | 7 | type System struct { 8 | mem deviceMem 9 | main []byte 10 | m *uxn.Machine 11 | state StateFunc 12 | } 13 | 14 | func (s *System) Halt() uint16 { return s.mem.short(0x0) } 15 | func (s *System) Red() uint16 { return s.mem.short(0x8) } 16 | func (s *System) Green() uint16 { return s.mem.short(0xa) } 17 | func (s *System) Blue() uint16 { return s.mem.short(0xc) } 18 | func (s *System) ExitCode() int { return int(s.mem[0xf] & 0x7f) } 19 | 20 | func (s *System) In(p byte) byte { 21 | return s.mem[p] 22 | } 23 | 24 | func (s *System) Out(p, b byte) { 25 | s.mem[p] = b 26 | switch p { 27 | case 0x3: 28 | switch addr := s.mem.short(0x2); s.main[addr] { 29 | case 0x01: // copy 30 | var ( 31 | v = func() uint16 { 32 | addr += 2 33 | return short(s.main[addr-1], s.main[addr]) 34 | } 35 | size = int(v()) 36 | src = int(v()%0x10) * 0x10000 37 | srcAddr = v() 38 | dst = int(v()%0x10) * 0x10000 39 | dstAddr = v() 40 | ) 41 | for i := 0; i < size; i++ { 42 | s.main[dst+int(dstAddr+uint16(i))] = s.main[src+int(srcAddr+uint16(i))] 43 | } 44 | } 45 | case 0xe: 46 | panic(uxn.Debug) 47 | case 0xf: 48 | if b != 0 { 49 | panic(uxn.Halt) // Stop execution. 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /varvara/varvara.go: -------------------------------------------------------------------------------- 1 | // Package varvara implements the Varvara computing stack. 2 | package varvara 3 | 4 | import ( 5 | "io" 6 | "log" 7 | "os" 8 | "sort" 9 | "sync/atomic" 10 | 11 | "github.com/nf/nux/uxn" 12 | ) 13 | 14 | type Runner struct { 15 | gui bool 16 | dev bool 17 | state StateFunc 18 | 19 | swap chan []byte 20 | swapDone chan bool 21 | debug chan debugOp 22 | 23 | stdout, stderr io.Writer 24 | } 25 | 26 | type StateFunc func(*uxn.Machine, StateKind) 27 | 28 | type StateKind byte 29 | 30 | const ( 31 | ClearState StateKind = iota 32 | HaltState 33 | PauseState 34 | BreakState 35 | DebugState 36 | QuietState 37 | ) 38 | 39 | func NewRunner(enableGUI, devMode bool, state StateFunc) *Runner { 40 | if state == nil { 41 | state = func(*uxn.Machine, StateKind) {} 42 | } 43 | return &Runner{ 44 | gui: enableGUI, 45 | dev: devMode, 46 | state: state, 47 | swap: make(chan []byte), 48 | swapDone: make(chan bool), 49 | debug: make(chan debugOp), 50 | 51 | stdout: os.Stdout, 52 | stderr: os.Stderr, 53 | } 54 | } 55 | 56 | type debugOp struct { 57 | cmd string 58 | addr uint16 59 | } 60 | 61 | func (r *Runner) SetOutput(w io.Writer) { 62 | r.stdout = w 63 | r.stderr = w 64 | } 65 | 66 | func (r *Runner) Debug(cmd string, addr uint16) { r.debug <- debugOp{cmd, addr} } 67 | 68 | func (r *Runner) Swap(rom []byte) { 69 | if !r.dev { 70 | panic("Reset called while not running in dev mode") 71 | } 72 | r.swap <- rom 73 | <-r.swapDone 74 | } 75 | 76 | func (r *Runner) Run(rom []byte) (exitCode int) { 77 | var v *Varvara 78 | newV := func() { 79 | prev := v 80 | v = New(rom, r.state, r.stdout, r.stderr) 81 | if prev != nil { 82 | v.debugAddr = prev.debugAddr 83 | v.breakAddrs.Store(prev.breakAddrs.Load()) 84 | } 85 | v.state(v.m, ClearState) 86 | } 87 | newV() 88 | var ( 89 | g = NewGUI(v, r) 90 | exit = make(chan bool) 91 | ) 92 | go func() { 93 | var ( 94 | execErr = make(chan error) 95 | running = false 96 | ) 97 | exec := func() { 98 | if running { 99 | return 100 | } 101 | running = true 102 | go func() { execErr <- v.Exec(g) }() 103 | if r.dev { 104 | log.Printf("uxn: started") 105 | } 106 | } 107 | halt := func() { 108 | if !running { 109 | return 110 | } 111 | running = false 112 | v.Halt() 113 | if err := <-execErr; err != nil { 114 | log.Printf("uxn: stopped: %v", err) 115 | } else { 116 | log.Printf("uxn: stopped") 117 | } 118 | } 119 | exec() 120 | for { 121 | select { 122 | case rom = <-r.swap: 123 | halt() 124 | newV() 125 | g.Swap(v) 126 | exec() 127 | r.swapDone <- true 128 | case err := <-execErr: 129 | running = false 130 | if r.dev { 131 | if err != nil { 132 | log.Printf("uxn: stopped: %v", err) 133 | } else { 134 | log.Printf("uxn: stopped") 135 | } 136 | } else { 137 | close(exit) 138 | return 139 | } 140 | case op := <-r.debug: 141 | switch op.cmd { 142 | case "halt": 143 | halt() 144 | case "reset": 145 | halt() 146 | newV() 147 | g.Swap(v) 148 | exec() 149 | case "step": 150 | v.Step() 151 | case "cont": 152 | v.Continue() 153 | case "break": 154 | v.SetBreak(op.addr) 155 | case "rmbreak": 156 | v.RemoveBreak(op.addr) 157 | case "exit": 158 | halt() 159 | close(exit) 160 | return 161 | } 162 | } 163 | } 164 | }() 165 | if r.gui { 166 | // If the GUI is enabled then Run will drive the GUI and the 167 | // screen vector until exit is closed. 168 | if err := g.Run(exit); err != nil { 169 | log.Fatalf("gui: %v", err) 170 | } 171 | } else { 172 | <-exit 173 | } 174 | return v.sys.ExitCode() 175 | } 176 | 177 | type Varvara struct { 178 | m *uxn.Machine 179 | sys System 180 | con Console 181 | scr Screen 182 | cntrl Controller 183 | mouse Mouse 184 | fileA File 185 | fileB File 186 | time Datetime 187 | 188 | state StateFunc 189 | 190 | // Atomics 191 | paused int32 192 | breakAddrs atomic.Value // addrSet 193 | debugAddr int32 194 | 195 | halted bool 196 | halt chan bool 197 | cont chan bool 198 | } 199 | 200 | func New(rom []byte, state StateFunc, stdout, stderr io.Writer) *Varvara { 201 | m := uxn.NewMachine(rom) 202 | v := &Varvara{ 203 | m: m, 204 | state: state, 205 | halt: make(chan bool), 206 | cont: make(chan bool), 207 | } 208 | m.Dev = v 209 | v.sys.main = m.Mem[:] 210 | v.sys.m = m 211 | v.sys.state = state 212 | v.con.in = os.Stdin 213 | v.con.out = stdout 214 | v.con.err = stderr 215 | v.scr.main = m.Mem[:] 216 | v.scr.sys = &v.sys 217 | v.scr.setWidth(0x100) 218 | v.scr.setHeight(0x100) 219 | v.fileA.main = m.Mem[:] 220 | v.fileB.main = m.Mem[:] 221 | v.breakAddrs.Store(addrSet(nil)) 222 | return v 223 | } 224 | 225 | func (v *Varvara) Halt() { 226 | if !v.halted { 227 | close(v.halt) 228 | v.halted = true 229 | } 230 | atomic.StoreInt32(&v.paused, 1) 231 | } 232 | 233 | func (v *Varvara) Continue() { 234 | atomic.StoreInt32(&v.paused, 0) 235 | select { 236 | case v.cont <- true: 237 | default: 238 | } 239 | } 240 | 241 | func (v *Varvara) Step() { 242 | atomic.StoreInt32(&v.paused, 1) 243 | select { 244 | case v.cont <- true: 245 | default: 246 | } 247 | } 248 | 249 | func (v *Varvara) SetBreak(addr uint16) { 250 | s, _ := v.breakAddrs.Load().(addrSet) 251 | if s.add(addr) { 252 | v.breakAddrs.Store(s) 253 | } 254 | } 255 | 256 | func (v *Varvara) RemoveBreak(addr uint16) { 257 | if addr == 0 { 258 | v.breakAddrs.Store(addrSet(nil)) 259 | return 260 | } 261 | s, _ := v.breakAddrs.Load().(addrSet) 262 | if s.remove(addr) { 263 | v.breakAddrs.Store(s) 264 | } 265 | } 266 | 267 | func (v *Varvara) Exec(g *GUI) error { 268 | defer v.state(v.m, HaltState) 269 | for { 270 | clear := false 271 | for { 272 | wait := false 273 | breakAddrs, _ := v.breakAddrs.Load().(addrSet) 274 | switch { 275 | case breakAddrs.contains(v.m.PC): 276 | v.state(v.m, BreakState) 277 | wait = true 278 | case atomic.LoadInt32(&v.paused) != 0: 279 | v.state(v.m, PauseState) 280 | wait = true 281 | } 282 | if wait { 283 | select { 284 | case <-v.halt: 285 | return nil 286 | case <-v.cont: 287 | } 288 | // Send the clear state after we resume. 289 | clear = true 290 | } 291 | if err := v.m.Exec(); err == uxn.ErrBRK { 292 | break 293 | } else if err != nil { 294 | h, ok := err.(uxn.HaltError) 295 | if ok && h.HaltCode == uxn.Debug { 296 | v.state(v.m, DebugState) 297 | continue 298 | } 299 | if ok { 300 | if h.HaltCode == uxn.Halt { 301 | return nil 302 | } 303 | if vec := v.sys.Halt(); vec > 0 { 304 | v.m.Work.Ptr = 4 305 | v.m.Work.Bytes[0] = byte(h.Addr >> 8) 306 | v.m.Work.Bytes[1] = byte(h.Addr) 307 | v.m.Work.Bytes[2] = byte(h.Op) 308 | v.m.Work.Bytes[3] = byte(h.HaltCode) 309 | v.m.Ret.Ptr = 0 310 | v.m.PC = vec 311 | continue 312 | } 313 | } 314 | return err 315 | } 316 | } 317 | if clear { 318 | v.state(v.m, ClearState) 319 | } else { 320 | v.state(v.m, QuietState) 321 | } 322 | 323 | var vector uint16 324 | for vector == 0 { 325 | select { 326 | case <-v.con.Ready: 327 | vector = v.con.Vector() 328 | case <-v.cntrl.Ready: 329 | vector = v.cntrl.Vector() 330 | case <-v.mouse.Ready: 331 | vector = v.mouse.Vector() 332 | case g.Update <- true: 333 | <-g.UpdateDone 334 | vector = v.scr.Vector() 335 | case <-v.halt: 336 | return nil 337 | } 338 | } 339 | v.m.PC = vector 340 | } 341 | } 342 | 343 | func (v *Varvara) In(p byte) byte { 344 | dev := p & 0xf0 345 | p &= 0xf 346 | switch dev { 347 | case 0x00: 348 | return v.sys.In(p) 349 | case 0x10: 350 | return v.con.In(p) 351 | case 0x20: 352 | return v.scr.In(p) 353 | case 0x80: 354 | return v.cntrl.In(p) 355 | case 0x90: 356 | return v.mouse.In(p) 357 | case 0xa0: 358 | return v.fileA.In(p) 359 | case 0xb0: 360 | return v.fileB.In(p) 361 | case 0xc0: 362 | return v.time.In(p) 363 | default: 364 | return 0 // Unimplemented device. 365 | } 366 | } 367 | 368 | func (v *Varvara) InShort(p byte) uint16 { 369 | return short(v.In(p), v.In(p+1)) 370 | } 371 | 372 | func (v *Varvara) Out(p, b byte) { 373 | dev := p & 0xf0 374 | p &= 0xf 375 | switch dev { 376 | case 0x00: 377 | v.sys.Out(p, b) 378 | case 0x10: 379 | v.con.Out(p, b) 380 | case 0x20: 381 | v.scr.Out(p, b) 382 | case 0x80: 383 | v.cntrl.Out(p, b) 384 | case 0x90: 385 | v.mouse.Out(p, b) 386 | case 0xa0: 387 | v.fileA.Out(p, b) 388 | case 0xb0: 389 | v.fileB.Out(p, b) 390 | case 0xc0: 391 | v.time.Out(p, b) 392 | } 393 | } 394 | 395 | func (v *Varvara) OutShort(p byte, b uint16) { 396 | v.Out(p, byte(b>>8)) 397 | v.Out(p+1, byte(b)) 398 | } 399 | 400 | type deviceMem [16]byte 401 | 402 | func (m *deviceMem) short(addr byte) uint16 { 403 | return short(m[addr], m[addr+1]) 404 | } 405 | 406 | func (m *deviceMem) setShort(addr byte, v uint16) { 407 | m[addr] = byte(v >> 8) 408 | m[addr+1] = byte(v) 409 | } 410 | 411 | func (m *deviceMem) setChanged(addr, v byte) bool { 412 | if v == m[addr] { 413 | return false 414 | } 415 | m[addr] = v 416 | return true 417 | } 418 | 419 | func (m *deviceMem) setShortChanged(addr byte, v uint16) bool { 420 | if v == m.short(addr) { 421 | return false 422 | } 423 | m.setShort(addr, v) 424 | return true 425 | } 426 | 427 | func short(hi, lo byte) uint16 { 428 | return uint16(hi)<<8 + uint16(lo) 429 | } 430 | 431 | type addrSet []uint16 432 | 433 | func (s *addrSet) add(addr uint16) bool { 434 | if s.contains(addr) { 435 | return false 436 | } 437 | *s = append(*s, addr) 438 | sort.Slice(*s, func(i, j int) bool { return (*s)[i] < (*s)[j] }) 439 | return true 440 | } 441 | 442 | func (s *addrSet) remove(addr uint16) bool { 443 | for i, a := range *s { 444 | if a == addr { 445 | *s = append((*s)[:i], (*s)[i+1:]...) 446 | return true 447 | } else if a > addr { 448 | return false 449 | } 450 | } 451 | return false 452 | } 453 | 454 | func (s addrSet) contains(addr uint16) bool { 455 | for _, a := range s { 456 | if a == addr { 457 | return true 458 | } else if a > addr { 459 | return false 460 | } 461 | } 462 | return false 463 | } 464 | --------------------------------------------------------------------------------