Compare commits

...

45 Commits

Author SHA1 Message Date
Ondřej Hruška 169c003980
fix tests and warnings 4 years ago
Ondřej Hruška df8bfa91a2
Merge branch 'includes' 4 years ago
Ondřej Hruška 9f289cffd3
add includes 4 years ago
Ondřej Hruška 9d4fef5222
add file index field to SourcePosition, some refactor, put coroutines in Box 4 years ago
Ondřej Hruška daff23dd98
Add loop syntactic sugar; errors now use print instead of debug; add check for duplicate labels. 4 years ago
Ondřej Hruška fd90480cec
make stdin async 4 years ago
Ondřej Hruška f87d822432
more docs, fixes, examples 4 years ago
Ondřej Hruška 735f871ea0
Implement critical sections and timeslice setting cmd 4 years ago
Ondřej Hruška 9cd03ca8e3
make scheduler timeslice configurable 4 years ago
Ondřej Hruška 2752fa4beb
fix unit tests, cargo fiox 4 years ago
Ondřej Hruška 5107b78ae5
add warning and yield docs to readme 4 years ago
Ondřej Hruška d6fc82c533
improve readme 4 years ago
Ondřej Hruška 77efa0fdc0
typo 4 years ago
Ondřej Hruška 91939f520b
Merge branch 'coroutines' 4 years ago
Ondřej Hruška 5a0e370589
document coroutines 4 years ago
Ondřej Hruška af52270a29
add generator example 4 years ago
Ondřej Hruška c8d01b6151
coroutine fixes, first example 4 years ago
Ondřej Hruška 8659240d01
wip coroutines and scheduler 4 years ago
Ondřej Hruška d0d4f1c15e
add (cas), (casXX), and (bfcas) 4 years ago
Ondřej Hruška 4e67ac291f
update examples to use the new const eval syntax 4 years ago
Ondřej Hruška 66b3674f81
Merge branch 'consteval' 4 years ago
Ondřej Hruška 02f73a21f4
fixes, safeguards and docs for "compile-time arithmetics" 4 years ago
Ondřej Hruška 83996348cb
Implement compile-time immediate arithmetics 4 years ago
Ondřej Hruška cfcd099060
remove bad script 4 years ago
Ondřej Hruška 1cfab8d21e
fix rng not being inclusive 4 years ago
Ondřej Hruška 506a4e42a2
Mandelbrot interactive fixes + improvements 4 years ago
Ondřej Hruška 191ba495f2 Merge branch 'master' of cpsdqs/crsn into master 4 years ago
cpsdqs e6c8125cfe
Interactive mandelbrot: use quadtree table for nicer previews 4 years ago
cpsdqs ae82157d19
Add interactive mandelbrot example 4 years ago
Ondřej Hruška 40d4b272e4
add mandelbrot examples 4 years ago
Ondřej Hruška b5016c6b12
add key checking example, key and mouse constants 4 years ago
Ondřej Hruška 062095cc28 Merge branch 'master' of User_4574/crsn into master 4 years ago
Nat Lasseter ab5fed3cfa Fixed conditional parsing bug, npos and nneg options wrong way round 4 years ago
Ondřej Hruška 2800b8cab2
life is now better 4 years ago
Ondřej Hruška 54ff5fbce9
Merge branch 'nat-master' into master 4 years ago
Ondřej Hruška 8d10fa406d
Minor "else" improvements 4 years ago
Nat Lasseter 0006cd06ec Reduce true/false conditionals down to just 'else' and warn if else is used in non-final branch 4 years ago
Ondřej Hruška f61dc00291
add GOL example 4 years ago
Nat Lasseter 12e37d4335 Introduce the 'true' and 'false' conditions 4 years ago
Ondřej Hruška 453f421bae
remove junk 4 years ago
Ondřej Hruška aa20959d82
add a font demo 4 years ago
Ondřej Hruška 562c6baddc
fixes, graphic acceleration commands, upscaling pixels 4 years ago
Ondřej Hruška 9faeceba61
talk about constants 4 years ago
Ondřej Hruška d9be3278f5
docs for float 4 years ago
Ondřej Hruška 28fcec7dfc
Merge branch 'floats' into master 4 years ago
  1. 17
      Cargo.lock
  2. 2
      Cargo.toml
  3. 427
      README.md
  4. 6
      compile_examples.sh
  5. 44
      crsn/crsn-sexp/src/error.rs
  6. 11
      crsn/crsn-sexp/src/lib.rs
  7. 72
      crsn/crsn-sexp/src/position.rs
  8. 20
      crsn/crsn-sexp/src/test.rs
  9. 28
      crsn/src/asm/error.rs
  10. 14
      crsn/src/asm/instr/cond.rs
  11. 51
      crsn/src/asm/instr/flatten.rs
  12. 22
      crsn/src/asm/instr/op.rs
  13. 87
      crsn/src/asm/mod.rs
  14. 58
      crsn/src/asm/parse/arg_parser.rs
  15. 38
      crsn/src/asm/parse/mod.rs
  16. 114
      crsn/src/asm/parse/parse_data.rs
  17. 129
      crsn/src/asm/parse/parse_instr.rs
  18. 4
      crsn/src/asm/parse/parse_op.rs
  19. 21
      crsn/src/asm/patches/mod.rs
  20. 2
      crsn/src/asm/read_file.rs
  21. 17
      crsn/src/builtin/defs.rs
  22. 121
      crsn/src/builtin/exec.rs
  23. 123
      crsn/src/builtin/mod.rs
  24. 149
      crsn/src/builtin/parse.rs
  25. 22
      crsn/src/module/eval_res.rs
  26. 11
      crsn/src/module/mod.rs
  27. 4
      crsn/src/runtime/exec.rs
  28. 15
      crsn/src/runtime/fault.rs
  29. 6
      crsn/src/runtime/frame.rs
  30. 2
      crsn/src/runtime/frame/status.rs
  31. 11
      crsn/src/runtime/program.rs
  32. 243
      crsn/src/runtime/run_thread.rs
  33. 4
      crsn/src/runtime/run_thread/info.rs
  34. 155
      crsn/src/runtime/run_thread/state.rs
  35. 11
      crsn_arith/src/exec.rs
  36. 1
      crsn_buf/src/defs.rs
  37. 31
      crsn_buf/src/exec.rs
  38. 8
      crsn_buf/src/parse.rs
  39. 12
      crsn_screen/src/defs.rs
  40. 183
      crsn_screen/src/exec.rs
  41. 121
      crsn_screen/src/lib.rs
  42. 30
      crsn_screen/src/parse.rs
  43. 1
      crsn_stdio/Cargo.toml
  44. 173
      crsn_stdio/src/lib.rs
  45. 38
      examples/coroutines1.csn
  46. 44
      examples/coroutines2-gen.csn
  47. 44
      examples/coroutines3-crit.csn
  48. 17
      examples/coroutines4-io.csn
  49. 15
      examples/expr.csn
  50. 6
      examples/flipcoin.csn
  51. 1
      examples/font/.gitignore
  52. 7
      examples/font/Makefile
  53. 3
      examples/font/README.txt
  54. 40
      examples/font/convert.c
  55. 259
      examples/font/font.csn
  56. 174
      examples/font/font.xbm
  57. 18
      examples/include/main.csn
  58. 3
      examples/include/other.csn
  59. 14
      examples/include/utils/itoa.csn
  60. 17
      examples/include/utils/printnum.csn
  61. 27
      examples/itoa.csn
  62. 236
      examples/life.csn
  63. 43
      examples/loop.csn
  64. 87
      examples/mandelbrot/mandelbrot-ascii.csn
  65. 95
      examples/mandelbrot/mandelbrot-full.csn
  66. 305
      examples/mandelbrot/mandelbrot-interactive.csn
  67. 97
      examples/mandelbrot/mandelbrot-lowres.csn
  68. 48
      examples/mandelbrot/output-ascii.txt
  69. BIN
      examples/mandelbrot/output-full.png
  70. 353
      examples/screen_font.csn
  71. 58
      examples/screen_keys.csn
  72. 28
      examples/screen_pick_color.csn
  73. 14
      examples/screen_upscale.csn
  74. 15
      examples/screen_upscale_mouse.csn
  75. 36
      examples/test_cond.csn
  76. 39
      launcher/src/main.rs

17
Cargo.lock generated

@ -216,6 +216,7 @@ version = "0.1.0"
dependencies = [
"crsn",
"libc",
"log",
]
[[package]]
@ -366,22 +367,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "launcher_nox"
version = "0.1.0"
dependencies = [
"anyhow",
"clappconfig",
"crsn",
"crsn_arith",
"crsn_buf",
"crsn_stdio",
"log",
"serde",
"simple_logger",
"thiserror",
]
[[package]]
name = "lazy_static"
version = "1.4.0"

@ -1,7 +1,7 @@
[workspace]
members = [
"launcher",
"launcher_nox",
#"launcher_nox",
"crsn",
"crsn_arith",
"crsn_screen",

@ -98,6 +98,7 @@ These keywords (among others) are used in conditional branches to specify flag t
- `nem`, `nempty` … Not empty
- `eof` … EOF
- `neof` … Not EOF
- `else` … Always true, may be used in the last branch
# Syntax
@ -135,6 +136,29 @@ The same program can be written in a compact form:
((ld r0 100)(:again)(sub r0 1 (nz? (j :again))))
```
### Includes
Croissant allows program composition using includes.
```
(include path)
```
Path can be a filesystem absolute or relative path (with slashes). Relative path is always resolved from the current source file.
Use double quotes if needed.
If the included file is missing an extension, `.csn` is appended automatically.
Each included file must contain a list of instructions or procedures, just like the main file.
Includes work at the S-expression level. The included file is parsed to instructions and inserted
in place of the include directive. Error reports will always show which file the line and column refer to.
There are no special scoping rules. It is possible to define a label in a file and jump to it from another.
Defines and register aliases (sym) also work cross-file.
Don't go too crazy with includes, it can get messy. Or do, I'm not your mom.
## Instruction
Instructions are written like this:
@ -169,7 +193,8 @@ This document uses the following notation for arguments:
- `NUM` - literal values
- unsigned `123`
- signed `-123`
- float `-45.6789`
- float `-45.6789`. For now, you must use literals with a period to enter float literals, integers will not be converted to
float when used in floating point instructions!
- hex `0xabcd`, `#abcd`
- binary `0b0101`
- character `'a'`, `'🐁'`. Supports unicode and C-style escapes. Use `\\` for a literal backslash.
@ -185,7 +210,8 @@ The different ways to specify a value can be grouped as "reads" and "writes":
- `RW` - intersection of the two sets, capable of reading and writing: `REG`, `SYM`, `@REG`, `@SYM`
Objects (`@reg`, `@sym`) can be read or written as if they were a register, but only if the referenced object supports it.
Other objects may produce a runtime fault or set the INVALID flag.
Other objects may produce a runtime fault or set the INVALID flag.
The object syntax is also used to read values yielded by a generator-like coroutine.
In the instruction lists below, I will use the symbols `Rd` for reads, `Wr` for writes, `RW` for read-writes, and `@Obj` for object handles,
with optional description after an apostrophe, such as: `(add Wr'dst Rd'a Rd'b)`.
@ -194,6 +220,35 @@ Some instructions use bit offsets and widths. Width is directly attached to the
offsets are attached at the respective arguments after a colon: `(ld16 r0:8 r1:32)` -
load 16 bits, starting at offset 32 of `r1`, into `r0`, starting at offset 8. The rest if the register is not affected.
### Compile-time arithmetics
Quite often you will want to do some calculations at compile time, for example to create a constant whose value depends on another,
or to compose a binary value using bit shifts and masking. This can be done in any place that expects an input operand (`Rd`).
The syntax for this is as follows:
```
(ld r0 (=add 123 456))
```
The expressions can be nested. The equals sign is not required in the inner levels, and the output operand must be omitted
(the compiler inserts a placeholder register there during evaluation):
```
(def WIDTH 1024)
(def HALFW_MAX (=sub (div WIDTH 2) 1))
```
Almost any instruction can be evaluated this way, excluding instructions that perform IO, work with the screen, objects etc.
Instructions that are not compile-time evaluation safe will produce a compile error.
There are several limitations to consider:
- Only immediate values (literals) and constant names may be used.
- Registers, if used, will always read as zero and writes have no effect. (This should also produce an error)
- Only instructions that take the form `(op Wr ...)` can be used. The `Wr` operand is inserted automatically and must NOT be specified!
- Conditionals are not allowed inside expressions: branches, conditional suffixes
### Conditional branches
Conditonal branches are written like this:
@ -206,6 +261,7 @@ Conditonal branches are written like this:
then only one branch is taken - there is no fall-through.
- The definition order is preserved, i.e. if the `inval` flag is to be checked, it should be done
before checking e.g. `nz`, which is, incidentally, true by default, because most flags are cleared by instructions that affects flags.
- `else` can be used as a final choice of branch that will always be taken.
## Routines
@ -254,7 +310,105 @@ It can also be written like this:
...
)
```
## Coroutines
Croissant implements something that could be called "pre-emptive coroutines". They do not provide any performance gain,
but add asynchronicity to the program, and can work as generators!
There is no true parallelism, it is difficult to implement safely and efficiently with a global state.
### Spawning
*Any procedure can be used as a coroutine.*
A coroutine is created using the `spawn` instruction, which produces an object handle.
```
(spawn r0 do_stuff 1 2 3)
```
At this point, the program is evenly divided between the original and the coroutine "thread".
The spawned coroutine is scheduled to run immediately after being spawned.
### Task switching
Coroutines take turns to execute the program. The scheduling interval can be configured.
Control can be given up using the `yield` instruction; for example, when waiting for a mutex. This happens automatically when
a `sleep` instruction is invoked.
### Race conditions
Take care when working with objects, resources and global registers: you can get race conditions
with coroutines. Use atomic instructions (`cas`, `casXX`, `bfcas`…) to implement synchronization.
The `casXX` instruction is very powerful: you can use one bit of a register as a mutex and the rest of it to store some useful data.
You can also use one register for up to 64 mutexes.
Remember to only use global registers (or buffer items) as mutexes: `g0`-`g15`. Each coroutine has its own set of *regular* registers.
Another way to avoid race conditions is to use **critical sections**.
Context switch (switching between active coroutines) is forbidden in a critical section. Try to keep critical sections as short as possible,
since they can distort sleep times and cause other similar problems.
```
(crit-begin)
...
(crit-end)
```
A safer way is to use the "critical block", which expands to the same, but also detects some common bugs at compile time,
like trying to jump out of the critical section.
```
(crit
...
)
```
Critical section nesting is allowed, but probably a bug.
**Beware deadlocks!**
### Using coroutines as generators
A coroutine can "yield a value" by invoking the `yield` instruction with an operand. This can be done any number of times.
```
(yield r0)
```
The coroutine is blocked until the value is consumed by someone. To consume a yielded value, read the coroutine object handle:
```
(spawn r5 foo)
(ld r0 @r5) ; read a yielded value
```
**Caution!!!** Due to the way this is implemented, the instruction that tries to use a yielded value might partially execute
before detecting that it is blocked, and will be retried when the thread is next scheduled to run. This has the consequence
that if something like a object handle is read by the same instruction, it may be read multiple times while waiting for the
yielded value. It is recommended to *never combine reads of a yielded value with reads of other object handles*.
### Joining a coroutine
Use the `join` instruction with a coroutine object handle to wait for its completion.
A coroutine completes by calling `ret` at its top level. This naturally means that a coroutine can return values!
The returned values are placed in the result registers, just like with the `call` instruction.
```
(spawn r5 foo)
; ...
(join @r5)
; res0-res15 now contain return values
```
# Instruction Set
Crsn instruction set is composed of extensions.
@ -277,6 +431,36 @@ to a varying number of skips and conditional instructions by the assembler. Only
Jumping to a label is always safer than a manual skip.
### Loop syntactic sugar
Infinite loops are a very common construct, so there is special syntax added for them.
These are converted by the assembler to an anonymous label and a jump to it.
```
(loop
...ops
)
```
If you want to have a convenient jump target, give the loop a name. This lets you easily "break" and "continue"
by jumping to the labels.
```
(loop :label
...ops
)
```
becomes
```
(:label)
...ops
(j :label)
(:label-end)
```
## Built-in Instructions
...and pseudo-instructions
@ -293,7 +477,9 @@ Jumping to a label is always safer than a manual skip.
; However, if the register is a global register, then the alias is valid everywhere.
(sym SYM REG)
; Define a constant. These are valid in the whole program.
; Define a constant. These are valid in the entire program following the definition point, unless
; un-defined. Constants are evaluated and assigned at compile time, the program control flow has no
; effect.
; Value must be known at compile time.
(def CONST VALUE)
@ -343,6 +529,18 @@ Jumping to a label is always safer than a manual skip.
; Offsets can be specified to work with arbitrary bit slices
(xchXX RW:offset RW:offset)
; Compare and swap; atomic instruction for coroutine synchronization
; - The "Equal" flag is set on success, use it with conditional branches or conditional execution.
; - The new value is not read until the comparison passes and it is needed.
; This behavior may matter for side effects when used with object handles.
(cas RW Rd'expected Rd'new)
; Compare and swap a bit slice; atomic instruction for coroutine synchronization
; See (cas) above for more info.
;
; Offsets can be specified to work with arbitrary bit slices
(casXX RW:offset Rd:offset'expected Rd:offset'new)
; Store status flags to a register
(stf Wr)
@ -357,9 +555,34 @@ Jumping to a label is always safer than a manual skip.
; The arguments are passed as argX. Return values are stored in resX registers.
(call PROC Rd...)
; Exit the current routine with return values
; Spawn a coroutine. The handle is stored in the output register.
(spawn Wr'handle PROC Rd...)
; Exit the current routine (or coroutine) with return values
(ret Rd...)
; Yield control from a coroutine (use when waiting for a mutex to give control early to
; other coroutines that might be holding it)
(yield)
; Yield a value generated by a coroutine. Gives up control and blocks until the value is consumed.
; Conversely, an instruction that tries to read a yielded value using the object handle is blocked
; until such value becomes available.
(yield Rd'value)
; Wait for a coroutine to complete, read its return values and delete it.
(join @Obj)
; Begin a critical section (no context switch allowed)
(crit-begin)
; End a critical section
(crit-end)
; Shortcut to define a critical section
(crit
...ops
)
; Generate a run-time fault with a debugger message
(fault)
(fault message)
@ -375,6 +598,9 @@ Jumping to a label is always safer than a manual skip.
; The label can be a numeric or string label, its sole purpose is tying the two together. They must be unique in the program.
(barrier-open LABEL)
(barrier-close LABEL)
; Set coroutine scheduler timeslice (in microseconds). Set to zero to disable preemption.
(rt-opt RT_TIMESLICE Rd'usec)
```
## Arithmetic Module
@ -428,6 +654,17 @@ Many instructions have two forms:
(mod Wr Rd Rd'divider)
(mod RW Rd'divider)
; Get abs value
(abs Wr Rd)
(abs RW)
; Get signum
(sgn Wr Rd)
(sgn RW)
; Power - e.g. (pow r0 2 8) is 256
(pow Wr Rd Rd'pow)
; Swap the 32-bit halves of a value
; 0x01234567_89abcdef -> 0x89abcdef_01234567
(sw32 Wr Rd)
@ -515,6 +752,125 @@ Many instructions have two forms:
(del @Rd)
```
### Floating Point Arithmetics
The arithmetics module has support for floating point values. There are some gotchas though:
- Floating point is simply the binary representation of it in an unsigned integer register.
I thought of adding special float registers for this, but then you can't easily pass floats
to subroutines, push them on a stack etc. Not worth it.
- To enter a float literal, always use the notation with a decimal point. It should support minus and scientific notation too.
- There are special instructions dedicated to working with floats. The regular integer instructions
will happily work with their binary forms, but that's absolutely not what you want.
```
(itf r0 1.0) ; NO!!! it is already float, what are you doing
(ld r0 1.0) ; okay
(itf r0 1) ; also okay
(fmul r0 2) ; NO!!!!!!!!!!!!! 2 is not float
(mul r0 2.0) ; ALSO NO!!!!!!!!!!!!! mul is not a float instruction!
(fmul r0 2.0) ; good
```
You have to be a bit careful, that's all.
Here's an abridged summary of the floating point submodule:
(most of these support the shorthand version too - `RW` in place of `Wr Rd`)
```
; Convert int to float
(itf Wr Rd)
; Convert float to int (round)
(fti Wr Rd)
; Convert float to int (ceil)
(ftic Wr Rd)
; Convert float to int (floor)
(ftif Wr Rd)
; Test properties of a float
; Set flags:
; NaN -> invalid
; Infinities -> overflow
; Positive, negative, zero
(ftst Rd)
; Float compare
(fcmp Rd Rd)
; Float range test
(fcmpr Rd Rd'min Rd'max)
; FloatRng. Unlike rng, frng is exclusive in the higher bound
(frng Wr Rd'min Rd'max)
; --- Basic float arith ---
; Float add
(fadd Wr Rd Rd)
; Float subtract
(fsub Wr Rd Rd)
; Float multiply
(fmul Wr Rd Rd)
; Float power
(fpow Wr Rd Rd'pow)
; Float root
(froot Wr Rd Rd'root)
; Float hyp - sqrt(a*a + b*b)
(fhyp Wr Rd Rd)
; Float divide
(fdiv Wr Wr'rem Rd'a Rd'div)
; Float modulo
(fmod Wr Rd'a Rd'div)
; Float abs value
(fabs Wr Rd)
; Float signum (returns -1 or 1)
(fsgn Wr Rd)
; --- Basic trig ---
; Float sine
(fsin Wr Rd)
; Float arcsine
(fasin Wr Rd)
; Float cosine
(fcos Wr Rd)
; Float arccosine
(facos Wr Rd)
; Float tangent
(ftan Wr Rd)
; Float arctangent
(fatan Wr Rd)
; Float 2-argumenmt arctangent
(fatan2 Wr Rd'y Rd'x)
; Float cotangent
(fcot Wr Rd)
; Float arccotangent
(facot Wr Rd)
; --- Hyperbolic trig ---
; Float hyperbolic sine
(fsinh Wr Rd)
; Float hyperbolic arcsine
(fasinh Wr Rd)
; Float hyperbolic cosine
(fcosh Wr Rd)
; Float hyperbolic arccosine
(facosh Wr Rd)
; Float hyperbolic tangent
(ftanh Wr Rd)
; Float hyperbolic arctangent
(fatanh Wr Rd)
; Float hyperbolic cotangent
(fcoth Wr Rd)
; Float hyperbolic arccotangent
(facoth Wr Rd)
```
Wow, thats a lot. I didn't test many of these yet. There may be bugs.
There are also some pre-defined constants: `PI`, `PI_2` (½×`PI`), `TAU` (2×`PI`), `E`
## Buffers Module
This module defines dynamic size integer buffers.
@ -571,6 +927,17 @@ Primitive buffer ops (position is always 0-based)
; Remove item at a position, shifting the rest to the left to fill the empty space
(bfrm Wr @Obj Rd:index)
; Buffer value compare and swap; atomic instruction for coroutine synchronization.
; - The "Equal" flag is set on success, use it with conditional branches or conditional execution.
; - The new value is not read until the comparison passes and it is needed.
; This behavior may matter for side effects when used with object handles.
;
; This instruction is useful when more than one lock is needed and they are stored in a buffer at well known positions.
; Naturally, the buffer must not be mutated in other ways that would undermine the locking.
;
; If an index just outside the buffer is used, the value is read as zero the position is created (if zero was expected).
(bfcas @Obj Rd:index Rd'expected Rd'new)
```
Whole buffer manipulation:
@ -611,7 +978,7 @@ To delete a buffer, use the `del` instruction - `(del @Obj)`
This module uses the minifb rust crate to provide a framebuffer with key and mouse input.
Colors use the `RRGGBB` hex format.
Colors use the `0xRRGGBB` or `#RRGGBB` hex format.
If input events are required, then make sure to periodically call `(sc-blit)` or `(sc-poll)`.
This may not be needed if the auto-blit function is enabled and the display is regularly written.
@ -626,17 +993,17 @@ such as animations.
; Initialize the screen (opens a window)
(sc-init WIDTH HEIGHT)
; Erase the screen (fill with black)
(sc-erase)
; Fill with a custom color
(sc-erase 0xFF00FF)
; Set pixel color
(sc-px X Y COLOR)
(sc-wr Rd'x Rd'y Rd'color) ; legacy name: sc-px
; Get pixel color
(sc-rd Wr'color Rd'x Rd'y)
; Set screen option. Constants are pre-defined.
; SCREEN_AUTO_BLIT (1) ... auto-blit (blit automatically on pixel write when needed to achieve the target FPS)
; SCREEN_FPS (2) ... frame rate
; - SCREEN_AUTO_BLIT (1) ... auto-blit (blit automatically on pixel write when needed to achieve the target FPS)
; - SCREEN_FPS (2) ......... frame rate
; - SCREEN_UPSCALE (3) ..... upscaling factor (big pixels).
; Scales coordinates for mouse and pixels and increases pixel area
(sc-opt OPTION VALUE)
; Blit (render the pixel buffer).
@ -654,11 +1021,37 @@ such as animations.
; Check key status. Keys are 0-127. Reads 1 if the key is pressed, 0 if not.
; A list of supported keys can be found in the extension source code.
(sc-key PRESSED KEY)
; Run the example screen_keys.scn to interactively check key codes.
(sc-key Wr'pressed Rd'num)
; Check mouse button state
; Check mouse button state.
; 0-left, 1-right, 2-middle
(sc-mbtn PRESSED BTN)
(sc-mbtn Wr'pressed Rd'btn)
```
Available constants provided by the module:
- SCREEN_AUTO_BLIT, SCREEN_FPS, SCREEN_UPSCALE,
- MBTN_LEFT, MBTN_RIGHT, MBTN_MIDDLE (MBTN_MID)
- KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 (0-9)
- KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z (10-35)
- KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15,
- KEY_Down, KEY_Left, KEY_Right, KEY_Up, KEY_Apos, KEY_Backtick, KEY_Backslash, KEY_Comma, KEY_Equal, KEY_BracketL, KEY_Minus, KEY_Period, KEY_BracketR, KEY_Semicolon, KEY_Slash, KEY_Backspace, KEY_Delete, KEY_End, KEY_Enter, KEY_Escape, KEY_Home, KEY_Insert, KEY_Menu, KEY_PageDown, KEY_PageUp, KEY_Pause, KEY_Space, KEY_Tab, KEY_NumLock, KEY_CapsLock, KEY_ScrollLock,
- KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9,
- KEY_KPDot, KEY_KPSlash, KEY_KPAsterisk, KEY_KPMinus, KEY_KPPlus, KEY_KPEnter,
- KEY_ShiftL, KEY_ShiftR, KEY_CtrlL, KEY_CtrlR, KEY_AltL, KEY_AltR, KEY_WinL, KEY_WinR,
### Graphic acceleration commands
```
; Fill a rectangle
(sc-rect Rd'x Rd'y Rd'w Rd'h Rd'color)
; Erase the screen (fill with black)
(sc-erase)
; Fill with a custom color
(sc-erase Rd'color)
```
## Stdio module

@ -4,8 +4,4 @@ set -e
cargo build
for file in examples/*.csn
do
echo "--- $file ---"
target/debug/launcher -P "$file"
done
find examples -name '*.csn' -type f -exec target/debug/launcher -P {} \;

@ -1,4 +1,6 @@
use std::{cmp, fmt};
use std::{fmt};
use super::SourcePosition;
use position::get_line_and_column;
/// The representation of an s-expression parse error.
pub struct Error {
@ -8,17 +10,6 @@ pub struct Error {
pub pos: SourcePosition,
}
/// Position in the input string
#[derive(Debug, PartialEq, Clone, Default)]
pub struct SourcePosition {
/// The line number on which the error occurred.
pub line: u32,
/// The column number on which the error occurred.
pub column: u32,
/// The index in the given string which caused the error.
pub index: u32,
}
/// Since errors are the uncommon case, they're boxed. This keeps the size of
/// structs down, which helps performance in the common case.
///
@ -32,7 +23,7 @@ pub(crate) type ERes<T> = Result<T, Err>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}:{}: {}", self.pos.line, self.pos.column, self.message)
write!(f, "{}: {}", self.pos, self.message)
}
}
@ -44,24 +35,6 @@ impl fmt::Debug for Error {
impl std::error::Error for Error {}
pub(crate) fn get_line_and_column(s: &str, pos: usize) -> SourcePosition {
let mut line: usize = 1;
let mut col: isize = -1;
for c in s.chars().take(pos + 1) {
if c == '\n' {
line += 1;
col = -1;
} else {
col += 1;
}
}
SourcePosition {
line: line as u32,
column: cmp::max(col, 0) as u32,
index: pos as u32,
}
}
#[cold]
fn err_impl(message: &'static str, s: &str, pos: usize) -> Err {
Box::new(Error {
@ -74,12 +47,3 @@ fn err_impl(message: &'static str, s: &str, pos: usize) -> Err {
pub(crate) fn err<T>(message: &'static str, s: &str, pos: usize) -> ERes<T> {
Err(err_impl(message, s, pos))
}
/// Build a span
pub(crate) fn spos(s: &str, pos: usize) -> SourcePosition {
if pos >= s.len() {
Default::default()
} else {
get_line_and_column(s, pos)
}
}

@ -8,14 +8,16 @@ use std::borrow::Cow;
use std::fmt;
use std::str::{self, FromStr};
use error::{ERes, err, spos};
use error::{ERes, err};
pub use error::Error;
pub use error::SourcePosition;
pub use position::SourcePosition;
use position::spos;
#[cfg(test)]
mod test;
mod error;
mod position;
/// A single data element in an s-expression. Floats are excluded to ensure
/// atoms may be used as keys in ordered and hashed data structures.
@ -331,10 +333,11 @@ fn parse_sexp(s: &str, pos: &mut usize) -> ERes<Sexp> {
//trace!("parse_sexp {}", pos);
zspace(s, pos)?;
let (c, _) = peek(s, *pos)?;
let pos0 = *pos;
let r = if c == '(' {
Ok(Sexp::List(parse_list(s, pos)?, spos(s, *pos)))
Ok(Sexp::List(parse_list(s, pos)?, spos(s, pos0)))
} else {
Ok(Sexp::Atom(parse_atom(s, pos)?, spos(s, *pos)))
Ok(Sexp::Atom(parse_atom(s, pos)?, spos(s, pos0)))
};
zspace(s, pos)?;
r

@ -0,0 +1,72 @@
use std::{fmt, cmp};
use std::fmt::{Formatter, Debug};
/// Position in the input string
#[derive(PartialEq, Clone, Default)]
pub struct SourcePosition {
/// The line number on which the error occurred.
pub line: u32,
/// The column number on which the error occurred.
pub column: u32,
/// The index in the given string which caused the error.
pub index: u32,
/// File index if there are multiple files
pub file: u32,
}
impl fmt::Display for SourcePosition {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}:{}", self.line, self.column)
}
}
impl SourcePosition {
pub fn with_file(mut self, file: u32) -> Self {
self.file = file;
self
}
}
impl Debug for SourcePosition {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("SourcePosition")
.field("line", &self.line)
.field("column", &self.column)
.field("index", &self.index)
.field("file", &self.file)
.finish()
} else {
// shorter version
write!(f, "Pos({}|{}:{})", self.file, self.line, self.column)
}
}
}
pub(crate) fn get_line_and_column(s: &str, pos: usize) -> SourcePosition {
let mut line: usize = 1;
let mut col: isize = -1;
for c in s.chars().take(pos + 1) {
if c == '\n' {
line += 1;
col = -1;
} else {
col += 1;
}
}
SourcePosition {
line: line as u32,
column: cmp::max(col, 0) as u32,
index: pos as u32,
file: 0
}
}
/// Build a span
pub(crate) fn spos(s: &str, pos: usize) -> SourcePosition {
if pos >= s.len() {
Default::default()
} else {
get_line_and_column(s, pos)
}
}

@ -1,5 +1,5 @@
use super::*;
use super::error::get_line_and_column;
use super::position::get_line_and_column;
#[test]
fn test_hello_world() {
@ -67,23 +67,23 @@ fn show_an_error() {
#[test]
fn line_and_col_test() {
let s = "0123456789\n0123456789\n\n6";
assert_eq!(get_line_and_column(s, 4), SourcePosition { line: 1, column: 4, index: 4 });
assert_eq!(get_line_and_column(s, 4), SourcePosition { line: 1, column: 4, index: 4, file: 0 });
assert_eq!(get_line_and_column(s, 10), SourcePosition { line: 2, column: 0, index: 10 });
assert_eq!(get_line_and_column(s, 11), SourcePosition { line: 2, column: 0, index: 11 });
assert_eq!(get_line_and_column(s, 15), SourcePosition { line: 2, column: 4, index: 15 });
assert_eq!(get_line_and_column(s, 10), SourcePosition { line: 2, column: 0, index: 10, file: 0 });
assert_eq!(get_line_and_column(s, 11), SourcePosition { line: 2, column: 0, index: 11, file: 0 });
assert_eq!(get_line_and_column(s, 15), SourcePosition { line: 2, column: 4, index: 15, file: 0 });
assert_eq!(get_line_and_column(s, 21), SourcePosition { line: 3, column: 0, index: 21 });
assert_eq!(get_line_and_column(s, 22), SourcePosition { line: 4, column: 0, index: 22 });
assert_eq!(get_line_and_column(s, 23), SourcePosition { line: 4, column: 0, index: 23 });
assert_eq!(get_line_and_column(s, 500), SourcePosition { line: 4, column: 0, index: 500 });
assert_eq!(get_line_and_column(s, 21), SourcePosition { line: 3, column: 0, index: 21, file: 0 });
assert_eq!(get_line_and_column(s, 22), SourcePosition { line: 4, column: 0, index: 22, file: 0 });
assert_eq!(get_line_and_column(s, 23), SourcePosition { line: 4, column: 0, index: 23, file: 0 });
assert_eq!(get_line_and_column(s, 500), SourcePosition { line: 4, column: 0, index: 500, file: 0 });
}
#[test]
fn sexp_size() {
// I just want to see when this changes, in the diff.
use std::mem;
assert_eq!(mem::size_of::<Sexp>(), mem::size_of::<isize>() * 6);
assert_eq!(mem::size_of::<Sexp>(), mem::size_of::<isize>() * 7);
}
#[test]

@ -8,18 +8,34 @@ use sexp::SourcePosition;
use crate::asm::data::{Register};
use crate::asm::data::literal::Label;
use crate::asm::instr::Cond;
use std::io;
/// csn_asm unified error type
#[derive(Error, Debug)]
pub enum CrsnError {
#[error("S-expression parsing error: {0:?}")]
#[error("S-expression parsing error: {0}")]
Sexp(#[from] Box<sexp::Error>),
#[error("Parse error: {0:?} at {1:?}")]
#[error("Parse error: {0} at {1}")]
Parse(Cow<'static, str>, SourcePosition),
#[error("Parse error: {0:?} at {1:?}")]
#[error("Parse error: {0} at {1}")]
ParseOther(Box<dyn Error + Send + Sync>, SourcePosition),
#[error("Assembler error: {0:?} at {1:?}")]
#[error("Assembler error: {0} at {1}")]
Asm(AsmError, SourcePosition),
#[error("IO error: {0}")]
IOError(#[from] io::Error),
}
impl CrsnError {
/// Get error pos
pub fn pos(&self) -> Option<&SourcePosition> {
match self {
CrsnError::Parse(_, p) => Some(p),
CrsnError::ParseOther(_, p) => Some(p),
CrsnError::Asm(_, p) => Some(p),
CrsnError::Sexp(se) => Some(&se.pos),
CrsnError::IOError(_) =>None,
}
}
}
/// Error from the assembler stage (after parsing S-expressions and basic validation)
@ -33,8 +49,10 @@ pub enum AsmError {
DiscardAsValue,
#[error("Conditional branch already defined for \"{0}\"")]
ConditionalAlreadyUsed(Cond),
#[error("Label \"{0:?}\" not defined")]
#[error("Label \"{0}\" not defined")]
LabelNotDefined(Label),
#[error("Label \"{0}\" already defined in this scope")]
LabelDuplicate(Label),
#[error("Bad register type: {0}")]
BadRegisterType(Register),
}

@ -106,6 +106,10 @@ pub enum Cond {
Eof,
/// Not empty
NotEof,
// Always true, for eg. (else? ...)
True,
/// Always false
False,
}
pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
@ -120,8 +124,8 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
"ge" | ">=" | "≥" => Cond::GreaterOrEqual,
"pos" | "+" | ">0" => Cond::Positive,
"neg" | "-" | "<0" => Cond::Negative,
"npos" | "0-" | "<=0" | "0" => Cond::NonPositive,
"nneg" | "0+" | ">=0" | "0" => Cond::NonNegative,
"npos" | "0-" | "<=0" | "0" => Cond::NonPositive,
"nneg" | "0+" | ">=0" | "0" => Cond::NonNegative,
"c" => Cond::Carry,
"nc" => Cond::NotCarry,
"em" | "empty" => Cond::Empty,
@ -134,6 +138,7 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
"neof" => Cond::NotEof,
"ov" => Cond::Overflow,
"nov" => Cond::NotOverflow,
"else" => Cond::True,
_ => {
return Err(CrsnError::Parse(format!("Unknown cond: {}", text).into(), pos.clone()));
}
@ -174,6 +179,8 @@ impl Display for Cond {
Cond::Valid => "valid",
Cond::Eof => "eof",
Cond::NotEof => "neof",
Cond::True => "else",
Cond::False => "never",
})
}
}
@ -214,6 +221,9 @@ impl Not for Cond {
Cond::NotEof => Cond::Eof,
Cond::Eof => Cond::NotEof,
Cond::True => Cond::False,
Cond::False => Cond::True,
}
}
}

@ -29,6 +29,16 @@ impl Flatten for () {
}
}
impl Flatten for Op {
fn flatten(self: Box<Self>, _label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
Ok(vec![*self])
}
fn pos(&self) -> SourcePosition {
self.pos.clone()
}
}
impl Flatten for InstrWithBranches {
fn pos(&self) -> SourcePosition {
self.pos.clone()
@ -49,6 +59,10 @@ impl Flatten for InstrWithBranches {
return Err(CrsnError::Asm(AsmError::ConditionalAlreadyUsed(cond), branch.pos()));
}
if cnt != branch_count - 1 && cond == Cond::True {
warn!("\"Else\" conditional used in non-final branch at {}", branch.pos());
}
let next_lbl = if cnt == branch_count - 1 {
end_lbl.clone()
} else {
@ -64,11 +78,13 @@ impl Flatten for InstrWithBranches {
// optimization for single-branch conditionals with a single instruction
ops.push(Op { cond: Some(cond), pos: pos.clone(), kind: flattened.remove(0).kind });
} else {
ops.push(Op {
kind: OpKind::BuiltIn(BuiltinOp::Jump(next_lbl.clone())),
pos: pos.clone(),
cond: Some(!cond),
});
if cond != Cond::True { // evoid emiting `op.never`
ops.push(Op {
kind: OpKind::BuiltIn(BuiltinOp::Jump(next_lbl.clone())),
pos: pos.clone(),
cond: Some(!cond),
});
}
ops.extend(flattened);
}
@ -105,6 +121,23 @@ impl Flatten for Vec<Box<dyn Flatten>> {
}
}
impl Flatten for Vec<Op> {
fn pos(&self) -> SourcePosition {
match self.first() {
None => {
Default::default()
}
Some(f) => {
f.pos()
}
}
}
fn flatten(self: Box<Self>, _label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
Ok(*self)
}
}
impl Flatten for Routine {
fn pos(&self) -> SourcePosition {
self.pos.clone()
@ -132,15 +165,19 @@ impl Flatten for Routine {
}.into_op(self_pos)
);
labels_to_skips(ops)
jumps_to_skips(ops)
}
}
/// Convert jumps to relative skips
pub fn labels_to_skips(ops: Vec<Op>) -> Result<Vec<Op>, CrsnError> {
pub fn jumps_to_skips(ops: Vec<Op>) -> Result<Vec<Op>, CrsnError> {
let mut label_positions = HashMap::<Label, usize>::new();
for (n, op) in ops.iter().enumerate() {
if let OpKind::BuiltIn(BuiltinOp::Label(name)) = &op.kind {
if label_positions.contains_key(name) {
return Err(CrsnError::Asm(AsmError::LabelDuplicate(name.clone()), op.pos.clone()));
}
label_positions.insert(name.clone(), n - label_positions.len());
}
}

@ -4,7 +4,7 @@ use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::instr::Cond;
use crate::builtin::defs::BuiltinOp;
use crate::module::{EvalRes, OpTrait};
use crate::module::{EvalRes, OpTrait, SchedSignal};
use crate::runtime::fault::Fault;
use crate::runtime::run_thread::{info::ThreadInfo, state::RunState};
@ -30,6 +30,7 @@ impl OpTrait for Op {
return Ok(EvalRes {
cycles: 0,
advance: 1,
sched: SchedSignal::Normal
});
}
}
@ -50,15 +51,26 @@ impl OpTrait for Op {
OpKind::Ext(op) => op.to_sexp()
};
// TODO rewrite to be more readable?
if let Some(cond) = self.cond {
if let Sexp::List(items, _) = &mut se {
if let Some(Sexp::Atom(Atom::S(s), _)) = &mut items.get_mut(0) {
s.push('.');
s.push_str(&cond.to_string());
// "true" is used for "else" branches, it has no effect - just omit it
if cond != Cond::True {
if let Sexp::List(items, _) = &mut se {
if let Some(Sexp::Atom(Atom::S(s), _)) = &mut items.get_mut(0) {
s.push('.');
s.push_str(&cond.to_string());
}
}
}
}
se
}
fn is_compile_time_evaluable(&self) -> bool {
match &self.kind {
OpKind::BuiltIn(op) => op.is_compile_time_evaluable(),
OpKind::Ext(op) => op.is_compile_time_evaluable()
}
}
}

@ -3,30 +3,33 @@ use std::sync::Arc;
use sexp::SourcePosition;
use crate::asm::instr::flatten::labels_to_skips;
use crate::asm::instr::flatten::jumps_to_skips;
use crate::asm::parse::{ParserContext, ParserState};
use crate::module::{CrsnExtension, CrsnUniq};
use crate::runtime::program::Program;
use crate::builtin::BuiltinOps;
use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken};
use crate::runtime::frame::REG_COUNT;
use std::sync::atomic::AtomicU32;
use std::path::{Path};
pub mod data;
pub mod error;
pub mod instr;
pub mod parse;
pub mod patches;
mod read_file;
use read_file::read_file;
/// Parse a program from string and assemble a low level instruction sequence from it.
pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, error::CrsnError> {
parsers.insert(0, BuiltinOps::new());
pub(crate) fn read_source_file(path: impl AsRef<Path>) -> Result<String, error::CrsnError> {
trace!("Read source file: {}", path.as_ref().display());
for p in &mut parsers {
p.init(uniq);
}
let source = read_file(path)?;
// remove first line if it looks like a shebang
let source = if source.starts_with("#!") {
let s = if source.starts_with("#!") {
if let Some(nl) = source.find('\n') {
&source[nl + 1..]
(&source[nl + 1..]).to_string()
} else {
source
}
@ -34,18 +37,76 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExt
source
};
Ok(s)
}
/// Parse a program from string and assemble a low level instruction sequence from it.
pub fn assemble(path: impl AsRef<Path>, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, error::CrsnError> {
parsers.insert(0, BuiltinOps::new());
for p in &mut parsers {
p.init(uniq);
}
let path = path.as_ref().canonicalize()?;
let source = read_source_file(&path)?;
let parsers_arc = Arc::new(parsers);
let ti = Arc::new(ThreadInfo {
id: ThreadToken(0),
uniq: Default::default(),
program: Program::new(vec![], parsers_arc.clone(), vec![path.clone()]).unwrap(),
cycle_time: Default::default(),
scheduler_interval: Default::default(),
extensions: parsers_arc.clone(),
});
/* numbered labels start with a weird high number
to avoid conflicts with user-defined numbered labels */
let label_num = Arc::new(AtomicU32::new(0x7890_0000));
let pcx = ParserContext {
parsers: &parsers,
parsers: &parsers_arc,
state: RefCell::new(ParserState {
reg_aliases: Default::default(),
reg_alias_stack: vec![],
global_reg_aliases: Default::default(),
constants: Default::default(),
// This is a fake thread to pass to constant expressions when evaluating them.
// This allows to evaluate nearly all instructions at compile time.
const_eval: RunState {
thread_info: ti.clone(),
cr: Default::default(),
parked: Default::default(),
global_regs: [0; REG_COUNT],
ext_data: Default::default(),
cr_deadline: None,
critical_section: 0,
},
const_eval_ti: ti,
parsing_expr: false,
label_num: label_num.clone(),
files: vec![
path,
],
active_file: 0
}),
};
let ops = parse::parse(source, &SourcePosition::default(), &pcx)?;
let ops = labels_to_skips(ops)?;
let res = do_parse(&source, &pcx, parsers_arc.clone());
if let Err(e) = &res {
if let Some(pos) = e.pos() {
let f = pcx.state.borrow().files[pos.file as usize].clone();
eprintln!("Error in source file: {}", f.display());
}
}
res
}
fn do_parse(source: &str, pcx : &ParserContext, parsers_arc : Arc<Vec<Box<dyn CrsnExtension>>>) -> Result<Arc<Program>, error::CrsnError> {
let ops = parse::parse(source, &SourcePosition::default(), pcx)?;
let ops = jumps_to_skips(ops)?;
Ok(Program::new(ops, Arc::new(parsers))?)
Ok(Program::new(ops, parsers_arc, pcx.state.borrow_mut().files.split_off(0))?)
}

@ -8,9 +8,10 @@ use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::data::literal::Value;
use crate::builtin::defs::BitMask;
use crate::asm::patches::ErrWithPos;
use std::fmt::{Debug, Formatter};
use std::fmt;
/// Utility for argument parsing
#[derive(Debug)]
pub struct TokenParser<'a> {
orig_len: usize,
args: Vec<Sexp>,
@ -18,6 +19,16 @@ pub struct TokenParser<'a> {
pub pcx: &'a ParserContext<'a>,
}
impl<'a> Debug for TokenParser<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TokenParser")
.field("orig_len", &self.orig_len)
.field("args", &self.args)
.field("start_pos", &self.start_pos)
.finish()
}
}
impl<'a> IntoIterator for TokenParser<'a> {
type Item = Sexp;
type IntoIter = std::vec::IntoIter<Sexp>;
@ -42,11 +53,22 @@ impl<'a> TokenParser<'a> {
}
}
/// Prepend a token
pub fn prepend(&mut self, what: Sexp) {
// the list is reversed - actually, append it
self.args.push(what);
self.orig_len += 1;
}
/// Get error if not empty.
/// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string"
pub fn ensure_empty(&self, what_arguments : &str) -> Result<(), CrsnError> {
self.ensure_empty_custom(&format!("Instruction needs {}!", what_arguments))
}
pub fn ensure_empty_custom(&self, msg : &str) -> Result<(), CrsnError> {
if self.have_more() {
Err(CrsnError::Parse(format!("Instruction needs {}!", what_arguments).into(), self.start_pos.clone()))
Err(CrsnError::Parse(msg.to_string().into(), self.start_pos.clone()))
} else {
Ok(())
}
@ -87,7 +109,7 @@ impl<'a> TokenParser<'a> {
}
/// Look at the next entry
pub fn peek(&mut self) -> Option<&Sexp> {
pub fn peek(&self) -> Option<&Sexp> {
self.args.last()
}
@ -269,6 +291,36 @@ impl<'a> TokenParser<'a> {
}
}
/// Parse (RdWr, Rd, Rd) with an optional mask
pub fn parse_masked_rdwr_rd_rd(&mut self, keyword: &str, prefix: &str) -> Result<Option<(RdWr, Rd, Rd, BitMask)>, CrsnError> {
if let Some(s) = keyword.strip_prefix(prefix) {
let width = if s.is_empty() {
(std::mem::size_of::<Value>() as u32) * 8
} else {
s.parse().err_pos(self.start_pos)?
};
if self.len() == 3 {
let (rw, dst_pos) = self.next_rdwr_offset()?;
let (r1, src_pos) = self.next_rd_offset()?;
let (r2, src2_pos) = self.next_rd_offset()?;
let mask = BitMask {
width,
dst_pos,
src_pos,
src2_pos,
};
mask.validate(self.start_pos)?;
Ok(Some((rw, r1, r2, mask)))
} else {
Err(CrsnError::Parse("Instruction needs 3 (RW,Rd,Rd) arguments!".into(), self.start_pos.clone()))
}
} else {
Ok(None)
}
}
/// Parse combining binary instruction operands (i.e. add) without masks
/// Accepts (Wr, Rd, Rd) and (RdWr, Rd)
pub fn parse_wr_rd_rd(&mut self) -> Result<(Wr, Rd, Rd), CrsnError> {

@ -11,6 +11,9 @@ use crate::asm::error::CrsnError;
use crate::asm::instr::Op;
use crate::asm::parse::sexp_expect::expect_list;
use crate::module::CrsnExtension;
use crate::runtime::run_thread::{ThreadInfo, RunState};
use std::sync::Arc;
use std::path::PathBuf;
pub mod parse_cond;
pub mod parse_instr;
@ -28,7 +31,7 @@ pub struct ParserContext<'a> {
pub state: RefCell<ParserState>,
}
#[derive(Default, Debug)]
#[derive(Debug)]
pub struct ParserState {
/// Register aliases valid globally
pub global_reg_aliases: HashMap<RegisterAlias, Register>,
@ -41,14 +44,39 @@ pub struct ParserState {
/// Global constants
pub constants: HashMap<ConstantName, Value>,
/// Dummy run state used for const eval
pub const_eval: RunState,
// ThreadInfo is needed separately from RunState. TODO check if this can be refactored
pub const_eval_ti: Arc<ThreadInfo>,
/// True if we are in an expression parser context
pub parsing_expr : bool,
/// Label numberer
pub label_num : Arc<AtomicU32>,
/// Numbered files
pub files : Vec<PathBuf>,
/// Active file number. Is backed up before descending into a sub-file, and restored afterwards.
pub active_file: usize,
}
impl ParserState {
pub fn reset(&mut self) {
self.global_reg_aliases.clear();
self.reg_aliases.clear();
self.reg_alias_stack.clear();
self.constants.clear();
self.parsing_expr = false;
self.const_eval.reset();
}
}
pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
let (items, _pos) = expect_list(sexp::parse(source)?, true)?;
/* numbered labels start with a weird high number
to avoid conflicts with user-defined numbered labels */
let label_num = AtomicU32::new(0x7890_0000);
parse_instructions(items.into_iter(), pos, parsers)?
.flatten(&label_num)
.flatten(&parsers.state.borrow().label_num)
}

@ -3,12 +3,15 @@ use std::convert::TryFrom;
use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr};
use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr, Register};
use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value};
use crate::asm::error::CrsnError;
use crate::asm::parse::ParserContext;
use crate::asm::parse::{ParserContext, parse_instructions};
use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::patches::ErrWithPos;
use std::sync::atomic::AtomicU32;
use crate::module::OpTrait;
use crate::asm::instr::Cond;
fn is_valid_identifier(name: &str) -> bool {
// ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~"
@ -72,6 +75,7 @@ pub fn parse_label_str(name: &str, pos: &SourcePosition) -> Result<Label, CrsnEr
pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
// trace!("parse data: {:?}", tok);
match tok {
// immediates are okay in const eval - no tests needed
Sexp::Atom(Atom::I(val), _pos) => {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) }))
}
@ -87,7 +91,57 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
Sexp::Atom(Atom::QS(_s), pos) => {
Err(CrsnError::Parse("Quoted string not expected here".into(), pos))
}
Sexp::List(_list, pos) => {
Sexp::List(list, pos) => {
if let Some(Sexp::Atom(Atom::S(s), s_pos)) = list.first() {
// Const eval
let parsing_expr = pcx.state.borrow().parsing_expr;
if !s.starts_with('=') && !parsing_expr {
return Err(CrsnError::Parse(format!("Invalid syntax, did you mean \"={}\" (a compile-time expression)?", s).into(), s_pos.clone()));
}
// start expr parsing mode
let orig_parsing_expr = pcx.state.borrow().parsing_expr;
pcx.state.borrow_mut().parsing_expr = true;
let lmut = AtomicU32::new(0);
let mut expr_ops = parse_instructions(
// TODO avoid this madness
vec![Sexp::List(list, pos.clone())].into_iter(),
&pos, pcx)?
.flatten(&lmut)?;
if expr_ops.len() != 1 {
return Err(CrsnError::Parse(format!("Expected one top level operation in an expression, got: {:?}", expr_ops).into(), pos.clone()));
}
let op = expr_ops.remove(0);
if !op.is_compile_time_evaluable() {
return Err(CrsnError::Parse(format!("Instruction {} cannot be used in an expression!", op.to_sexp()).into(), pos.clone()));
}
let mut state_mut = pcx.state.borrow_mut();
state_mut.const_eval.reset();
let ticl = state_mut.const_eval.thread_info.clone();
op.execute(&ticl, &mut state_mut.const_eval)
.map_err(|f| CrsnError::ParseOther(Box::new(f), pos.clone()))?;
if state_mut.const_eval.test_cond(Cond::Invalid) {
return Err(CrsnError::Parse("Expression evaluation failed - invalid flag set!".into(), pos.clone()));
}
if !orig_parsing_expr {
// exit expr parsing mode
state_mut.parsing_expr = false;
}
return Ok(DataDisp::Immediate(state_mut.const_eval.cr.frame.gen[0]));
}
Err(CrsnError::Parse("List not expected here".into(), pos))
}
Sexp::Atom(Atom::S(s), pos) => {
@ -97,12 +151,16 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
}
/// Parse data disp from a string token (a &str is used so the string can be preprocessed)
pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
if s == "_" {
return Ok(DataDisp::Discard);
}
if let Some(reference) = s.strip_prefix('@') {
if pcx.state.borrow().parsing_expr {
return Err(CrsnError::Parse("Object handles cannot be used in expressions!".into(), pos.clone()));
}
/* extension constants (pre-defined handles) */
for p in pcx.parsers {
if let Some(val) = p.get_constant_value(reference) {
@ -141,7 +199,7 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte
return Ok(DataDisp::Immediate(*val));
}
/* register aliases */
/* register aliases - no need to check for const_eval, the aliases could never be defined */
if let Some(val) = pstate.reg_aliases.get(s) {
return Ok(DataDisp::Register(*val));
} else if let Some(val) = pstate.global_reg_aliases.get(s) {
@ -168,6 +226,14 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte
}
}
if pcx.state.borrow().parsing_expr {
if s == "=result" {
return Ok(DataDisp::Register(Register::Gen(0)));
} else {
return Err(CrsnError::Parse("Registers cannot be used in expressions!".into(), pos.clone()));
}
}
/* register */
let reg = reg::parse_reg(&s, &pos)?;
@ -184,39 +250,13 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte
/// Parse immediate value
pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<(Value, SourcePosition), CrsnError> {
match tok {
Sexp::Atom(Atom::I(val), pos) => {
Ok((unsafe { std::mem::transmute(val) }, pos))
}
Sexp::Atom(Atom::U(val), pos) => {
Ok((val, pos))
}
Sexp::Atom(Atom::C(val), pos) => {
Ok((val as u64, pos))
}
Sexp::Atom(Atom::QS(_), pos) => {
Err(CrsnError::Parse("quoted string not expected here".into(), pos))
}
Sexp::Atom(Atom::F(val), pos) => {
Ok((unsafe { std::mem::transmute(val) }, pos))
}
Sexp::Atom(Atom::S(s), pos) => {
let pstate = pcx.state.borrow();
if let Some(val) = pstate.constants.get(&s) {
return Ok((*val, pos));
}
/* extension constants */
for p in pcx.parsers {
if let Some(val) = p.get_constant_value(&s) {
return Ok((val, pos));
}
}
let pos = tok.pos().clone();
let parsed = parse_rd(tok, pcx)?;
Err(CrsnError::Parse(format!("unknown constant: {}", s).into(), pos))
}
Sexp::List(_, pos) => {
Err(CrsnError::Parse("expected a value".into(), pos))
match parsed {
Rd(RdData::Immediate(value)) => Ok((value, pos)),
_ => {
Err(CrsnError::Parse("expected an immediate value".into(), pos))
}
}
}

@ -1,4 +1,4 @@
use sexp::{Sexp, SourcePosition, Atom};
use sexp::{Sexp, SourcePosition, Atom, atom_s};
use crate::asm::error::CrsnError;
use crate::asm::instr::{Flatten, InstrWithBranches};
@ -6,21 +6,115 @@ use crate::asm::parse::arg_parser::TokenParser;
use crate::asm::parse::parse_cond::parse_cond_branch;
use crate::asm::parse::parse_routine::parse_routine;
use crate::asm::parse::ParserContext;
use crate::asm::parse::sexp_expect::{expect_list, expect_string_atom};
use crate::asm::parse::sexp_expect::{expect_list, expect_string_atom, expect_any_string_atom};
use crate::asm::patches::NextOrErr;
use crate::module::ParseRes;
use crate::asm::patches::ErrSetFile;
use super::parse_op::parse_op;
use std::path::{PathBuf};
use std::convert::TryFrom;
use crate::asm::read_source_file;
pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> {
let mut parsed = vec![];
for expr in items {
let mut parsed: Vec<Box<dyn Flatten>> = vec![];
'exprs: for expr in items {
let (tokens, listpos) = expect_list(expr, false)?;
let mut toki = tokens.into_iter();
let (name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?;
let (mut name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?;
let parsing_expr = pcx.state.borrow().parsing_expr;
if parsing_expr {
if let Some(n) = name.strip_prefix('=') {
name = n.to_string();
}
}
if name == "include" {
// TODO move this to a separate function and get rid of err_file()
if parsing_expr {
return Err(CrsnError::Parse("Illegal syntax in const expression".into(), pos.clone()));
}
let (mut path, _namepos) = expect_any_string_atom(toki.next_or_err(listpos.clone(), "Expected file path or name to include")?)?;
// add extension if missing
let last_piece = path.split('/').rev().next().unwrap();
if !last_piece.contains('.') {
path.push_str(".csn");
}
trace!("*** include, raw path: {}", path);
let state = pcx.state.borrow();
let this_file = &state.files[state.active_file];
let new_file = PathBuf::try_from(path).unwrap();
let new_pb = if !new_file.is_absolute() {
let parent = this_file.parent().expect("file has parent");
let fixed = parent.join(&new_file);
trace!("Try to resolve: {}", fixed.display());
fixed.canonicalize()?
} else {
new_file.to_owned()
};
drop(state);
drop(new_file);
let (old_af, af) = {
let mut state = pcx.state.borrow_mut();
let old_af = state.active_file;
let af = state.files.len();
state.active_file = af;
state.files.push(new_pb.clone());
(old_af, af as u32)
};
let loaded = read_source_file(&new_pb)
.err_file(af)?;
let (items, _pos) =
expect_list(
sexp::parse(&loaded)
.map_err(|mut e| {
e.pos.file = af;
e
})?,
true)
.err_file(af)?;
let sub_parsed = parse_instructions(items.into_iter(), pos, pcx)
.err_file(af)?;
let mut flat = sub_parsed.flatten(&pcx.state.borrow().label_num)
.err_file(af)?;
flat.iter_mut().for_each(|op| {
op.pos.file = af as u32;
});
trace!("inner falt {:#?}", flat);
parsed.push(Box::new(flat));
{
let mut state = pcx.state.borrow_mut();
state.active_file = old_af;
}
continue;
}
if name == "proc" {
if parsing_expr {
return Err(CrsnError::Parse("Illegal syntax in const expression".into(), pos.clone()));
}
parsed.push(parse_routine(toki, pos, pcx)?);
continue;
}
@ -29,8 +123,13 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
let mut token_parser = TokenParser::new(toki.collect(), &listpos, pcx);
for p in pcx.parsers {
token_parser = match p.parse_syntax(pos, &name, token_parser) {
Ok(ParseRes::Parsed(op)) => return Ok(op),
Ok(ParseRes::ParsedNone) => return Ok(Box::new(())),
Ok(ParseRes::Parsed(op)) => {
parsed.push(op);
continue 'exprs;
},
Ok(ParseRes::ParsedNone) => {
continue 'exprs;
},
Ok(ParseRes::Unknown(to_reuse)) => {
if to_reuse.parsing_started() {
panic!("Module \"{}\" started parsing syntax, but returned Unknown!", p.name());
@ -62,8 +161,13 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
})
.collect::<Vec<_>>();
let arg_tokens = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx);
// debug!("branch_tokens: {:#?}", branch_tokens);
if parsing_expr && !branch_tokens.is_empty() {
return Err(CrsnError::Parse(format!("Conditional branches are not allowed in const expression: {:?}", branch_tokens).into(), pos.clone()));
}
let mut arg_parser = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx);
trace!("Parse op: {}\nargs {:?}\nbranches {:?}", name, arg_parser, branch_tokens);
let branches = {
let mut branches = vec![];
@ -77,7 +181,12 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
}
};
if let Some(op) = parse_op(name.as_str(), arg_tokens, &namepos)? {
if parsing_expr {
// hackity hack for const expr eval
arg_parser.prepend(atom_s("=result"));
}
if let Some(op) = parse_op(name.as_str(), arg_parser, &namepos)? {
parsed.push(Box::new(InstrWithBranches {
op,
pos: namepos,

@ -16,6 +16,10 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &S
keyword = &keyword[..pos];
}
if cond.is_some() && arg_tokens.pcx.state.borrow().parsing_expr {
return Err(CrsnError::Parse("Conditional suffixes are not allowed in const expression".into(), spos.clone()));
}
for p in arg_tokens.pcx.parsers {
arg_tokens = match p.parse_op(spos, keyword, arg_tokens) {
Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op {

@ -57,3 +57,24 @@ impl<T, E: std::error::Error + Send + Sync + 'static> ErrWithPos<T> for Result<T
}
}
}
pub trait ErrSetFile<T> {
fn err_file(self, file: u32) -> Result<T, CrsnError>;
}
impl<T> ErrSetFile<T> for Result<T, CrsnError> {
/// Set file context in the error
fn err_file(self, file: u32) -> Result<T, CrsnError> {
match self {
Ok(v) => Ok(v),
Err(CrsnError::Parse(a, p)) => Err(CrsnError::Parse(a, p.with_file(file))),
Err(CrsnError::ParseOther(a, p)) => Err(CrsnError::ParseOther(a, p.with_file(file))),
Err(CrsnError::Asm(a, p)) => Err(CrsnError::Asm(a, p.with_file(file))),
Err(CrsnError::Sexp(mut se)) => {
se.pos.file = file;
Err(CrsnError::Sexp(se))
},
Err(other @ CrsnError::IOError(_)) => Err(other),
}
}
}

@ -4,7 +4,7 @@ use std::io::Read;
use std::path::Path;
/// Read a file to string
pub fn read_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
pub fn read_file(path: impl AsRef<Path>) -> io::Result<String> {
let path = path.as_ref();
let mut file = File::open(path)?;

@ -136,7 +136,9 @@ pub enum BuiltinOp {
FarJump(Label),
/// Call a routine with arguments.
/// The arguments are passed as argX. Return values are stored in resX registers.
Call(RoutineName, Vec<Rd>),
Call { proc: RoutineName, args: Vec<Rd> },
/// Spawn a coroutine. The invocation is similar to (call).
Spawn { handle: Wr, proc: RoutineName, args: Vec<Rd> },
/// Exit the current routine with return values
Ret(Vec<Rd>),
/// Mark a routine entry point (call target).
@ -144,6 +146,17 @@ pub enum BuiltinOp {
Routine(RoutineName),
/// Skip backward or forward. The skip count can be defined by an argument.
Skip(Rd),
/// Join a coroutine
Join(RdObj),
/// Yield control, optionally yielding a value that must be consumed (by reading the task handle)
/// before execution can resume
Yield { value: Option<Rd> },
/// Set runtime option
RuntimeOpt { opt: Rd, value: Rd },
/// Begin critical section
CriticalBegin,
/// End critical section
CriticalEnd,
/// Deny jumps, skips and run across this address, producing a run-time fault.
Barrier {
kind: Barrier,
@ -164,6 +177,8 @@ pub enum BuiltinOp {
LoadSequence { dst: Wr, value: LdsValue },
/// Swap two registers
Exchange { a: RdWr, b: RdWr, mask: BitMask },
/// Swap if equal to a pattern
CompareSwap { dst: RdWr, expected: Rd, src: Rd, mask: BitMask },
/// Store runtime status to a register
StoreFlags { dst: Wr },
/// Load runtime status from a register

@ -4,12 +4,14 @@ use sexp::Sexp;
use crate::asm::data::literal::{Addr, Value};
use crate::builtin::defs::{Barrier, BuiltinOp, LdsValue};
use crate::module::{EvalRes, OpTrait};
use crate::module::{EvalRes, OpTrait, SchedSignal};
use crate::runtime::fault::Fault;
use crate::runtime::frame::StackFrame;
use crate::runtime::run_thread::{state::RunState, ThreadInfo};
use crate::asm::instr::cond::Flag;
use super::RT_OPT_TIMESLICE;
impl OpTrait for BuiltinOp {
fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
let program = &info.program;
@ -63,7 +65,7 @@ impl OpTrait for BuiltinOp {
BuiltinOp::Jump(_name) => {
panic!("jump not translated to skip by assembler!");
}
BuiltinOp::Call(name, args) => {
BuiltinOp::Call { proc: name, args } => {
match program.find_routine(&name) {
Ok(pos) => {
let mut values = Vec::with_capacity(args.len());
@ -72,8 +74,8 @@ impl OpTrait for BuiltinOp {
}
let mut frame2 = StackFrame::new(pos, &values);
std::mem::swap(&mut state.frame, &mut frame2);
state.call_stack.push(frame2);
std::mem::swap(&mut state.cr.frame, &mut frame2);
state.cr.call_stack.push(frame2);
res.advance = 0;
}
Err(e) => {
@ -81,18 +83,40 @@ impl OpTrait for BuiltinOp {
}
}
}
BuiltinOp::Ret(retvals) => {
match state.call_stack.pop() {
Some(previous) => {
let mut values = Vec::with_capacity(retvals.len());
for arg in retvals {
BuiltinOp::Spawn { handle, proc, args } => {
match program.find_routine(&proc) {
Ok(pos) => {
let mut values = Vec::with_capacity(args.len());
for arg in args {
values.push(state.read(arg)?);
}
state.frame = previous;
state.frame.set_retvals(&values);
let frame2 = StackFrame::new(pos, &values);
let handle_val = state.add_coroutine(frame2);
state.write(handle, handle_val)?;
// Yield execution so the new thread can start running
res.sched = SchedSignal::Yield(None);
}
Err(e) => {
return Err(e);
}
}
}
BuiltinOp::Ret(retvals) => {
let mut values = Vec::with_capacity(retvals.len());
for arg in retvals {
values.push(state.read(arg)?);
}
match state.cr.call_stack.pop() {
Some(previous) => {
state.cr.frame = previous;
state.cr.frame.set_retvals(&values);
}
None => {
return Err(Fault::CallStackUnderflow);
// Stack underflow - if this is a coroutine, it's finished
res.sched = SchedSignal::Ret(values);
}
}
}
@ -173,16 +197,38 @@ impl OpTrait for BuiltinOp {
state.write(b, bb2)?;
}
}
BuiltinOp::CompareSwap { dst, expected, src, mask: slice } => {
state.clear_status();
let old = state.read(dst)?;
let exp = state.read(expected)?;
if slice.is_full() {
if old == exp {
let new = state.read(src)?;
state.write(dst, new)?;
state.set_flag(Flag::Equal, true);
}
} else {
let ones: u64 = (1 << slice.width) - 1;
if (old & (ones << slice.dst_pos)) == (exp & (ones << slice.src_pos)) {
let new = state.read(src)?;
let replacement = (old & !(ones << slice.dst_pos)) | (((new & (ones << slice.src2_pos)) >> slice.src2_pos) << slice.dst_pos);
state.write(dst, replacement)?;
state.set_flag(Flag::Equal, true);
}
}
}
BuiltinOp::StoreFlags { dst } => {
let packed = state.frame.status.store();
let packed = state.cr.frame.status.store();
state.write(dst, packed)?;
}
BuiltinOp::LoadFlags { src } => {
let x = state.read(src)?;
state.frame.status.load(x);
state.cr.frame.status.load(x);
}
BuiltinOp::Sleep { count: micros, unit_us } => {
std::thread::sleep(Duration::from_micros(state.read(micros)? * unit_us.micros()))
res.sched = SchedSignal::Sleep(Duration::from_micros(state.read(micros)? * unit_us.micros()));
}
BuiltinOp::Delete(obj) => {
let hv = obj.read(state)?;
@ -199,11 +245,56 @@ impl OpTrait for BuiltinOp {
state.set_flag(Flag::Invalid, true);
}
}
BuiltinOp::Yield { value } => {
res.sched = SchedSignal::Yield(match value {
None => None,
Some(rd) => Some(state.read(rd)?)
});
}
BuiltinOp::Join(obj) => {
res.sched = SchedSignal::Join(obj.read(state)?);
}
BuiltinOp::RuntimeOpt { opt, value } => {
let opt = state.read(opt)?;
let val = state.read(value)?;
match opt {
RT_OPT_TIMESLICE => {
*state.thread_info.scheduler_interval.write() = Duration::from_micros(val);
}
_ => {
warn!("Invalid rt-opt {}", opt);
state.set_flag(Flag::Invalid, true);
}
}
}
BuiltinOp::CriticalBegin => {
if state.critical_section == Value::MAX {
return Err(Fault::UnbalancedCriticalSection);
}
state.critical_section += 1;
res.cycles = 0;
}
BuiltinOp::CriticalEnd => {
if state.critical_section == 0 {
return Err(Fault::UnbalancedCriticalSection);
}
state.critical_section -= 1;
res.cycles = 0;
}
}
Ok(res)
}
fn is_compile_time_evaluable(&self) -> bool {
match self {
BuiltinOp::Load { .. } | BuiltinOp::LoadBits { .. } => true,
_ => false
}
}
fn to_sexp(&self) -> Sexp {
super::parse::to_sexp(self)
}

@ -1,14 +1,23 @@
use sexp::SourcePosition;
use sexp::{SourcePosition, Sexp, Atom};
use crate::asm::error::CrsnError;
use crate::asm::instr::op::OpKind;
use crate::asm::parse::arg_parser::TokenParser;
use crate::module::{CrsnExtension, ParseRes};
use crate::asm::data::literal::{Value, Label};
use crate::asm::instr::{Flatten, Op};
use crate::asm::parse::parse_instructions;
use crate::builtin::defs::BuiltinOp;
use crate::asm::instr::flatten::jumps_to_skips;
use crate::asm::data::{Rd, RdData};
use crate::asm::parse::parse_data::parse_label;
pub mod defs;
pub mod exec;
pub mod parse;
pub(crate) const RT_OPT_TIMESLICE : u64 = 1;
#[derive(Debug, Clone)]
pub struct BuiltinOps;
@ -26,4 +35,116 @@ impl CrsnExtension for BuiltinOps {
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse_op(pos, keyword, args)
}
/// Get value of an extension-provided constant.
/// This constant may be an object handle, or a constant value used as argument in some other instruction.
fn get_constant_value<'a>(&self, name: &str) -> Option<Value> {
match name {
"RT_TIMESLICE" => Some(RT_OPT_TIMESLICE),
_ => None,
}
}
/// Parse a generic S-expression (non-op) that started with the given keyword
///
/// pcx is available on the arg_tokens parser
fn parse_syntax<'a>(&self, pos: &SourcePosition, keyword: &str, mut tokens: TokenParser<'a>)
-> Result<ParseRes<'a, Box<dyn Flatten>>, CrsnError>
{
if keyword == "crit" || keyword == "critical" {
let pcx = tokens.pcx;
let opts = parse_instructions(tokens.into_iter(), pos, pcx)?;
let flattened = jumps_to_skips(opts.flatten(&pcx.state.borrow_mut().label_num)?)?;
let len = flattened.len();
for (n, op) in flattened.iter().enumerate() {
match op.kind {
OpKind::BuiltIn(BuiltinOp::Skip(Rd(RdData::Immediate(skip)))) => {
let signed = i64::from_ne_bytes(skip.to_ne_bytes());
let target = n as i64 + signed;
if target < 0 || target > len as i64 {
return Err(CrsnError::Parse("Cannot jump out of a critical section!".into(), op.pos()));
}
}
/* Non-constant skip cannot be validated */
OpKind::BuiltIn(BuiltinOp::Skip(_)) => {
return Err(CrsnError::Parse("Variable skips are not allowed in a critical section".into(), op.pos()));
}
/* Yield in critical makes zero sense */
OpKind::BuiltIn(BuiltinOp::Yield { .. }) => {
return Err(CrsnError::Parse("Yield in a critical section!".into(), op.pos()));
}
/* This is likely a bug */
OpKind::BuiltIn(BuiltinOp::Ret(_)) => {
return Err(CrsnError::Parse("Ret in a critical section!".into(), op.pos()));
}
/* Probably also a bug. If someone really wants this, they can start and end the critical section manually. */
OpKind::BuiltIn(BuiltinOp::FarJump(_)) => {
return Err(CrsnError::Parse("Far jump a critical section!".into(), op.pos()));
}
_ => {}
}
}
let vec : Vec<Box<dyn Flatten>> = vec![
Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::CriticalBegin),
cond: None,
pos: pos.clone(),
}),
Box::new(flattened),
Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::CriticalEnd),
cond: None,
pos: pos.clone(),
}),
];
return Ok(ParseRes::Parsed(Box::new(vec)));
}
if keyword == "loop" {
let mut end_label = None;
let label = if let Some(Sexp::Atom(Atom::S(_), _)) = tokens.peek() {
let label = parse_label(tokens.next().unwrap())?;
if let Label::Named(n) = &label {
end_label = Some(Label::Named(format!("{}-end", n)));
}
label
} else {
Label::unique(&tokens.pcx.state.borrow().label_num)
};
let pcx = tokens.pcx;
let inner = parse_instructions(tokens.into_iter(), pos, pcx)?;
let mut vec : Vec<Box<dyn Flatten>> = vec![
Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::Label(label.clone())),
cond: None,
pos: pos.clone(),
}),
inner,
Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::Jump(label)),
cond: None,
pos: pos.clone(),
}),
];
if let Some(el) = end_label {
vec.push(Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::Label(el)),
cond: None,
pos: pos.clone(),
}));
}
return Ok(ParseRes::Parsed(Box::new(vec)))
}
Ok(ParseRes::Unknown(tokens))
}
}

@ -25,21 +25,21 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
BuiltinOp::Halt
}
"uslp" => {
"uslp" | "usleep" => {
BuiltinOp::Sleep {
count: args.next_rd()?,
unit_us: SleepUnit::Usec,
}
}
"mslp" => {
"mslp" | "msleep" => {
BuiltinOp::Sleep {
count: args.next_rd()?,
unit_us: SleepUnit::Msec,
}
}
"sslp" => {
"sslp" | "ssleep" => {
BuiltinOp::Sleep {
count: args.next_rd()?,
unit_us: SleepUnit::Sec,
@ -132,7 +132,7 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
for t in args {
call_args.push(parse_rd(t, pcx)?);
}
BuiltinOp::Call(dest, call_args)
BuiltinOp::Call { proc: dest, args: call_args }
}
"ret" => {
@ -183,6 +183,21 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
})
}
"crit-begin" => {
BuiltinOp::CriticalBegin
}
"crit-end" => {
BuiltinOp::CriticalEnd
}
"rt-opt" => {
BuiltinOp::RuntimeOpt {
opt: args.next_rd()?,
value: args.next_rd()?,
}
}
"ld" => {
BuiltinOp::Load {
dst: args.next_wr()?,
@ -258,6 +273,38 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
}
}
"spawn" => {
let handle = args.next_wr()?;
let dest = RoutineName { name: args.next_string()?.0, arity: args.len() as u8 };
let mut call_args = vec![];
for t in args {
call_args.push(parse_rd(t, pcx)?);
}
BuiltinOp::Spawn {
handle,
proc: dest,
args: call_args
}
}
"join" => {
BuiltinOp::Join(args.next_rdobj()?)
}
"yield" => {
if args.have_more() {
BuiltinOp::Yield {
value: Some(args.next_rd()?),
}
} else {
BuiltinOp::Yield {
value: None,
}
}
}
"del" => {
BuiltinOp::Delete(args.next_rdobj()?)
}
@ -289,6 +336,10 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
return Ok(ParseRes::builtin(BuiltinOp::Exchange { a, b, mask }));
}
if let Some((dst, expected, src, mask)) = args.parse_masked_rdwr_rd_rd(other, "cas")? {
return Ok(ParseRes::builtin(BuiltinOp::CompareSwap { dst, expected, src, mask }));
}
if other.starts_with(':') {
BuiltinOp::Label(parse_label_str(other, &op_pos)?)
} else {
@ -313,6 +364,9 @@ pub(crate) fn parse_routine_name(name: String, pos: &SourcePosition) -> Result<R
pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
match op {
BuiltinOp::CriticalBegin => sexp::list(&[A("crit-begin")]),
BuiltinOp::CriticalEnd => sexp::list(&[A("crit-end")]),
BuiltinOp::RuntimeOpt { opt, value } => sexp::list(&[A("rt-opt"), A(opt), A(value)]),
BuiltinOp::Nop => sexp::list(&[A("nop")]),
BuiltinOp::Halt => sexp::list(&[A("halt")]),
BuiltinOp::Sleep { count: micros, unit_us: SleepUnit::Sec } => sexp::list(&[A("sslp"), A(micros)]),
@ -322,7 +376,7 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
BuiltinOp::Jump(label) => sexp::list(&[A("j"), A(label)]),
BuiltinOp::FarLabel(label) => sexp::list(&[A("far"), A(label)]),
BuiltinOp::FarJump(label) => sexp::list(&[A("fj"), A(label)]),
BuiltinOp::Call(name, args) => {
BuiltinOp::Call { proc: name, args } => {
if args.is_empty() {
sexp::list(&[A("call"), A(&name.name)])
} else {
@ -389,6 +443,13 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
sexp::list(&[A(format!("xch{}", mask.width)), AM(a, mask.dst_pos), AM(b, mask.src_pos)])
}
},
BuiltinOp::CompareSwap { dst, expected, src, mask } => {
if mask.is_full() {
sexp::list(&[A("cas"), A(dst), A(expected), A(src)])
} else {
sexp::list(&[A(format!("cas{}", mask.width)), AM(dst, mask.dst_pos), AM(expected, mask.src_pos), AM(src, mask.src2_pos)])
}
},
BuiltinOp::StoreFlags { dst } => sexp::list(&[A("stf"), A(dst)]),
BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)]),
BuiltinOp::LoadSequence { dst, value } => {
@ -405,6 +466,22 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
}
}
}
BuiltinOp::Spawn { handle, proc, args } => {
if args.is_empty() {
sexp::list(&[A("spawn"), A(handle), A(proc)])
} else {
let mut v = vec![A("spawn"), A(handle), A(&proc.name)];
v.extend(args.iter().map(|r| A(r)));
sexp::list(&v)
}
}
BuiltinOp::Join(handle) => sexp::list(&[A("join"), A(handle)]),
BuiltinOp::Yield { value } => {
match value {
None => sexp::list(&[A("yield")]),
Some(rd) => sexp::list(&[A("yield"), A(rd)]),
}
}
}
}
@ -414,10 +491,15 @@ mod test {
use sexp::SourcePosition;
use crate::asm::parse::{parse_instructions, ParserContext};
use crate::asm::parse::{parse_instructions, ParserContext, ParserState};
use crate::asm::parse::sexp_expect::expect_list;
use crate::builtin::BuiltinOps;
use crate::module::OpTrait;
use std::cell::RefCell;
use std::sync::Arc;
use crate::runtime::run_thread::{ThreadInfo, ThreadToken, RunState};
use crate::runtime::program::Program;
use crate::runtime::frame::REG_COUNT;
#[test]
fn roundtrip() {
@ -474,6 +556,8 @@ mod test {
("(xch r0 r1)", "(xch r0 r1)"),
("(xch32 r0 r1)", "(xch32 r0 r1)"),
("(xch32 r0:8 r1:16)", "(xch32 r0:8 r1:16)"),
("(cas r0 r1 r2)", "(cas r0 r1 r2)"),
("(cas8 r0:8 r1:16 r2:24)", "(cas8 r0:8 r1:16 r2:24)"),
("(ld r0 r0)", "(ld r0 r0)"),
("(ld8 r0 r1)", "(ld8 r0 r1)"),
("(ld16 r0 r1)", "(ld16 r0 r1)"),
@ -492,17 +576,55 @@ mod test {
("(far :label)", "(far :label)"),
("(del @r5)", "(del @r5)"),
("(sym cat r0)(del @cat)", "(del @r0)"),
("(spawn r0 foo 1 2 3)", "(spawn r0 foo 1 2 3)"),
("(yield)", "(yield)"),
("(yield -1)", "(yield -1)"),
("(yield r5)", "(yield r5)"),
("(join @r5)", "(join @r5)"),
];
let parser = BuiltinOps::new();
let parsers = &[parser];
let parsers = Arc::new(vec![parser]);
let ti = Arc::new(ThreadInfo {
id: ThreadToken(0),
uniq: Default::default(),
program: Program::new(vec![], parsers.clone(), vec![]).unwrap(),
cycle_time: Default::default(),
scheduler_interval: Default::default(),
extensions: parsers.clone(),
});
let pcx = ParserContext {
parsers: &parsers,
state: RefCell::new(ParserState {
reg_aliases: Default::default(),
reg_alias_stack: vec![],
global_reg_aliases: Default::default(),
constants: Default::default(),
// This is a fake thread to pass to constant expressions when evaluating them.
// This allows to evaluate nearly all instructions at compile time.
const_eval: RunState {
thread_info: ti.clone(),
cr: Default::default(),
parked: Default::default(),
global_regs: [0; REG_COUNT],
ext_data: Default::default(),
cr_deadline: None,
critical_section: 0,
},
const_eval_ti: ti.clone(),
parsing_expr: false,
label_num: Default::default(),
files: vec![],
active_file: 0
}),
};
for (sample, expected) in samples {
let pcx = ParserContext {
parsers,
state: Default::default(),
};
pcx.state.borrow_mut().reset();
println!("Parse: {}", sample);
@ -521,10 +643,7 @@ mod test {
assert_eq!(expected, exported);
println!(" - 2nd cycle");
let pcx = ParserContext {
parsers,
state: Default::default(),
};
pcx.state.borrow_mut().reset();
/* second cycle, nothing should change */
let s = sexp::parse(&format!("({})", exported))

@ -1,3 +1,6 @@
use crate::asm::data::literal::Value;
use std::time::Duration;
/// Cycles spent executing an instruction
pub type CyclesSpent = usize;
@ -6,6 +9,7 @@ pub type CyclesSpent = usize;
pub struct EvalRes {
pub cycles: CyclesSpent,
pub advance: i64,
pub sched: SchedSignal,
}
impl Default for EvalRes {
@ -13,6 +17,24 @@ impl Default for EvalRes {
Self {
cycles: 1,
advance: 1,
sched: SchedSignal::Normal,
}
}
}
/// Signal to the scheduler
#[derive(Debug)]
pub enum SchedSignal {
/// No signal, execution went normally.
Normal,
/// Yield control, optionally with a value.
/// If a value is yielded, it must be consumed through the handle before execution can resume.
Yield(Option<Value>),
/// The routine requests a delay in execution. The actual sleep time can be longer due to task
/// switching overhead.
Sleep(Duration),
/// Return from a coroutine - the thread ends and should be joined.
Ret(Vec<Value>),
/// Request to join a coroutine/thread
Join(Value),
}

@ -2,7 +2,7 @@
use std::fmt::Debug;
pub use eval_res::EvalRes;
pub use eval_res::*;
use sexp::{Sexp, SourcePosition};
use crate::asm::data::literal::Value;
@ -48,11 +48,18 @@ pub trait OpTrait: Debug + Send + Sync + 'static {
/// Turn into an S-expression that produces this instruction when parsed
fn to_sexp(&self) -> Sexp;
/// Check if an operation is safe to use in compile-time arithmetics - has no side effects
/// outside the input and output registers (and flags), has the form (op Wr ...), and does not
/// use object handles or extension data.
fn is_compile_time_evaluable(&self) -> bool {
false
}
}
/// CRSN initializer object.
/// Only one should be created for the lifespan of the parser and runtime.
#[derive(Default)]
#[derive(Default,Debug)]
pub struct CrsnUniq {
object_handle_counter : AtomicU64
}

@ -8,9 +8,9 @@ impl RunThread {
let state = &mut self.state;
let info = &self.info;
let op = info.program.fetch_instr(state.frame.pc);
let op = info.program.fetch_instr(state.cr.frame.pc);
trace!("### {:04} : {:?}", state.frame.pc.0, op);
trace!("### {:04} : {:?}", state.cr.frame.pc.0, op);
op.execute(info, state)
}

@ -26,6 +26,9 @@ pub enum Fault {
#[error("Program ended.")]
Halt,
#[error("Instruction did not finish in time for context switch, retry later")]
Blocked,
#[error("Operation not allowed: {0}")]
NotAllowed(Cow<'static, str>),
@ -74,6 +77,18 @@ pub enum Fault {
#[error("Attempt to read undefined extension data store")]
ExtDataNotDefined,
#[error("Unbalanced critical sections")]
UnbalancedCriticalSection,
#[error("Deadlock detected")]
Deadlock,
#[error("Root routine returned or yielded a value")]
RootReturned,
#[error("System IO error: {0}")]
IOError(#[from] std::io::Error),
}
#[derive(Error, Debug)]

@ -33,10 +33,14 @@ impl StackFrame {
sf
}
#[inline(always)]
pub fn set_retvals(&mut self, vals: &[Value]) {
for n in 0..(vals.len().min(REG_COUNT)) {
self.res[n] = vals[n];
}
// Zero the rest
for n in vals.len()..REG_COUNT {
self.res[n] = 0;
}
}
}

@ -115,6 +115,8 @@ impl StatusFlags {
Cond::NotEmpty => !self.empty,
Cond::Eof => self.eof,
Cond::NotEof => !self.eof,
Cond::True => true,
Cond::False => false,
}
}

@ -10,6 +10,7 @@ use crate::asm::instr::op::OpKind;
use crate::builtin::defs::{Barrier, BuiltinOp};
use crate::module::CrsnExtension;
use crate::runtime::fault::Fault;
use std::path::PathBuf;
#[derive(Debug)]
pub struct Program {
@ -20,16 +21,23 @@ pub struct Program {
/// Barriers from-to (inclusive).
/// Standalone barriers have both addresses the same.
barriers: Vec<(Addr, Addr)>,
/// This is used at runtime to better report error locations when included files are used
pub(crate) file_names: Vec<PathBuf>,
}
impl Program {
pub fn new(ops: Vec<Op>, extensions: Arc<Vec<Box<dyn CrsnExtension>>>) -> Result<Arc<Self>, CrsnError> {
pub fn new(
ops: Vec<Op>,
extensions: Arc<Vec<Box<dyn CrsnExtension>>>,
file_names: Vec<PathBuf>,
) -> Result<Arc<Self>, CrsnError> {
let mut p = Self {
ops,
extensions,
routines: Default::default(),
far_labels: Default::default(),
barriers: Default::default(),
file_names,
};
p.scan()?;
Ok(Arc::new(p))
@ -100,6 +108,7 @@ impl Program {
line: 0,
column: 0,
index: 0,
file: 0,
},
cond: None,
}

@ -1,16 +1,20 @@
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::{Duration};
use std::time::{Duration, Instant};
pub use info::ThreadInfo;
pub use state::RunState;
use crate::asm::data::literal::Addr;
use crate::module::{EvalRes, CrsnUniq};
use crate::module::{EvalRes, CrsnUniq, SchedSignal};
use crate::runtime::fault::Fault;
use crate::runtime::frame::{StackFrame, REG_COUNT};
use crate::runtime::program::Program;
use crate::runtime::run_thread::state::{CoroutineContext, CoroutineState};
use std::{mem, thread};
use crate::asm::instr::cond::Flag;
use parking_lot::RwLock;
use crate::asm::instr::Flatten;
#[derive(Clone, Copy, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub struct ThreadToken(pub u32);
@ -29,6 +33,7 @@ pub struct ThreadParams<'a> {
pub program: Arc<Program>,
pub pc: Addr,
pub cycle_time: Duration,
pub scheduler_interval: Duration,
pub args: &'a [u64],
}
@ -36,20 +41,30 @@ impl RunThread {
pub fn new(params: ThreadParams) -> Self {
let extensions = params.program.extensions.clone();
// TODO investigate if this division to 2 structs is still needed
let ti = Arc::new(ThreadInfo {
id: params.id,
uniq: params.uniq,
program: params.program,
cycle_time: params.cycle_time,
scheduler_interval: RwLock::new(params.scheduler_interval),
extensions,
});
let rs = RunState {
thread_info: ti.clone(),
frame: StackFrame::new(params.pc, params.args),
call_stack: vec![],
cr: Box::new(CoroutineContext {
handle: 0, // this is the root
frame: StackFrame::new(params.pc, params.args),
call_stack: vec![],
cr_state: Default::default(),
}),
parked: Default::default(),
global_regs: [0; REG_COUNT],
ext_data: Default::default(),
cr_deadline: None,
critical_section: 0,
};
Self {
@ -58,41 +73,221 @@ impl RunThread {
}
}
/// Spawn as a thread
pub fn spawn(self) -> JoinHandle<()> {
std::thread::spawn(move || {
self.run();
})
}
/// Start synchronously
pub fn run(mut self) {
let mut loop_helper = spin_sleep::LoopHelper::builder()
.build_with_target_rate(1.0/self.info.cycle_time.as_secs_f64());
loop_helper.loop_start();
'run: loop {
let mut orig_pc;
let fault = 'run: loop {
let mut want_switch = false;
orig_pc = self.state.cr.frame.pc;
match self.eval_op() {
Ok(EvalRes { cycles, advance }) => {
Ok(EvalRes { cycles, advance, sched }) => {
for _ in 0..cycles {
loop_helper.loop_sleep();
loop_helper.loop_start();
}
trace!("Step {}; Status = {}", advance, self.state.frame.status);
self.state.frame.pc.advance(advance);
trace!("Step {}; Status = {}", advance, self.state.cr.frame.status);
self.state.cr.frame.pc.advance(advance);
if let Some(dl) = self.state.cr_deadline {
want_switch = dl <= Instant::now();
}
match sched {
SchedSignal::Normal => {}
SchedSignal::Yield(None) => {
trace!("yield");
self.state.cr.cr_state = CoroutineState::Ready;
want_switch = true;
}
SchedSignal::Yield(Some(value)) => {
trace!("yield {}", value);
self.state.cr.cr_state = CoroutineState::YieldedValue(value);
want_switch = true;
}
SchedSignal::Sleep(time) => {
trace!("sleep {:?}", time);
self.state.cr.cr_state = CoroutineState::Sleep { due: Instant::now() + time };
want_switch = true;
}
SchedSignal::Ret(results) => {
self.state.cr.cr_state = CoroutineState::Finished(results);
want_switch = true;
// TODO prioritize a thread that is waiting for this return value
}
SchedSignal::Join(handle) => {
trace!("Join cr {:#}", handle);
let mut found = false;
'find: for (n, th) in self.state.parked.iter_mut().enumerate() {
if th.handle == handle {
if let CoroutineState::Finished(_) = &th.cr_state {
let crs = mem::replace(&mut th.cr_state, CoroutineState::Ready);
self.state.parked.remove(n); // delete it
found = true;
if let CoroutineState::Finished(vals) = crs {
self.state.cr.frame.set_retvals(&vals);
break 'find;
} else {
unreachable!();
}
} else {
self.state.cr.frame.pc = orig_pc; // Retry
self.state.cr.cr_state = CoroutineState::Ready;
want_switch = true;
found = true;
}
}
}
if !found {
self.state.set_flag(Flag::Invalid, true);
warn!("Join with invalid thread handle {:#}!", handle);
self.state.cr.frame.set_retvals(&[]);
}
}
}
}
Err(Fault::Halt) => {
// TODO implement coordinated shutdown when more threads are running!
break 'run;
Err(Fault::Blocked) => {
trace!("Thread reports being blocked!");
self.state.cr.frame.pc = orig_pc; // Retry
self.state.cr.cr_state = CoroutineState::Ready;
want_switch = true;
}
Err(e) => {
error!("Fault: {:?}", e);
error!("Core dump: {:?}", self.state);
break 'run;
break 'run e;
}
}
}
debug!("Thread ended.");
// Resolve the next coroutine to run, or wait a bit...
'next: loop {
if want_switch {
let now = Instant::now();
if self.state.critical_section > 0 {
match self.state.cr.cr_state {
CoroutineState::Ready => {}
CoroutineState::Sleep { due } => {
let time = due.saturating_duration_since(now);
trace!("Sleep in critical: {:?}", time);
thread::sleep(time);
continue 'run;
}
CoroutineState::Finished(_) | CoroutineState::YieldedValue(_) => {
// This is not good
break 'run Fault::Deadlock;
}
}
// This is either a bug, or waiting for IO.
// If it is the latter, try to give the OS a bit of breathing room..
std::thread::yield_now();
continue 'run;
}
trace!("Switch requested");
let mut candidate = None;
let mut closest_due = None;
'findbest: for _ in 0..self.state.parked.len() {
if let Some(mut rt) = self.state.parked.pop_front() {
match rt.cr_state {
CoroutineState::Ready => {
trace!("Found READY thread to run next");
candidate = Some(rt);
break 'findbest;
}
CoroutineState::Sleep { due } => {
if due <= now {
trace!("Found DUE sleeping thread to run next");
rt.cr_state = CoroutineState::Ready;
candidate = Some(rt);
break 'findbest;
} else {
match closest_due {
Some(d) => {
if d > due {
closest_due = Some(due);
}
},
None => {
closest_due = Some(due);
}
}
self.state.parked.push_back(rt);
}
}
_ => {
self.state.parked.push_back(rt);
}
}
}
}
if let Some(cr) = candidate {
trace!("Context switch to {:?}", cr);
// Do switch
let old = mem::replace(&mut self.state.cr, cr);
self.state.parked.push_back(old);
self.state.start_task_switching();
} else if let Some(due) = closest_due {
if self.state.cr.cr_state == CoroutineState::Ready {
trace!("No candidate found to run, retry same thread.");
// Let it run another quantum, maybe it was just blocked
continue 'run;
}
let time = due.saturating_duration_since(now);
trace!("No thread to switch to, sleep {:?}", time);
thread::sleep(time);
continue 'next;
} else {
// Nothing to run?
thread::yield_now();
trace!("No thread to switch to!");
}
let n_alive = self.state.parked.iter()
.filter(|p| p.cr_state.is_alive()).count();
if n_alive == 0 {
trace!("Stop task switching, no parked threads are alive");
self.state.stop_task_switching(); // This should improve performance in single-threaded mode
}
match self.state.cr.cr_state {
CoroutineState::Finished(_) | CoroutineState::YieldedValue(_) => {
break 'run Fault::RootReturned;
}
_ => {}
}
}
break 'next;
}
};
match fault {
Fault::Halt => {
debug!("Thread ended.");
}
e => {
eprintln!("*** Program failed: {} ***", e);
if let Some(instr) = self.info.program.ops.get(orig_pc.0 as usize) {
eprintln!("Source location: {}, line {}, column {}", self.info.program.file_names[instr.pos().file as usize].display(), instr.pos().line, instr.pos().column);
} else {
eprintln!("Instruction address: {}", orig_pc);
}
debug!("\nCore dump: {:?}", self.state);
}
}
}
}

@ -6,7 +6,9 @@ use crate::asm::data::literal::Value;
use crate::runtime::program::Program;
use crate::runtime::run_thread::ThreadToken;
use crate::module::{CrsnExtension, CrsnUniq};
use parking_lot::RwLock;
#[derive(Debug)]
pub struct ThreadInfo {
/// Thread ID
pub id: ThreadToken,
@ -16,6 +18,8 @@ pub struct ThreadInfo {
pub program: Arc<Program>,
/// Program to run
pub(crate) cycle_time: Duration,
/// Interval one thread/coroutine is let to run before the context switches
pub(crate) scheduler_interval: RwLock<Duration>,
/// Extensions
pub extensions: Arc<Vec<Box<dyn CrsnExtension>>>,
}

@ -1,5 +1,5 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};
use crate::asm::data::{Rd, RdData, RdObj, Register, Wr, WrData};
use crate::asm::data::literal::{Addr, Value};
@ -12,24 +12,114 @@ use nudge::{likely};
use crate::asm::instr::cond::Flag;
use std::fmt::{Debug, Formatter};
use std::fmt;
use std::time::{Instant, Duration};
pub struct RunState {
pub thread_info: Arc<ThreadInfo>,
/// Active stack frame
pub frame: StackFrame,
/// Call stack
pub call_stack: CallStack,
/// The active coroutine
pub cr: Box<CoroutineContext>,
/// Parked coroutines
pub parked: VecDeque<Box<CoroutineContext>>,
/// General purpose registers that stay valid for the entire run-time of the thread
pub global_regs: [Value; REG_COUNT],
/// Extension data
pub ext_data: ExtensionDataStore,
/// Execution deadline, if multi-tasked
pub cr_deadline: Option<Instant>,
/// Nonzero if inside a critical section
pub critical_section: Value,
}
#[derive(Debug,Default)]
pub struct CoroutineContext {
pub handle: Value,
/// Active stack frame
pub frame: StackFrame,
/// Call stack
pub call_stack: CallStack,
/// Coroutine run state
pub cr_state: CoroutineState,
}
/// Execution state of an inactive coroutine
#[derive(Debug, Eq, PartialEq)]
pub enum CoroutineState {
/// Ready to run, just started, or interrupted by the scheduler
Ready,
/// Delay in progress
Sleep { due: Instant },
/// The task finished
Finished(Vec<Value>),
/// The task yielded a value that must be consumed before it can resume.
/// State switches to Ready when the value is read.
YieldedValue(Value),
}
impl CoroutineState {
pub fn is_alive(&self) -> bool {
match self {
CoroutineState::Ready => true,
CoroutineState::Sleep { .. } => true,
CoroutineState::Finished(_) => false,
CoroutineState::YieldedValue(_) => true,
}
}
}
impl Default for CoroutineState {
fn default() -> Self {
Self::Ready
}
}
impl RunState {
/// Clear everything. Caution: when done at runtime, this effectively reboots the thread
pub fn reset(&mut self) {
self.cr = Default::default();
self.parked = Default::default();
self.global_regs = Default::default();
self.ext_data = Default::default();
}
/// Add a coroutine, marked as Ready, to run next
pub fn add_coroutine(&mut self, frame : StackFrame) -> Value {
let handle = self.thread_info.unique_handle();
trace!("Spawn cr {:#}", handle);
// front - so it runs ASAP
self.parked.push_front(Box::new(CoroutineContext {
handle,
frame,
call_stack: vec![],
cr_state: Default::default()
}));
if self.cr_deadline.is_none() {
// start context switching
self.start_task_switching();
}
handle
}
pub(crate) fn start_task_switching(&mut self) {
let ival = *self.thread_info.scheduler_interval.read();
if ival > Duration::default() {
self.cr_deadline = Some(Instant::now() + ival);
} else {
// Disabled
self.cr_deadline = None;
}
}
pub(crate) fn stop_task_switching(&mut self) {
self.cr_deadline = None;
}
}
impl Debug for RunState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("RunState")
.field("frame", &self.frame)
.field("call_stack", &self.call_stack)
.field("cr", &self.cr)
.field("parked", &self.parked)
.field("global_regs", &self.global_regs)
//.field("ext_data", &self.ext_data)
.finish()
@ -69,44 +159,44 @@ impl RunState {
#[inline(always)]
pub fn set_flag(&mut self, flag: Flag, set: bool) {
trace!("Flag {} = {:?}", flag, set);
self.frame.status.set(flag, set);
self.cr.frame.status.set(flag, set);
}
/// Check status flags for a condition
#[inline(always)]
pub fn test_cond(&self, cond: Cond) -> bool {
self.frame.status.test(cond)
self.cr.frame.status.test(cond)
}
/// Set program counter - address of the next instruction to run
pub fn set_pc(&mut self, pc: Addr) {
trace!("PC := {}", pc);
self.frame.pc = pc;
self.cr.frame.pc = pc;
}
/// Get program counter - address of the next instruction to run
pub fn get_pc(&self) -> Addr {
self.frame.pc
self.cr.frame.pc
}
/// Clear status flags
#[inline(always)]
pub fn clear_status(&mut self) {
self.frame.status.clear();
self.cr.frame.status.clear();
}
/// Update status flags using a variable.
/// The update is additive - call `clear_status()` first if desired!
#[inline(always)]
pub fn update_status(&mut self, val: Value) {
self.frame.status.update(val);
self.cr.frame.status.update(val);
}
/// Update status flags using a variable.
/// The update is additive - call `clear_status()` first if desired!
#[inline(always)]
pub fn update_status_float(&mut self, val: f64) {
self.frame.status.update_float(val);
self.cr.frame.status.update_float(val);
}
/// Read object handle value
@ -115,14 +205,16 @@ impl RunState {
}
/// Read a `Rd` value
#[inline]
pub fn read(&mut self, rd: impl Into<Rd>) -> Result<Value, Fault> {
let rd = rd.into();
// TODO dedupe
match rd.0 {
RdData::Immediate(v) => Ok(v),
RdData::Register(Register::Gen(rn)) => {
if likely(rn < REG_COUNT as u8) {
trace!("Rd {:?} = {}", rd, self.frame.gen[rn as usize]);
Ok(self.frame.gen[rn as usize])
trace!("Rd {:?} = {}", rd, self.cr.frame.gen[rn as usize]);
Ok(self.cr.frame.gen[rn as usize])
} else {
Err(Fault::RegisterNotExist { reg: Register::Gen(rn) })
}
@ -135,19 +227,18 @@ impl RunState {
Err(Fault::RegisterNotExist { reg: Register::Global(rn) })
}
}
RdData::Immediate(v) => Ok(v),
RdData::Register(Register::Arg(rn)) => {
if likely(rn < REG_COUNT as u8) {
trace!("Rd {:?} = {}", rd, self.frame.arg[rn as usize]);
Ok(self.frame.arg[rn as usize])
trace!("Rd {:?} = {}", rd, self.cr.frame.arg[rn as usize]);
Ok(self.cr.frame.arg[rn as usize])
} else {
Err(Fault::RegisterNotExist { reg: Register::Arg(rn) })
}
}
RdData::Register(Register::Res(rn)) => {
if likely(rn < REG_COUNT as u8) {
trace!("Rd {:?} = {}", rd, self.frame.res[rn as usize]);
Ok(self.frame.res[rn as usize])
trace!("Rd {:?} = {}", rd, self.cr.frame.res[rn as usize]);
Ok(self.cr.frame.res[rn as usize])
} else {
Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490
}
@ -163,6 +254,26 @@ impl RunState {
}
fn read_object(&mut self, reference: Value) -> Result<Value, Fault> {
// values yielded from coroutines
for cr in &mut self.parked {
if cr.handle == reference {
match cr.cr_state {
CoroutineState::Ready | CoroutineState::Sleep { .. } => {
return Err(Fault::Blocked);
}
CoroutineState::Finished(_) => {
self.set_flag(Flag::Eof, true);
warn!("Attempt to read yielded value of a finished coroutine");
return Ok(0);
}
CoroutineState::YieldedValue(v) => {
cr.cr_state = CoroutineState::Ready;
return Ok(v);
}
}
}
}
// This is a shitty dirty hack to allow iterating over the extensions while passing a mutable reference
// to self to the reading methods. Since the extensions array is in an Arc, it can't be mutated internally
// anyway, and we know it will still live after the method returns - unless someone does something incredibly stupid.
@ -202,7 +313,7 @@ impl RunState {
match wr.0 {
WrData::Register(Register::Gen(rn)) => {
if likely(rn < REG_COUNT as u8) {
self.frame.gen[rn as usize] = val;
self.cr.frame.gen[rn as usize] = val;
Ok(())
} else {
Err(Fault::RegisterNotExist { reg: Register::Gen(rn) })

@ -84,7 +84,7 @@ impl OpTrait for ArithOp {
offset + if max == u64::MAX {
rand::thread_rng().gen()
} else {
rand::thread_rng().gen_range(0, max)
rand::thread_rng().gen_range(0, max + 1)
}
};
state.write(dst, val)?;
@ -757,6 +757,15 @@ impl OpTrait for ArithOp {
ArithOp::FloatHypAcot { dst, a } => to_sexp_1_or_2("facoth", dst, a),
}
}
fn is_compile_time_evaluable(&self) -> bool {
match self {
ArithOp::Test { .. } | ArithOp::Compare { .. } | ArithOp::RangeTest { .. }
| ArithOp::FloatTest { .. } | ArithOp::FloatCompare { .. } | ArithOp::FloatRangeTest { .. } => false,
// rng is allowed... makes little sense, but it also has no side effects
_ => true, // a bold claim...
}
}
}
fn to_sexp_2_or_3(name: &str, dst: &Wr, a: &Rd, b: &Rd) -> Sexp {

@ -45,6 +45,7 @@ pub enum BufOps {
Write { obj: RdObj, idx: Rd, value: Rd },
Insert { obj: RdObj, idx: Rd, value: Rd },
Remove { dst: Wr, obj: RdObj, idx: Rd },
CompareSwap { obj: RdObj, idx: Rd, expected: Rd, src: Rd },
// Whole buffer ops
Resize { obj: RdObj, len: Rd },

@ -138,6 +138,33 @@ impl OpTrait for BufOps {
}
}
BufOps::CompareSwap { obj, idx, expected, src } => {
state.clear_status();
let handle = state.read_obj(obj)?;
let idx = state.read(idx)? as usize;
let expected = state.read(expected)?;
let store: &mut ExtData = state.ext_mut();
let buf = store.buffers.get_mut(&handle).ok_or_else(|| Fault::ObjectNotExist(handle))?;
if idx < buf.data.len() {
if buf.data[idx] == expected {
// This looks stupid and it is stupid - the dance is needed to satisfy the borrow checker.
let val = state.read(src)?;
let store: &mut ExtData = state.ext_mut();
let buf = store.buffers.get_mut(&handle).unwrap();
buf.data[idx] = val;
state.set_flag(Flag::Equal, true);
}
} else if idx == buf.data.len() && expected == 0 {
let val = state.read(src)?;
let store: &mut ExtData = state.ext_mut();
let buf = store.buffers.get_mut(&handle).unwrap();
buf.data.push_back(val);
state.set_flag(Flag::Equal, true);
} else {
state.set_flag(Flag::Overflow, true);
}
}
BufOps::Insert { obj, idx, value } => {
state.clear_status();
let handle = state.read_obj(obj)?;
@ -255,6 +282,10 @@ impl OpTrait for BufOps {
sexp::list(&[A("bfwr"), A(obj), A(idx), A(value)])
}
BufOps::CompareSwap { obj, idx, expected , src } => {
sexp::list(&[A("bfcas"), A(obj), A(idx), A(expected), A(src)])
}
BufOps::Insert { obj, idx, value } => {
sexp::list(&[A("bfins"), A(obj), A(idx), A(value)])
}

@ -75,6 +75,14 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
BufOps::Write { obj, idx, value }
}
"bfcas" => {
let obj = args.next_rdobj()?;
let idx = args.next_rd()?;
let expected = args.next_rd()?;
let src = args.next_rd()?;
BufOps::CompareSwap { obj, idx, expected, src }
}
"bfsz" => {
let dst = args.next_wr()?;
let obj = args.next_rdobj()?;

@ -12,6 +12,11 @@ pub enum ScreenOp {
y: Rd,
color: Rd,
},
GetPixel {
color: Wr,
x: Rd,
y: Rd,
},
GetMouse {
x: Wr,
y: Wr,
@ -28,6 +33,13 @@ pub enum ScreenOp {
Erase {
color: Rd,
},
FillRect {
x: Rd,
y: Rd,
w: Rd,
h: Rd,
color: Rd,
},
Blit {
force: Rd,
},

@ -3,7 +3,8 @@ use std::time::{Duration, Instant};
use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions};
use crsn::asm::data::literal::Value;
use crsn::asm::data::literal::{Value, is_negative};
use crsn::asm::instr::cond::Flag;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
@ -12,18 +13,21 @@ use crsn::sexp::Sexp;
use crsn::utils::A;
use crate::defs::ScreenOp;
use crsn::asm::instr::cond::Flag;
use std::mem;
#[derive(Debug)]
struct Opts {
auto_blit: bool,
frame_rate: Duration,
upscale: Value,
}
#[derive(Debug)]
struct Backend {
width: usize,
height: usize,
width: Value,
height: Value,
log_width: Value,
log_height: Value,
buffer: Vec<u32>,
window: Option<Window>,
last_render: Instant,
@ -35,19 +39,33 @@ impl Default for Backend {
Self {
width: 0,
height: 0,
log_width: 0,
log_height: 0,
buffer: vec![],
window: None,
last_render: Instant::now().sub(Duration::from_secs(1)),
opts: Opts {
auto_blit: true,
upscale: 1,
frame_rate: Duration::from_micros(16600),
},
}
}
}
impl Backend {
pub fn draw_rect_log(&mut self, x: Value, y: Value, w: Value, h: Value, color: u32) {
for yy in (y * self.opts.upscale)..((y + h) * self.opts.upscale).min(self.height) {
for xx in (x * self.opts.upscale)..((x + w) * self.opts.upscale).min(self.width) {
self.buffer[(yy * self.width + xx) as usize] = color as u32;
}
}
}
}
pub const OPT_AUTO_BLIT: u64 = 1;
pub const OPT_FRAME_RATE: u64 = 2;
pub const OPT_UPSCALE: u64 = 3;
// Hack for Window
unsafe impl std::marker::Send for Backend {}
@ -81,8 +99,22 @@ impl OpTrait for ScreenOp {
debug!("Set auto blit to {:?}", backend.opts.auto_blit);
}
OPT_FRAME_RATE => {
backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val);
debug!("Set frame rate to {:?}", backend.opts.frame_rate);
if val == 0 {
state.set_flag(Flag::Invalid, true);
} else {
backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val);
debug!("Set frame rate to {:?}", backend.opts.frame_rate);
}
}
OPT_UPSCALE => {
if val == 0 {
state.set_flag(Flag::Invalid, true);
} else {
backend.opts.upscale = val;
backend.log_width = backend.width / val;
backend.log_height = backend.height / val;
debug!("Set upscale to {:?}", val);
}
}
_other => {
unreachable!()
@ -106,8 +138,9 @@ impl OpTrait for ScreenOp {
match &mut backend.window {
Some(w) => {
if !w.is_open() {
// TODO...
std::process::exit(0);
debug!("Window is closed");
let _ = backend.window.take();
return Err(Fault::Halt);
}
w.update();
@ -126,23 +159,46 @@ impl OpTrait for ScreenOp {
let backend: &mut Backend = state.ext_mut();
if x >= backend.width as u64 || y >= backend.height as u64 {
if x >= backend.log_width as u64 || y >= backend.log_height as u64 {
state.set_flag(Flag::Overflow, true);
return Ok(eres);
}
match &mut backend.window {
Some(_w) => {
let index = y * backend.width as u64 + x;
backend.draw_rect_log(x, y, 1, 1, color as u32);
if backend.opts.auto_blit {
blit_maybe(backend)?;
}
}
None => {
state.set_flag(Flag::Invalid, true);
}
}
}
ScreenOp::GetPixel { color, x, y } => {
state.clear_status();
let x = state.read(x)?;
let y = state.read(y)?;
let backend: &mut Backend = state.ext_mut();
if x >= backend.log_width as u64 || y >= backend.log_height as u64 {
state.set_flag(Flag::Overflow, true);
return Ok(eres);
}
match &mut backend.window {
Some(_w) => {
let index = y * backend.opts.upscale * backend.width as u64 + x * backend.opts.upscale;
if index as usize > backend.buffer.len() {
warn!("Screen set pixel out of bounds");
state.set_flag(Flag::Invalid, true);
} else {
backend.buffer[index as usize] = color as u32;
if backend.opts.auto_blit {
blit_maybe(backend)?;
}
let c = backend.buffer[index as usize];
trace!("index {}, c {}", index, c);
state.write(color, c as Value)?;
}
}
None => {
@ -163,8 +219,11 @@ impl OpTrait for ScreenOp {
state.set_flag(Flag::Overflow, true);
}
Some((xf, yf)) => {
let xval = xf.round() as u64;
let yval = yf.round() as u64;
let mut xval = xf.round() as u64;
let mut yval = yf.round() as u64;
xval /= backend.opts.upscale;
yval /= backend.opts.upscale;
state.write(x, xval)?;
state.write(y, yval)?;
@ -227,6 +286,51 @@ impl OpTrait for ScreenOp {
}
}
}
ScreenOp::FillRect {
x, y,
w, h,
color
} => {
let mut x = state.read(x)?;
let mut y = state.read(y)?;
let mut w = state.read(w)?;
let mut h = state.read(h)?;
let c = state.read(color)?;
let backend: &mut Backend = state.ext_mut();
if is_negative(w) || is_negative(h) {
state.set_flag(Flag::Invalid, true);
return Ok(eres);
}
if w > backend.log_width {
w = backend.log_width;
}
if h > backend.log_height {
h = backend.log_height;
}
if is_negative(x) {
let xi : i64 = unsafe { mem::transmute(x) };
let minus = xi.abs() as u64;
w -= minus;
x = 0;
}
if is_negative(y) {
let yi : i64 = unsafe { mem::transmute(y) };
let minus = yi.abs() as u64;
h -= minus;
y = 0;
}
match &mut backend.window {
Some(_w) => {
backend.draw_rect_log(x, y, w, h, c as u32);
}
None => {
state.set_flag(Flag::Invalid, true);
}
}
}
}
Ok(eres)
@ -236,13 +340,17 @@ impl OpTrait for ScreenOp {
match self {
ScreenOp::SetOpt { opt, val } => sexp::list(&[A("sc-opt"), A(opt), A(val)]),
ScreenOp::ScreenInit { width, height } => sexp::list(&[A("sc-init"), A(width), A(height)]),
ScreenOp::SetPixel { x, y, color } => sexp::list(&[A("sc-px"), A(x), A(y), A(color)]),
ScreenOp::SetPixel { x, y, color } => sexp::list(&[A("sc-wr"), A(x), A(y), A(color)]),
ScreenOp::GetPixel { color, x, y } => sexp::list(&[A("sc-rd"), A(color), A(x), A(y)]),
ScreenOp::Blit { force } => sexp::list(&[A("sc-blit"), A(force)]),
ScreenOp::Update => sexp::list(&[A("sc-poll")]),
ScreenOp::GetMouse { x, y } => sexp::list(&[A("sc-mouse"), A(x), A(y)]),
ScreenOp::TestKey { pressed, code } => sexp::list(&[A("sc-key"), A(pressed), A(code)]),
ScreenOp::TestMouse { pressed, button } => sexp::list(&[A("sc-mbtn"), A(pressed), A(button)]),
ScreenOp::Erase { color } => sexp::list(&[A("sc-erase"), A(color)]),
ScreenOp::FillRect { x, y, w, h, color } => {
sexp::list(&[A("sc-rect"), A(x), A(y), A(w), A(h), A(color)])
}
}
}
}
@ -266,8 +374,10 @@ fn init(state: &mut RunState, width: Value, height: Value) -> Result<(), Fault>
return Err(Fault::NotAllowed("Screen already initialized".into()));
}
backend.width = width as usize;
backend.height = height as usize;
backend.width = width;
backend.height = height;
backend.log_width = width;
backend.log_height = height;
backend.buffer = vec![0; (width * height) as usize];
backend.window = Some(window);
@ -289,10 +399,12 @@ fn blit(backend: &mut Backend) -> Result<(), Fault> {
let w = backend.window.as_mut().unwrap();
if !w.is_open() {
debug!("Window is closed");
let _ = backend.window.take();
return Err(Fault::Halt);
}
w.update_with_buffer(&backend.buffer, backend.width, backend.height)
w.update_with_buffer(&backend.buffer, backend.width as usize, backend.height as usize)
.expect("Update screen"); // TODO fault
backend.last_render = Instant::now();
@ -301,7 +413,7 @@ fn blit(backend: &mut Backend) -> Result<(), Fault> {
fn num2key(num: Value) -> Option<Key> {
let remap = [
Key::Key0,
Key::Key0, // 0
Key::Key1,
Key::Key2,
Key::Key3,
@ -310,28 +422,29 @@ fn num2key(num: Value) -> Option<Key> {
Key::Key6,
Key::Key7,
Key::Key8,
Key::Key9,
Key::Key9, // 9
Key::A, // 10
Key::B,
Key::C,
Key::D,
Key::E,
Key::F,
Key::F, // 15
Key::G,
Key::H,
Key::I,
Key::J,
Key::K,
Key::K, // 20
Key::L,
Key::M,
Key::N,
Key::O,
Key::P,
Key::P, // 25
Key::Q,
Key::R,
Key::S,
Key::T,
Key::U,
Key::U, // 30
Key::V,
Key::W,
Key::X,
@ -342,12 +455,12 @@ fn num2key(num: Value) -> Option<Key> {
Key::F2,
Key::F3,
Key::F4,
Key::F5,
Key::F5, // 40
Key::F6,
Key::F7,
Key::F8,
Key::F9,
Key::F10,
Key::F10, // 45
Key::F11,
Key::F12,
Key::F13,
@ -355,18 +468,18 @@ fn num2key(num: Value) -> Option<Key> {
Key::F15, // 50
Key::Down, // 51
Key::Left,
Key::Right,
Key::Up,
Key::Left, // 52
Key::Right, // 53
Key::Up, // 54
Key::Apostrophe,
Key::Backquote,
Key::Backquote, // 56 (a backtick)
Key::Backslash, // 57
Key::Comma,
Key::Equal,
Key::LeftBracket,
Key::LeftBracket, // 60 [
Key::Minus,
Key::Period,
Key::RightBracket,
Key::RightBracket, // 63 ] *This does not work due to a bug in the library we use - PR is sent upstream https://github.com/emoon/rust_minifb/pull/222*
Key::Semicolon,
Key::Slash, // 65
Key::Backspace,

@ -7,7 +7,8 @@ use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::{CrsnExtension, ParseRes};
use crsn::sexp::SourcePosition;
use crsn::asm::data::literal::Value;
use crate::exec::{OPT_AUTO_BLIT, OPT_FRAME_RATE};
use crate::exec::{OPT_AUTO_BLIT, OPT_FRAME_RATE, OPT_UPSCALE};
use minifb::Key;
mod defs;
mod parse;
@ -36,8 +37,124 @@ impl CrsnExtension for ScreenOps {
fn get_constant_value<'a>(&self, name: &str) -> Option<Value>
{
match name {
"SCREEN_AUTO_BLIT" => Some(OPT_AUTO_BLIT),
"SCREEN_AUTO_BLIT" | "SCREEN_AUTOBLIT" => Some(OPT_AUTO_BLIT),
"SCREEN_FPS" => Some(OPT_FRAME_RATE),
"SCREEN_UPSCALE" => Some(OPT_UPSCALE),
"MBTN_LEFT" => Some(0),
"MBTN_RIGHT" => Some(1),
"MBTN_MIDDLE" | "MBTN_MID" => Some(2),
"KEY_0" => Some(Key::Key0 as Value), // 0
"KEY_1" => Some(Key::Key1 as Value),
"KEY_2" => Some(Key::Key2 as Value),
"KEY_3" => Some(Key::Key3 as Value),
"KEY_4" => Some(Key::Key4 as Value),
"KEY_5" => Some(Key::Key5 as Value),
"KEY_6" => Some(Key::Key6 as Value),
"KEY_7" => Some(Key::Key7 as Value),
"KEY_8" => Some(Key::Key8 as Value),
"KEY_9" => Some(Key::Key9 as Value), // 9
"KEY_A" => Some(Key::A as Value), // 10
"KEY_B" => Some(Key::B as Value),
"KEY_C" => Some(Key::C as Value),
"KEY_D" => Some(Key::D as Value),
"KEY_E" => Some(Key::E as Value),
"KEY_F" => Some(Key::F as Value), // 15
"KEY_G" => Some(Key::G as Value),
"KEY_H" => Some(Key::H as Value),
"KEY_I" => Some(Key::I as Value),
"KEY_J" => Some(Key::J as Value),
"KEY_K" => Some(Key::K as Value), // 20
"KEY_L" => Some(Key::L as Value),
"KEY_M" => Some(Key::M as Value),
"KEY_N" => Some(Key::N as Value),
"KEY_O" => Some(Key::O as Value),
"KEY_P" => Some(Key::P as Value), // 25
"KEY_Q" => Some(Key::Q as Value),
"KEY_R" => Some(Key::R as Value),
"KEY_S" => Some(Key::S as Value),
"KEY_T" => Some(Key::T as Value),
"KEY_U" => Some(Key::U as Value), // 30
"KEY_V" => Some(Key::V as Value),
"KEY_W" => Some(Key::W as Value),
"KEY_X" => Some(Key::X as Value),
"KEY_Y" => Some(Key::Y as Value),
"KEY_Z" => Some(Key::Z as Value), // 35
"KEY_F1" => Some(Key::F1 as Value), // 36
"KEY_F2" => Some(Key::F2 as Value),
"KEY_F3" => Some(Key::F3 as Value),
"KEY_F4" => Some(Key::F4 as Value),
"KEY_F5" => Some(Key::F5 as Value), // 40
"KEY_F6" => Some(Key::F6 as Value),
"KEY_F7" => Some(Key::F7 as Value),
"KEY_F8" => Some(Key::F8 as Value),
"KEY_F9" => Some(Key::F9 as Value),
"KEY_F10" => Some(Key::F10 as Value), // 45
"KEY_F11" => Some(Key::F11 as Value),
"KEY_F12" => Some(Key::F12 as Value),
"KEY_F13" => Some(Key::F13 as Value),
"KEY_F14" => Some(Key::F14 as Value),
"KEY_F15" => Some(Key::F15 as Value), // 50
"KEY_Down" => Some(Key::Down as Value), // 51
"KEY_Left" => Some(Key::Left as Value), // 52
"KEY_Right" => Some(Key::Right as Value), // 53
"KEY_Up" => Some(Key::Up as Value), // 54
"KEY_Apos" => Some(Key::Apostrophe as Value),
"KEY_Backtick" => Some(Key::Backquote as Value), // 56 (a backtick)
"KEY_Backslash" => Some(Key::Backslash as Value), // 57
"KEY_Comma" => Some(Key::Comma as Value),
"KEY_Equal" => Some(Key::Equal as Value),
"KEY_BracketL" => Some(Key::LeftBracket as Value), // 60 [
"KEY_Minus" => Some(Key::Minus as Value),
"KEY_Period" => Some(Key::Period as Value),
"KEY_BracketR" => Some(Key::RightBracket as Value), // 63 ] *This does not work due to a bug in the library we use - PR is sent upstream https://github.com/emoon/rust_minifb/pull/222*
"KEY_Semicolon" => Some(Key::Semicolon as Value),
"KEY_Slash" => Some(Key::Slash as Value), // 65
"KEY_Backspace" => Some(Key::Backspace as Value),
"KEY_Delete" => Some(Key::Delete as Value),
"KEY_End" => Some(Key::End as Value),
"KEY_Enter" => Some(Key::Enter as Value),
"KEY_Escape" => Some(Key::Escape as Value), // 70
"KEY_Home" => Some(Key::Home as Value),
"KEY_Insert" => Some(Key::Insert as Value),
"KEY_Menu" => Some(Key::Menu as Value),
"KEY_PageDown" => Some(Key::PageDown as Value),
"KEY_PageUp" => Some(Key::PageUp as Value),
"KEY_Pause" => Some(Key::Pause as Value), // 76
"KEY_Space" => Some(Key::Space as Value),
"KEY_Tab" => Some(Key::Tab as Value),
"KEY_NumLock" => Some(Key::NumLock as Value),
"KEY_CapsLock" => Some(Key::CapsLock as Value),
"KEY_ScrollLock" => Some(Key::ScrollLock as Value),
"KEY_ShiftL" => Some(Key::LeftShift as Value),
"KEY_ShiftR" => Some(Key::RightShift as Value),
"KEY_CtrlL" => Some(Key::LeftCtrl as Value),
"KEY_CtrlR" => Some(Key::RightCtrl as Value),
"KEY_KP0" => Some(Key::NumPad0 as Value), // 86
"KEY_KP1" => Some(Key::NumPad1 as Value),
"KEY_KP2" => Some(Key::NumPad2 as Value),
"KEY_KP3" => Some(Key::NumPad3 as Value),
"KEY_KP4" => Some(Key::NumPad4 as Value),
"KEY_KP5" => Some(Key::NumPad5 as Value),
"KEY_KP6" => Some(Key::NumPad6 as Value),
"KEY_KP7" => Some(Key::NumPad7 as Value),
"KEY_KP8" => Some(Key::NumPad8 as Value),
"KEY_KP9" => Some(Key::NumPad9 as Value),
"KEY_KPDot" => Some(Key::NumPadDot as Value),
"KEY_KPSlash" => Some(Key::NumPadSlash as Value),
"KEY_KPAsterisk" => Some(Key::NumPadAsterisk as Value),
"KEY_KPMinus" => Some(Key::NumPadMinus as Value),
"KEY_KPPlus" => Some(Key::NumPadPlus as Value),
"KEY_KPEnter" => Some(Key::NumPadEnter as Value), // 100
"KEY_AltL" => Some(Key::LeftAlt as Value),
"KEY_AltR" => Some(Key::RightAlt as Value),
"KEY_WinL" | "KEY_SuperL" | "KEY_MetaL" => Some(Key::LeftSuper as Value),
"KEY_WinR" | "KEY_SuperR" | "KEY_MetaR" => Some(Key::RightSuper as Value),
_ => None
}
}

@ -9,9 +9,10 @@ use crate::defs::ScreenOp;
use super::exec::OPT_AUTO_BLIT;
use super::exec::OPT_FRAME_RATE;
use super::exec::OPT_UPSCALE;
pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
Ok(ParseRes::ext(match keyword {
let rv = Ok(ParseRes::ext(match keyword {
"sc-init" => {
ScreenOp::ScreenInit {
width: args.next_rd()?,
@ -29,7 +30,7 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
}
}
"sc-px" => {
"sc-wr" | "sc-px" => {
ScreenOp::SetPixel {
x: args.next_rd()?,
y: args.next_rd()?,
@ -37,6 +38,14 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
}
}
"sc-rd" => {
ScreenOp::GetPixel {
color: args.next_wr()?,
x: args.next_rd()?,
y: args.next_rd()?,
}
}
"sc-opt" => {
let (val, valopt) = args.next_value()?;
ScreenOp::SetOpt {
@ -47,6 +56,9 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
OPT_FRAME_RATE => {
OPT_FRAME_RATE
}
OPT_UPSCALE => {
OPT_UPSCALE
}
_ => {
return Err(CrsnError::Parse("Bad screen option".into(), valopt));
}
@ -90,8 +102,20 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
}
}
"sc-rect" => {
ScreenOp::FillRect {
x: args.next_rd()?,
y: args.next_rd()?,
w: args.next_rd()?,
h: args.next_rd()?,
color: args.next_rd()?
}
}
_other => {
return Ok(ParseRes::Unknown(args));
}
}))
}));
args.ensure_empty_custom("Too many arguments for this instruction!")?;
return rv;
}

@ -9,3 +9,4 @@ edition = "2018"
[dependencies]
crsn = { path = "../crsn" }
libc = "0.2.79"
log = "0.4.11"

@ -1,3 +1,6 @@
#[macro_use]
extern crate log;
use crsn::asm::data::literal::Value;
use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
@ -18,13 +21,29 @@ mod console {
use std::ffi::c_void;
use std::mem::{self, MaybeUninit};
use crsn::runtime::fault::Fault;
use std::time::{Instant};
struct ReadCharState {
bytes: [u8; 4],
cursor: usize,
len: usize,
last_timeout : u8,
}
static mut READ_CHAR_STATE: ReadCharState = ReadCharState {
bytes: [0; 4],
cursor: 0,
len: 0,
last_timeout : 0,
};
fn setup_fd(fd: RawFd) -> io::Result<libc::termios> {
fn setup_fd(fd: RawFd) -> Result<libc::termios, Fault> {
use libc::*;
let mut tio = MaybeUninit::uninit();
if 0 != unsafe { tcgetattr(fd, tio.as_mut_ptr()) } {
return Err(io::Error::last_os_error());
return Err(Fault::IOError(io::Error::last_os_error()));
}
let mut tio = unsafe { MaybeUninit::assume_init(tio) };
let old_tio : termios = unsafe { mem::transmute_copy(&tio) };
@ -36,36 +55,76 @@ mod console {
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 0;
if 0 != unsafe { tcsetattr(fd, TCSANOW, &tio) } {
return Err(io::Error::last_os_error());
return Err(Fault::IOError(io::Error::last_os_error()));
}
Ok(old_tio)
}
pub fn init_io() -> io::Result<libc::termios> {
pub fn init_io() -> Result<libc::termios, Fault> {
setup_fd(libc::STDIN_FILENO)
}
pub fn read_byte() -> io::Result<u8> {
pub fn read_byte(deadline : Option<Instant>) -> Result<u8, Fault> {
// Set TIO timeout
let state = unsafe { &mut READ_CHAR_STATE };
if (state.last_timeout == 0) && deadline.is_none() {
// Keep it like that
} else {
let vtime = if let Some(dl) = deadline {
let timeout = dl.saturating_duration_since(Instant::now());
((timeout.as_secs_f32() * 10.0).round() as u32).min(255).max(1) as u8
} else {
0
};
if state.last_timeout != vtime {
// vtime changes
state.last_timeout = vtime;
let mut tio = MaybeUninit::uninit();
if 0 != unsafe { libc::tcgetattr(libc::STDIN_FILENO, tio.as_mut_ptr()) } {
return Err(Fault::IOError(io::Error::last_os_error()));
}
let mut tio = unsafe { MaybeUninit::assume_init(tio) };
if vtime > 0 {
tio.c_cc[libc::VTIME] = vtime; /* unit = 0.1 */
tio.c_cc[libc::VMIN] = 0;
} else {
tio.c_cc[libc::VTIME] = 0; // no counting
tio.c_cc[libc::VMIN] = 1; // want at least one character
}
unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) };
}
}
let mut buf = 0u8;
let len = unsafe { libc::read(libc::STDIN_FILENO, &mut buf as *mut u8 as *mut c_void, 1) };
if len == 0 && state.last_timeout != 0 {
return Err(Fault::IOError(io::Error::new(std::io::ErrorKind::TimedOut, "")));
}
if len <= 0 {
Err(io::Error::last_os_error())
Err(Fault::IOError(io::Error::last_os_error()))
} else {
Ok(buf as u8)
}
}
pub fn write_byte(b : u8) -> io::Result<()> {
pub fn write_byte(b : u8) -> Result<(), Fault> {
let len = unsafe { libc::write(libc::STDOUT_FILENO, &b as *const u8 as *const c_void, 1) };
if len <= 0 {
Err(io::Error::last_os_error())
Err(Fault::IOError(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn write_char(c : char) -> io::Result<()> {
pub fn write_char(c : char) -> Result<(), Fault> {
let mut buf = [0u8; 4];
for b in c.encode_utf8(&mut buf).as_bytes() {
write_byte(*b)?;
@ -73,30 +132,43 @@ mod console {
Ok(())
}
pub fn read_char() -> io::Result<char> {
let first = read_byte()?;
pub fn read_char(deadline : Option<Instant>) -> Result<char, Fault> {
let state = unsafe { &mut READ_CHAR_STATE };
if first & 0x80 == 0 {
return Ok(first as char);
}
if state.cursor == 0 {
let first = read_byte(deadline)?;
if first & 0x80 == 0 {
return Ok(first as char);
}
let mut bytes = [first, 0, 0, 0];
state.bytes[0] = first;
state.cursor = 1;
state.len = if first & 0b1110_0000 == 0b1100_0000 {
2
} else if first & 0b1111_0000 == 0b1110_0000 {
3
} else /*if first & 0b1111_1000 == 0b1111_0000*/ {
4
};
}
let remain = if first & 0b1110_0000 == 0b1100_0000 {
1
} else if first & 0b1111_0000 == 0b1110_0000 {
2
} else /*if first & 0b1111_1000 == 0b1111_0000*/ {
3
};
let len = state.len;
for n in 1..=remain {
bytes[n] = read_byte()?;
while state.cursor < len {
let b = read_byte(deadline)?;
state.bytes[state.cursor] = b;
state.cursor += 1;
}
std::str::from_utf8(&bytes[..=remain])
let rv = std::str::from_utf8(&state.bytes[..=len])
.map(|s| s.chars().nth(0).unwrap())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
.map_err(|e| Fault::IOError(io::Error::new(io::ErrorKind::InvalidData, e)));
state.cursor = 0;
state.len = 0;
rv
}
}
@ -134,6 +206,7 @@ impl StdioOps {
impl Drop for StdioOps {
fn drop(&mut self) {
debug!("stdin restore");
// Un-break the terminal
if let Some(tio) = self.old_tio.take() {
let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) };
@ -179,35 +252,47 @@ impl CrsnExtension for StdioOps {
fn read_obj(&self, state: &mut RunState, handle: Value)
-> Result<Option<Value>, Fault>
{
let deadline = state.cr_deadline;
if handle == self.hdl_stdin {
match console::read_char() {
match console::read_char(deadline) {
Ok(c) => {
return Ok(Some(c as Value));
}
Err(e) => {
Err(Fault::IOError(e)) => {
if e.kind() == io::ErrorKind::TimedOut {
return Err(Fault::Blocked);
}
state.set_flag(Flag::Invalid, true);
if e.kind() != io::ErrorKind::InvalidData {
state.set_flag(Flag::Eof, true);
}
return Ok(Some(0));
}
Err(other) => {
return Err(other);
}
}
}
if handle == self.hdl_stdin_raw {
match console::read_byte() {
} else if handle == self.hdl_stdin_raw {
match console::read_byte(deadline) {
Ok(b) => {
return Ok(Some(b as Value));
}
Err(_e) => {
Err(Fault::IOError(e)) => {
if e.kind() == io::ErrorKind::TimedOut {
return Err(Fault::Blocked);
}
state.set_flag(Flag::Invalid, true);
state.set_flag(Flag::Eof, true);
return Ok(Some(0));
}
Err(other) => {
return Err(other);
}
}
}
if handle == self.hdl_stdout || handle == self.hdl_stdout_raw {
} else if handle == self.hdl_stdout || handle == self.hdl_stdout_raw {
state.set_flag(Flag::Invalid, true);
return Ok(Some(0));
}
@ -250,13 +335,15 @@ impl CrsnExtension for StdioOps {
fn read_obj_all(&self, state: &mut RunState, whandle: Wr, rhandle: Value)
-> Result<Option<()>, Fault>
{
// XXX This is blocking, there is no sensible way to split it up.
if rhandle == self.hdl_stdin {
loop {
match console::read_char() {
match console::read_char(None) {
Ok(c) => {
state.write(whandle, c as Value)?;
}
Err(e) => {
Err(Fault::IOError(e)) => {
if e.kind() != io::ErrorKind::InvalidData {
state.set_flag(Flag::Eof, true);
} else {
@ -264,17 +351,20 @@ impl CrsnExtension for StdioOps {
}
return Ok(Some(()));
}
Err(other) => {
return Err(other);
}
}
}
}
if rhandle == self.hdl_stdin_raw {
loop {
match console::read_byte() {
match console::read_byte(None) {
Ok(c) => {
state.write(whandle, c as Value)?;
}
Err(e) => {
Err(Fault::IOError(e)) => {
if e.kind() != io::ErrorKind::InvalidData {
state.set_flag(Flag::Eof, true);
} else {
@ -282,6 +372,9 @@ impl CrsnExtension for StdioOps {
}
return Ok(Some(()));
}
Err(other) => {
return Err(other);
}
}
}
}

@ -0,0 +1,38 @@
(
(lds @cout "main\n")
(msleep 500)
(lds @cout "main\n")
(msleep 500)
(lds @cout "Spawnign bg\n")
(spawn r15 bg 10)
(msleep 500)
(lds @cout "FG\n")
(msleep 500)
(lds @cout "FG\n")
(msleep 500)
(lds @cout "FG\n")
(msleep 1000)
(lds @cout "Wait for BG\n")
(join @r15)
(lds @cout "Joined\n")
(msleep 500)
(lds @cout "main\n")
(msleep 500)
(lds @cout "main\n")
(msleep 500)
(proc bg times
(ld r0 times)
(:x)
(msleep 500)
(lds @cout "***BG\n")
(dec r0 (nz? (j :x)))
(lds @cout "***BG done.\n")
(ret)
)
)

@ -0,0 +1,44 @@
(
; using a coroutine as a generator
(spawn r15 fibo)
(ld r0 20)
(:next)
(ld r1 @r15) ; Read a yielded value
(call printnum r1)
(lds @cout ", ")
(dec r0)
(j.nz :next)
(ld @cout '\n')
(halt)
(proc fibo
(ld r0 0)
(ld r1 1)
(:x)
(yield r1)
(add r2 r0 r1)
(ld r0 r1)
(ld r1 r2)
(j :x)
)
(proc printnum num
(mkbf r15)
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @r15 r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @r15 '-')))
(lds @cout @r15)
(del @r15)
(ret)))
(j :next)
)
)

@ -0,0 +1,44 @@
(
; This example shows the use of critical sections.
; Set short timeslice (50us) to make the effect more pronounced
(rt-opt RT_TIMESLICE 50)
(spawn _ unsafe 'A' 'Z')
(spawn _ unsafe 'a' 'z')
(spawn _ safe '0' '9') ; Notice that the sequence 0-9 is always printed in its entirety - because it is in a critical section
(msleep 200)
(halt)
(proc unsafe start end
; This can be interrupted any time
(:x)
(yield)
(ld r0 start)
(:l)
(ld @cout r0)
(cmp r0 end)
(j.eq :x)
(inc r0)
(j :l)
)
(proc safe start end
(:again)
(ld r0 start)
; The sequence will always be complete
(crit
(ld @cout ' ') ; space to make it easier to read
(:l)
(ld @cout r0)
(cmp r0 end)
(j.eq :x)
(inc r0)
(j :l)
(:x)
(ld @cout ' ') ; space to make it easier to read
)
(yield)
(j :again)
)
)

@ -0,0 +1,17 @@
(
; This demo shows that stdin is nearly non-blocking
(spawn _ ReadAndEcho)
(:a)
(mslp 2500)
(lds @cout "Tick\n")
(j :a)
(proc ReadAndEcho
(:a)
(ld @cout '?')
(ld @cout @cin)
(j :a)
)
)

@ -0,0 +1,15 @@
(
(def A 10)
(def FOO (=add A (sub 10 5)))
; FOO is 15
(ld r0 (=add 14 1))
(cmp r0 15 (ne? (fault "1")))
(ld r0 (=add FOO 1))
(cmp r0 16 (ne? (fault "2")))
(ld r0 (=add (sub FOO 2) 3))
(cmp r0 16 (ne? (fault "3")))
)

@ -0,0 +1,6 @@
(
(ld r0 5)
(:next)
(rng r1 0 1 (z? (lds @cout "(1€)\n")) (else? (lds @cout "(::)\n")))
(dec r0 (nz? (j :next)))
)

@ -0,0 +1 @@
a.out

@ -0,0 +1,7 @@
.PHONY: run
run: a.out
./a.out > font.csn
a.out: convert.c font.xbm
cc convert.c

@ -0,0 +1,3 @@
This is a demo of producing font data for use in CRSN programs.
The source font was obtained from a .FON file using psftools

@ -0,0 +1,40 @@
// Convert the xbm file to crsn
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "font.xbm"
void main() {
uint64_t digits[256];
printf("(sym FONT r15)\n");
printf("(mkbf FONT (\n");
for (int ch = 0; ch < 256; ch++) {
uint64_t symbol = 0;
for(int row = 0; row<8;row++) {
symbol <<= 8ull;
unsigned char slice = psf_bits[row*32 + (ch/32)*(32*8) + (ch%32)];
symbol |= slice;
/* // show the symbol
printf(" ; ");
for(int j=0;j<8;j++) {
if(slice&0x1) {
printf("");
} else {
printf(" ");
}
slice >>= 1;
}
printf("\n");
//*/
}
digits[ch] = symbol;
printf(" 0x%016lx ; %d", symbol, ch);
if (ch >= 32 && ch <= 126) {
printf(" '%c'\n", ch);
} else {
printf("\n", ch);
}
}
printf("))\n");
}

@ -0,0 +1,259 @@
(sym FONT r15)
(mkbf FONT (
0x0000000000000000 ; 0
0x7e81a581bd99817e ; 1
0x7effdbffc3e7ff7e ; 2
0x367f7f7f3e1c0800 ; 3
0x081c3e7f3e1c0800 ; 4
0x1c3e1c7f7f3e1c3e ; 5
0x08081c3e7f3e1c3e ; 6
0x0000183c3c180000 ; 7
0xffffe7c3c3e7ffff ; 8
0x003c664242663c00 ; 9
0xffc399bdbd99c3ff ; 10
0xf0e0f0be3333331e ; 11
0x3c6666663c187e18 ; 12
0xfcccfc0c0c0e0f07 ; 13
0xfec6fec6c6e66703 ; 14
0x995a3ce7e73c5a99 ; 15
0x01071f7f1f070100 ; 16
0x40707c7f7c704000 ; 17
0x183c7e18187e3c18 ; 18
0x6666666666006600 ; 19
0xfedbdbded8d8d800 ; 20
0x7cc61c36361c331e ; 21
0x000000007e7e7e00 ; 22
0x183c7e187e3c18ff ; 23
0x183c7e1818181800 ; 24
0x181818187e3c1800 ; 25
0x0018307f30180000 ; 26
0x000c067f060c0000 ; 27
0x00000303037f0000 ; 28
0x002466ff66240000 ; 29
0x00183c7effff0000 ; 30
0x00ffff7e3c180000 ; 31
0x0000000000000000 ; 32 ' '
0x0c1e1e0c0c000c00 ; 33 '!'
0x3636360000000000 ; 34 '"'
0x36367f367f363600 ; 35 '#'
0x0c3e031e301f0c00 ; 36 '$'
0x006333180c666300 ; 37 '%'
0x1c361c6e3b336e00 ; 38 '&'
0x0606030000000000 ; 39 '''
0x180c0606060c1800 ; 40 '('
0x060c1818180c0600 ; 41 ')'
0x00663cff3c660000 ; 42 '*'
0x000c0c3f0c0c0000 ; 43 '+'
0x00000000000c0c06 ; 44 ','
0x0000003f00000000 ; 45 '-'
0x00000000000c0c00 ; 46 '.'
0x6030180c06030100 ; 47 '/'
0x3e63737b6f673e00 ; 48 '0'
0x0c0e0c0c0c0c3f00 ; 49 '1'
0x1e33301c06333f00 ; 50 '2'
0x1e33301c30331e00 ; 51 '3'
0x383c36337f307800 ; 52 '4'
0x3f031f3030331e00 ; 53 '5'
0x1c06031f33331e00 ; 54 '6'
0x3f3330180c0c0c00 ; 55 '7'
0x1e33331e33331e00 ; 56 '8'
0x1e33333e30180e00 ; 57 '9'
0x000c0c00000c0c00 ; 58 ':'
0x000c0c00000c0c06 ; 59 ';'
0x180c0603060c1800 ; 60 '<'
0x00003f00003f0000 ; 61 '='
0x060c1830180c0600 ; 62 '>'
0x1e3330180c000c00 ; 63 '?'
0x3e637b7b7b031e00 ; 64 '@'
0x0c1e33333f333300 ; 65 'A'
0x3f66663e66663f00 ; 66 'B'
0x3c66030303663c00 ; 67 'C'
0x1f36666666361f00 ; 68 'D'
0x7f46161e16467f00 ; 69 'E'
0x7f46161e16060f00 ; 70 'F'
0x3c66030373667c00 ; 71 'G'
0x3333333f33333300 ; 72 'H'
0x1e0c0c0c0c0c1e00 ; 73 'I'
0x7830303033331e00 ; 74 'J'
0x6766361e36666700 ; 75 'K'
0x0f06060646667f00 ; 76 'L'
0x63777f7f6b636300 ; 77 'M'
0x63676f7b73636300 ; 78 'N'
0x1c36636363361c00 ; 79 'O'
0x3f66663e06060f00 ; 80 'P'
0x1e3333333b1e3800 ; 81 'Q'
0x3f66663e36666700 ; 82 'R'
0x1e33070e38331e00 ; 83 'S'
0x3f2d0c0c0c0c1e00 ; 84 'T'
0x3333333333333f00 ; 85 'U'
0x33333333331e0c00 ; 86 'V'
0x6363636b7f776300 ; 87 'W'
0x6363361c1c366300 ; 88 'X'
0x3333331e0c0c1e00 ; 89 'Y'
0x7f6331184c667f00 ; 90 'Z'
0x1e06060606061e00 ; 91 '['
0x03060c1830604000 ; 92 '\'
0x1e18181818181e00 ; 93 ']'
0x081c366300000000 ; 94 '^'
0x00000000000000ff ; 95 '_'
0x0c0c180000000000 ; 96 '`'
0x00001e303e336e00 ; 97 'a'
0x0706063e66663b00 ; 98 'b'
0x00001e3303331e00 ; 99 'c'
0x3830303e33336e00 ; 100 'd'
0x00001e333f031e00 ; 101 'e'
0x1c36060f06060f00 ; 102 'f'
0x00006e33333e301f ; 103 'g'
0x0706366e66666700 ; 104 'h'
0x0c000e0c0c0c1e00 ; 105 'i'
0x300030303033331e ; 106 'j'
0x070666361e366700 ; 107 'k'
0x0e0c0c0c0c0c1e00 ; 108 'l'
0x0000337f7f6b6300 ; 109 'm'
0x00001f3333333300 ; 110 'n'
0x00001e3333331e00 ; 111 'o'
0x00003b66663e060f ; 112 'p'
0x00006e33333e3078 ; 113 'q'
0x00003b6e66060f00 ; 114 'r'
0x00003e031e301f00 ; 115 's'
0x080c3e0c0c2c1800 ; 116 't'
0x0000333333336e00 ; 117 'u'
0x00003333331e0c00 ; 118 'v'
0x0000636b7f7f3600 ; 119 'w'
0x000063361c366300 ; 120 'x'
0x00003333333e301f ; 121 'y'
0x00003f190c263f00 ; 122 'z'
0x380c0c070c0c3800 ; 123 '{'
0x1818180018181800 ; 124 '|'
0x070c0c380c0c0700 ; 125 '}'
0x6e3b000000000000 ; 126 '~'
0x00081c3663637f00 ; 127
0x1e3303331e18301e ; 128
0x0033003333337e00 ; 129
0x38001e333f031e00 ; 130
0x7ec33c607c66fc00 ; 131
0x33001e303e337e00 ; 132
0x07001e303e337e00 ; 133
0x0c0c1e303e337e00 ; 134
0x00001e03031e301c ; 135
0x7ec33c667e063c00 ; 136
0x33001e333f031e00 ; 137
0x07001e333f031e00 ; 138
0x33000e0c0c0c1e00 ; 139
0x3e631c1818183c00 ; 140
0x07000e0c0c0c1e00 ; 141
0x631c36637f636300 ; 142
0x0c0c001e333f3300 ; 143
0x38003f061e063f00 ; 144
0x0000fe30fe33fe00 ; 145
0x7c36337f33337300 ; 146
0x1e33001e33331e00 ; 147
0x0033001e33331e00 ; 148
0x0007001e33331e00 ; 149
0x1e33003333337e00 ; 150
0x0007003333337e00 ; 151
0x00330033333e301f ; 152
0xc3183c66663c1800 ; 153
0x3300333333331e00 ; 154
0x18187e03037e1818 ; 155
0x1c36260f06673f00 ; 156
0x33331e3f0c3f0c0c ; 157
0x1f33335f63f363e3 ; 158
0x70d8183c18181b0e ; 159
0x38001e303e337e00 ; 160
0x1c000e0c0c0c1e00 ; 161
0x0038001e33331e00 ; 162
0x0038003333337e00 ; 163
0x001f001f33333300 ; 164
0x3f0033373f3b3300 ; 165
0x3c36367c007e0000 ; 166
0x1c36361c003e0000 ; 167
0x0c000c0603331e00 ; 168
0x0000003f03030000 ; 169
0x0000003f30300000 ; 170
0xc363337bcc6633f0 ; 171
0xc36333dbecf6f3c0 ; 172
0x1818001818181800 ; 173
0x00cc663366cc0000 ; 174
0x003366cc66330000 ; 175
0x4411441144114411 ; 176
0xaa55aa55aa55aa55 ; 177
0xdbeedb77dbeedb77 ; 178
0x1818181818181818 ; 179
0x181818181f181818 ; 180
0x18181f181f181818 ; 181
0x6c6c6c6c6f6c6c6c ; 182
0x000000007f6c6c6c ; 183
0x00001f181f181818 ; 184
0x6c6c6f606f6c6c6c ; 185
0x6c6c6c6c6c6c6c6c ; 186
0x00007f606f6c6c6c ; 187
0x6c6c6f607f000000 ; 188
0x6c6c6c6c7f000000 ; 189
0x18181f181f000000 ; 190
0x000000001f181818 ; 191
0x18181818f8000000 ; 192
0x18181818ff000000 ; 193
0x00000000ff181818 ; 194
0x18181818f8181818 ; 195
0x00000000ff000000 ; 196
0x18181818ff181818 ; 197
0x1818f818f8181818 ; 198
0x6c6c6c6cec6c6c6c ; 199
0x6c6cec0cfc000000 ; 200
0x0000fc0cec6c6c6c ; 201
0x6c6cef00ff000000 ; 202
0x0000ff00ef6c6c6c ; 203
0x6c6cec0cec6c6c6c ; 204
0x0000ff00ff000000 ; 205
0x6c6cef00ef6c6c6c ; 206
0x1818ff00ff000000 ; 207
0x6c6c6c6cff000000 ; 208
0x0000ff00ff181818 ; 209
0x00000000ff6c6c6c ; 210
0x6c6c6c6cfc000000 ; 211
0x1818f818f8000000 ; 212
0x0000f818f8181818 ; 213
0x00000000fc6c6c6c ; 214
0x6c6c6c6cff6c6c6c ; 215
0x1818ff18ff181818 ; 216
0x181818181f000000 ; 217
0x00000000f8181818 ; 218
0xffffffffffffffff ; 219
0x00000000ffffffff ; 220
0x0f0f0f0f0f0f0f0f ; 221
0xf0f0f0f0f0f0f0f0 ; 222
0xffffffff00000000 ; 223
0x00006e3b133b6e00 ; 224
0x001e331f331f0303 ; 225
0x003f330303030300 ; 226
0x007f363636363600 ; 227
0x3f33060c06333f00 ; 228
0x00007e1b1b1b0e00 ; 229
0x00666666663e0603 ; 230
0x006e3b1818181800 ; 231
0x3f0c1e33331e0c3f ; 232
0x1c36637f63361c00 ; 233
0x1c36636336367700 ; 234
0x380c183e33331e00 ; 235
0x00007edbdb7e0000 ; 236
0x60307edbdb7e0603 ; 237
0x1c06031f03061c00 ; 238
0x1e33333333333300 ; 239
0x003f003f003f0000 ; 240
0x0c0c3f0c0c003f00 ; 241
0x060c180c06003f00 ; 242
0x180c060c18003f00 ; 243
0x70d8d81818181818 ; 244
0x18181818181b1b0e ; 245
0x0c0c003f000c0c00 ; 246
0x006e3b006e3b0000 ; 247
0x1c36361c00000000 ; 248
0x0000001818000000 ; 249
0x0000000018000000 ; 250
0xf030303037363c38 ; 251
0x1e36363636000000 ; 252
0x0e180c061e000000 ; 253
0x00003c3c3c3c0000 ; 254
0x0000000000000000 ; 255
))

@ -0,0 +1,174 @@
#define psf_width 256
#define psf_height 64
static unsigned char psf_bits[] = {
0x00, 0x7e, 0x7e, 0x36, 0x08, 0x1c, 0x08, 0x00, 0xff, 0x00, 0xff, 0xf0,
0x3c, 0xfc, 0xfe, 0x99, 0x01, 0x40, 0x18, 0x66, 0xfe, 0x7c, 0x00, 0x18,
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xff, 0x7f,
0x1c, 0x3e, 0x08, 0x00, 0xff, 0x3c, 0xc3, 0xe0, 0x66, 0xcc, 0xc6, 0x5a,
0x07, 0x70, 0x3c, 0x66, 0xdb, 0xc6, 0x00, 0x3c, 0x3c, 0x18, 0x18, 0x0c,
0x00, 0x24, 0x18, 0xff, 0x00, 0xa5, 0xdb, 0x7f, 0x3e, 0x1c, 0x1c, 0x18,
0xe7, 0x66, 0x99, 0xf0, 0x66, 0xfc, 0xfe, 0x3c, 0x1f, 0x7c, 0x7e, 0x66,
0xdb, 0x1c, 0x00, 0x7e, 0x7e, 0x18, 0x30, 0x06, 0x03, 0x66, 0x3c, 0xff,
0x00, 0x81, 0xff, 0x7f, 0x7f, 0x7f, 0x3e, 0x3c, 0xc3, 0x42, 0xbd, 0xbe,
0x66, 0x0c, 0xc6, 0xe7, 0x7f, 0x7f, 0x18, 0x66, 0xde, 0x36, 0x00, 0x18,
0x18, 0x18, 0x7f, 0x7f, 0x03, 0xff, 0x7e, 0x7e, 0x00, 0xbd, 0xc3, 0x3e,
0x3e, 0x7f, 0x7f, 0x3c, 0xc3, 0x42, 0xbd, 0x33, 0x3c, 0x0c, 0xc6, 0xe7,
0x1f, 0x7c, 0x18, 0x66, 0xd8, 0x36, 0x7e, 0x7e, 0x18, 0x7e, 0x30, 0x06,
0x03, 0x66, 0xff, 0x3c, 0x00, 0x99, 0xe7, 0x1c, 0x1c, 0x3e, 0x3e, 0x18,
0xe7, 0x66, 0x99, 0x33, 0x18, 0x0e, 0xe6, 0x3c, 0x07, 0x70, 0x7e, 0x00,
0xd8, 0x1c, 0x7e, 0x3c, 0x18, 0x3c, 0x18, 0x0c, 0x7f, 0x24, 0xff, 0x18,
0x00, 0x81, 0xff, 0x08, 0x08, 0x1c, 0x1c, 0x00, 0xff, 0x3c, 0xc3, 0x33,
0x7e, 0x0f, 0x67, 0x5a, 0x01, 0x40, 0x3c, 0x66, 0xd8, 0x33, 0x7e, 0x18,
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x00,
0x00, 0x3e, 0x3e, 0x00, 0xff, 0x00, 0xff, 0x1e, 0x18, 0x07, 0x03, 0x99,
0x00, 0x00, 0x18, 0x00, 0x00, 0x1e, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x36, 0x36, 0x0c, 0x00, 0x1c, 0x06,
0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x3e, 0x0c, 0x1e, 0x1e,
0x38, 0x3f, 0x1c, 0x3f, 0x1e, 0x1e, 0x00, 0x00, 0x18, 0x00, 0x06, 0x1e,
0x00, 0x1e, 0x36, 0x36, 0x3e, 0x63, 0x36, 0x06, 0x0c, 0x0c, 0x66, 0x0c,
0x00, 0x00, 0x00, 0x30, 0x63, 0x0e, 0x33, 0x33, 0x3c, 0x03, 0x06, 0x33,
0x33, 0x33, 0x0c, 0x0c, 0x0c, 0x00, 0x0c, 0x33, 0x00, 0x1e, 0x36, 0x7f,
0x03, 0x33, 0x1c, 0x03, 0x06, 0x18, 0x3c, 0x0c, 0x00, 0x00, 0x00, 0x18,
0x73, 0x0c, 0x30, 0x30, 0x36, 0x1f, 0x03, 0x30, 0x33, 0x33, 0x0c, 0x0c,
0x06, 0x3f, 0x18, 0x30, 0x00, 0x0c, 0x00, 0x36, 0x1e, 0x18, 0x6e, 0x00,
0x06, 0x18, 0xff, 0x3f, 0x00, 0x3f, 0x00, 0x0c, 0x7b, 0x0c, 0x1c, 0x1c,
0x33, 0x30, 0x1f, 0x18, 0x1e, 0x3e, 0x00, 0x00, 0x03, 0x00, 0x30, 0x18,
0x00, 0x0c, 0x00, 0x7f, 0x30, 0x0c, 0x3b, 0x00, 0x06, 0x18, 0x3c, 0x0c,
0x00, 0x00, 0x00, 0x06, 0x6f, 0x0c, 0x06, 0x30, 0x7f, 0x30, 0x33, 0x0c,
0x33, 0x30, 0x00, 0x00, 0x06, 0x00, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x36,
0x1f, 0x66, 0x33, 0x00, 0x0c, 0x0c, 0x66, 0x0c, 0x0c, 0x00, 0x0c, 0x03,
0x67, 0x0c, 0x33, 0x33, 0x30, 0x33, 0x33, 0x0c, 0x33, 0x18, 0x0c, 0x0c,
0x0c, 0x3f, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x36, 0x0c, 0x63, 0x6e, 0x00,
0x18, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x01, 0x3e, 0x3f, 0x3f, 0x1e,
0x78, 0x1e, 0x1e, 0x0c, 0x1e, 0x0e, 0x0c, 0x0c, 0x18, 0x00, 0x06, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x0c, 0x3f, 0x3c,
0x1f, 0x7f, 0x7f, 0x3c, 0x33, 0x1e, 0x78, 0x67, 0x0f, 0x63, 0x63, 0x1c,
0x3f, 0x1e, 0x3f, 0x1e, 0x3f, 0x33, 0x33, 0x63, 0x63, 0x33, 0x7f, 0x1e,
0x03, 0x1e, 0x08, 0x00, 0x63, 0x1e, 0x66, 0x66, 0x36, 0x46, 0x46, 0x66,
0x33, 0x0c, 0x30, 0x66, 0x06, 0x77, 0x67, 0x36, 0x66, 0x33, 0x66, 0x33,
0x2d, 0x33, 0x33, 0x63, 0x63, 0x33, 0x63, 0x06, 0x06, 0x18, 0x1c, 0x00,
0x7b, 0x33, 0x66, 0x03, 0x66, 0x16, 0x16, 0x03, 0x33, 0x0c, 0x30, 0x36,
0x06, 0x7f, 0x6f, 0x63, 0x66, 0x33, 0x66, 0x07, 0x0c, 0x33, 0x33, 0x63,
0x36, 0x33, 0x31, 0x06, 0x0c, 0x18, 0x36, 0x00, 0x7b, 0x33, 0x3e, 0x03,
0x66, 0x1e, 0x1e, 0x03, 0x3f, 0x0c, 0x30, 0x1e, 0x06, 0x7f, 0x7b, 0x63,
0x3e, 0x33, 0x3e, 0x0e, 0x0c, 0x33, 0x33, 0x6b, 0x1c, 0x1e, 0x18, 0x06,
0x18, 0x18, 0x63, 0x00, 0x7b, 0x3f, 0x66, 0x03, 0x66, 0x16, 0x16, 0x73,
0x33, 0x0c, 0x33, 0x36, 0x46, 0x6b, 0x73, 0x63, 0x06, 0x3b, 0x36, 0x38,
0x0c, 0x33, 0x33, 0x7f, 0x1c, 0x0c, 0x4c, 0x06, 0x30, 0x18, 0x00, 0x00,
0x03, 0x33, 0x66, 0x66, 0x36, 0x46, 0x06, 0x66, 0x33, 0x0c, 0x33, 0x66,
0x66, 0x63, 0x63, 0x36, 0x06, 0x1e, 0x66, 0x33, 0x0c, 0x33, 0x1e, 0x77,
0x36, 0x0c, 0x66, 0x06, 0x60, 0x18, 0x00, 0x00, 0x1e, 0x33, 0x3f, 0x3c,
0x1f, 0x7f, 0x0f, 0x7c, 0x33, 0x1e, 0x1e, 0x67, 0x7f, 0x63, 0x63, 0x1c,
0x0f, 0x38, 0x67, 0x1e, 0x1e, 0x3f, 0x0c, 0x63, 0x63, 0x1e, 0x7f, 0x1e,
0x40, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x0c, 0x00, 0x07, 0x00, 0x38, 0x00, 0x1c, 0x00, 0x07, 0x0c, 0x30, 0x07,
0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x38, 0x18, 0x07, 0x6e, 0x00, 0x0c, 0x00, 0x06, 0x00,
0x30, 0x00, 0x36, 0x00, 0x06, 0x00, 0x00, 0x06, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x18, 0x0c, 0x3b, 0x08, 0x18, 0x1e, 0x06, 0x1e, 0x30, 0x1e, 0x06, 0x6e,
0x36, 0x0e, 0x30, 0x66, 0x0c, 0x33, 0x1f, 0x1e, 0x3b, 0x6e, 0x3b, 0x3e,
0x3e, 0x33, 0x33, 0x63, 0x63, 0x33, 0x3f, 0x0c, 0x18, 0x0c, 0x00, 0x1c,
0x00, 0x30, 0x3e, 0x33, 0x3e, 0x33, 0x0f, 0x33, 0x6e, 0x0c, 0x30, 0x36,
0x0c, 0x7f, 0x33, 0x33, 0x66, 0x33, 0x6e, 0x03, 0x0c, 0x33, 0x33, 0x6b,
0x36, 0x33, 0x19, 0x07, 0x00, 0x38, 0x00, 0x36, 0x00, 0x3e, 0x66, 0x03,
0x33, 0x3f, 0x06, 0x33, 0x66, 0x0c, 0x30, 0x1e, 0x0c, 0x7f, 0x33, 0x33,
0x66, 0x33, 0x66, 0x1e, 0x0c, 0x33, 0x33, 0x7f, 0x1c, 0x33, 0x0c, 0x0c,
0x18, 0x0c, 0x00, 0x63, 0x00, 0x33, 0x66, 0x33, 0x33, 0x03, 0x06, 0x3e,
0x66, 0x0c, 0x33, 0x36, 0x0c, 0x6b, 0x33, 0x33, 0x3e, 0x3e, 0x06, 0x30,
0x2c, 0x33, 0x1e, 0x7f, 0x36, 0x3e, 0x26, 0x0c, 0x18, 0x0c, 0x00, 0x63,
0x00, 0x6e, 0x3b, 0x1e, 0x6e, 0x1e, 0x0f, 0x30, 0x67, 0x1e, 0x33, 0x67,
0x1e, 0x63, 0x33, 0x1e, 0x06, 0x30, 0x0f, 0x1f, 0x18, 0x6e, 0x0c, 0x36,
0x63, 0x30, 0x3f, 0x38, 0x18, 0x07, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x38, 0x7e, 0x33, 0x07, 0x0c, 0x00,
0x7e, 0x33, 0x07, 0x33, 0x3e, 0x07, 0x63, 0x0c, 0x38, 0x00, 0x7c, 0x1e,
0x00, 0x00, 0x1e, 0x00, 0x00, 0xc3, 0x33, 0x18, 0x1c, 0x33, 0x1f, 0x70,
0x33, 0x33, 0x00, 0xc3, 0x00, 0x00, 0x0c, 0x00, 0xc3, 0x00, 0x00, 0x00,
0x63, 0x00, 0x1c, 0x0c, 0x00, 0x00, 0x36, 0x33, 0x33, 0x07, 0x33, 0x07,
0x33, 0x18, 0x00, 0x18, 0x36, 0x33, 0x33, 0xd8, 0x03, 0x00, 0x1e, 0x3c,
0x1e, 0x1e, 0x1e, 0x1e, 0x3c, 0x1e, 0x1e, 0x0e, 0x1c, 0x0e, 0x36, 0x00,
0x3f, 0xfe, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x33, 0x7e,
0x26, 0x1e, 0x33, 0x18, 0x33, 0x33, 0x33, 0x60, 0x30, 0x30, 0x30, 0x03,
0x66, 0x33, 0x33, 0x0c, 0x18, 0x0c, 0x63, 0x1e, 0x06, 0x30, 0x7f, 0x1e,
0x1e, 0x1e, 0x33, 0x33, 0x33, 0x66, 0x33, 0x03, 0x0f, 0x3f, 0x5f, 0x3c,
0x1e, 0x33, 0x3f, 0x7c, 0x3e, 0x3e, 0x3e, 0x03, 0x7e, 0x3f, 0x3f, 0x0c,
0x18, 0x0c, 0x7f, 0x33, 0x1e, 0xfe, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
0x33, 0x66, 0x33, 0x03, 0x06, 0x0c, 0x63, 0x18, 0x18, 0x33, 0x03, 0x66,
0x33, 0x33, 0x33, 0x1e, 0x06, 0x03, 0x03, 0x0c, 0x18, 0x0c, 0x63, 0x3f,
0x06, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3e, 0x3c, 0x33, 0x7e,
0x67, 0x3f, 0xf3, 0x18, 0x30, 0x7e, 0x1e, 0xfc, 0x7e, 0x7e, 0x7e, 0x30,
0x3c, 0x1e, 0x1e, 0x1e, 0x3c, 0x1e, 0x63, 0x33, 0x3f, 0xfe, 0x73, 0x1e,
0x1e, 0x1e, 0x7e, 0x7e, 0x30, 0x18, 0x1e, 0x18, 0x3f, 0x0c, 0x63, 0x1b,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1f, 0x00, 0x00, 0x18, 0x00, 0x0c, 0xe3, 0x0e, 0x38, 0x1c, 0x00, 0x00,
0x00, 0x3f, 0x3c, 0x1c, 0x0c, 0x00, 0x00, 0xc3, 0xc3, 0x18, 0x00, 0x00,
0x44, 0xaa, 0xdb, 0x18, 0x18, 0x18, 0x6c, 0x00, 0x00, 0x6c, 0x6c, 0x00,
0x6c, 0x6c, 0x18, 0x00, 0x00, 0x00, 0x38, 0x38, 0x1f, 0x00, 0x36, 0x36,
0x00, 0x00, 0x00, 0x63, 0x63, 0x18, 0xcc, 0x33, 0x11, 0x55, 0xee, 0x18,
0x18, 0x18, 0x6c, 0x00, 0x00, 0x6c, 0x6c, 0x00, 0x6c, 0x6c, 0x18, 0x00,
0x1e, 0x0e, 0x00, 0x00, 0x00, 0x33, 0x36, 0x36, 0x0c, 0x00, 0x00, 0x33,
0x33, 0x00, 0x66, 0x66, 0x44, 0xaa, 0xdb, 0x18, 0x18, 0x1f, 0x6c, 0x00,
0x1f, 0x6f, 0x6c, 0x7f, 0x6f, 0x6c, 0x1f, 0x00, 0x30, 0x0c, 0x1e, 0x33,
0x1f, 0x37, 0x7c, 0x1c, 0x06, 0x3f, 0x3f, 0x7b, 0xdb, 0x18, 0x33, 0xcc,
0x11, 0x55, 0x77, 0x18, 0x18, 0x18, 0x6c, 0x00, 0x18, 0x60, 0x6c, 0x60,
0x60, 0x6c, 0x18, 0x00, 0x3e, 0x0c, 0x33, 0x33, 0x33, 0x3f, 0x00, 0x00,
0x03, 0x03, 0x30, 0xcc, 0xec, 0x18, 0x66, 0x66, 0x44, 0xaa, 0xdb, 0x18,
0x1f, 0x1f, 0x6f, 0x7f, 0x1f, 0x6f, 0x6c, 0x6f, 0x7f, 0x7f, 0x1f, 0x1f,
0x33, 0x0c, 0x33, 0x33, 0x33, 0x3b, 0x7e, 0x3e, 0x33, 0x03, 0x30, 0x66,
0xf6, 0x18, 0xcc, 0x33, 0x11, 0x55, 0xee, 0x18, 0x18, 0x18, 0x6c, 0x6c,
0x18, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x18, 0x7e, 0x1e, 0x1e, 0x7e,
0x33, 0x33, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x33, 0xf3, 0x18, 0x00, 0x00,
0x44, 0xaa, 0xdb, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x18, 0x6c, 0x6c, 0x6c,
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x11, 0x55, 0x77, 0x18,
0x18, 0x18, 0x6c, 0x6c, 0x18, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x18,
0x18, 0x18, 0x00, 0x18, 0x00, 0x18, 0x18, 0x6c, 0x6c, 0x00, 0x6c, 0x00,
0x6c, 0x00, 0x6c, 0x18, 0x6c, 0x00, 0x00, 0x6c, 0x18, 0x00, 0x00, 0x6c,
0x18, 0x18, 0x00, 0xff, 0x00, 0x0f, 0xf0, 0xff, 0x18, 0x18, 0x00, 0x18,
0x00, 0x18, 0x18, 0x6c, 0x6c, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6c, 0x18,
0x6c, 0x00, 0x00, 0x6c, 0x18, 0x00, 0x00, 0x6c, 0x18, 0x18, 0x00, 0xff,
0x00, 0x0f, 0xf0, 0xff, 0x18, 0x18, 0x00, 0x18, 0x00, 0x18, 0xf8, 0x6c,
0xec, 0xfc, 0xef, 0xff, 0xec, 0xff, 0xef, 0xff, 0x6c, 0xff, 0x00, 0x6c,
0xf8, 0xf8, 0x00, 0x6c, 0xff, 0x18, 0x00, 0xff, 0x00, 0x0f, 0xf0, 0xff,
0x18, 0x18, 0x00, 0x18, 0x00, 0x18, 0x18, 0x6c, 0x0c, 0x0c, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x6c, 0x18, 0x18, 0x00, 0x6c,
0x18, 0x18, 0x00, 0xff, 0x00, 0x0f, 0xf0, 0xff, 0xf8, 0xff, 0xff, 0xf8,
0xff, 0xff, 0xf8, 0xec, 0xfc, 0xec, 0xff, 0xef, 0xec, 0xff, 0xef, 0xff,
0xff, 0xff, 0xff, 0xfc, 0xf8, 0xf8, 0xfc, 0xff, 0xff, 0x1f, 0xf8, 0xff,
0xff, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x6c,
0x00, 0x6c, 0x00, 0x6c, 0x6c, 0x00, 0x6c, 0x00, 0x00, 0x18, 0x6c, 0x00,
0x00, 0x18, 0x6c, 0x6c, 0x18, 0x00, 0x18, 0xff, 0xff, 0x0f, 0xf0, 0x00,
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x6c, 0x00, 0x6c, 0x00, 0x6c,
0x6c, 0x00, 0x6c, 0x00, 0x00, 0x18, 0x6c, 0x00, 0x00, 0x18, 0x6c, 0x6c,
0x18, 0x00, 0x18, 0xff, 0xff, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x18, 0x18,
0x00, 0x18, 0x18, 0x6c, 0x00, 0x6c, 0x00, 0x6c, 0x6c, 0x00, 0x6c, 0x00,
0x00, 0x18, 0x6c, 0x00, 0x00, 0x18, 0x6c, 0x6c, 0x18, 0x00, 0x18, 0xff,
0xff, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x3f, 0x1c, 0x1c, 0x38, 0x00, 0x60, 0x1c, 0x1e, 0x00, 0x0c, 0x06, 0x18,
0x70, 0x18, 0x0c, 0x00, 0x1c, 0x00, 0x00, 0xf0, 0x1e, 0x0e, 0x00, 0x00,
0x00, 0x1e, 0x3f, 0x7f, 0x33, 0x00, 0x66, 0x6e, 0x0c, 0x36, 0x36, 0x0c,
0x00, 0x30, 0x06, 0x33, 0x3f, 0x0c, 0x0c, 0x0c, 0xd8, 0x18, 0x0c, 0x6e,
0x36, 0x00, 0x00, 0x30, 0x36, 0x18, 0x00, 0x00, 0x6e, 0x33, 0x33, 0x36,
0x06, 0x7e, 0x66, 0x3b, 0x1e, 0x63, 0x63, 0x18, 0x7e, 0x7e, 0x03, 0x33,
0x00, 0x3f, 0x18, 0x06, 0xd8, 0x18, 0x00, 0x3b, 0x36, 0x00, 0x00, 0x30,
0x36, 0x0c, 0x3c, 0x00, 0x3b, 0x1f, 0x03, 0x36, 0x0c, 0x1b, 0x66, 0x18,
0x33, 0x7f, 0x63, 0x3e, 0xdb, 0xdb, 0x1f, 0x33, 0x3f, 0x0c, 0x0c, 0x0c,
0x18, 0x18, 0x3f, 0x00, 0x1c, 0x18, 0x00, 0x30, 0x36, 0x06, 0x3c, 0x00,
0x13, 0x33, 0x03, 0x36, 0x06, 0x1b, 0x66, 0x18, 0x33, 0x63, 0x36, 0x33,
0xdb, 0xdb, 0x03, 0x33, 0x00, 0x0c, 0x06, 0x18, 0x18, 0x18, 0x00, 0x6e,
0x00, 0x18, 0x18, 0x37, 0x36, 0x1e, 0x3c, 0x00, 0x3b, 0x1f, 0x03, 0x36,
0x33, 0x1b, 0x3e, 0x18, 0x1e, 0x36, 0x36, 0x33, 0x7e, 0x7e, 0x06, 0x33,
0x3f, 0x00, 0x00, 0x00, 0x18, 0x1b, 0x0c, 0x3b, 0x00, 0x00, 0x00, 0x36,
0x00, 0x00, 0x3c, 0x00, 0x6e, 0x03, 0x03, 0x36, 0x3f, 0x0e, 0x06, 0x18,
0x0c, 0x1c, 0x77, 0x1e, 0x00, 0x06, 0x1c, 0x33, 0x00, 0x3f, 0x3f, 0x3f,
0x18, 0x1b, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00};

@ -0,0 +1,18 @@
(
(include other)
(def BAR (=add FOO 1000))
(cmp BAR 1123 (ne? (fault)))
(include utils/itoa.csn)
(include "utils/printnum")
(mkbf r0)
(call itoa r0 BAR)
(lds @cout @r0)
(del @r0)
(ld @cout '\n')
(call printnum BAR)
(ld @cout '\n')
)

@ -0,0 +1,3 @@
(
(def FOO 123)
)

@ -0,0 +1,14 @@
(
(proc itoa buf num
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @buf r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @buf '-')))
(ret)))
(j :next)
)
)

@ -0,0 +1,17 @@
(
(proc printnum num
(mkbf r15)
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @r15 r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @r15 '-')))
(lds @cout @r15)
(del @r15)
(ret)))
(j :next)
)
)

@ -1,24 +1,41 @@
(
; This is an example implementation of itoa using a buffer of characters
(ld r0 -123456789)
(mkbf r1)
(call itoa r1 r0)
; print it
(:pn) (bfrpop @cout @r1 (em? (ld @cout '\n') (halt))) (j :pn)
(halt)
(proc itoa buf num
(proc itoa buf num
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @buf r0)
(div r1 10 (z?
(div r1 10 (z?
(tst num (<0? (bfrpush @buf '-')))
(ret)))
(j :next)
)
; other version that prints it
(proc printnum num
(mkbf r15)
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @r15 r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @r15 '-')))
(lds @cout @r15)
(del @r15)
(ret)))
(j :next)
)
)

@ -0,0 +1,236 @@
(
; simple GOL with screen and a buffer + DRAWING!!!
; Middle-click to payse or resume
; (hold the button until paused - there is a sleep between generations and checking buttons)
;
; When paused, draw with left (white) and right (black) mouse buttons
;
; Resume by clicking middle again.
(def GENERATION_MS 200)
; Number of pixels
(def W 40)
(def H 40)
(def UPSCALE 10)
; --- end of config ---
; Real pixel size
(sc-init (=mul UPSCALE W) (=mul UPSCALE H))
; Upscaling factor (bug pixels)
(sc-opt SCREEN_UPSCALE UPSCALE)
(def XMAX (=sub W 1))
(def YMAX (=sub H 1))
(def NCELLS (=mul W H))
(sc-opt SCREEN_AUTO_BLIT 0)
(sc-erase 0) ; all black
(sym ng g15)
(mkbf ng NCELLS)
; one glider
(bfwr @ng 1 1)
(bfwr @ng 42 1)
(bfwr @ng 80 1)
(bfwr @ng 81 1)
(bfwr @ng 82 1)
; another glider
(bfwr @ng 16 1)
(bfwr @ng 55 1)
(bfwr @ng 95 1)
(bfwr @ng 96 1)
(bfwr @ng 97 1)
(:loop)
(sc-poll)
; Drawing
(sc-mbtn _ MBTN_MID
(nz?
(:release)
(sc-poll)
(mslp 10)
(sc-mbtn _ MBTN_MID (nz? (j :release)))
(:mousing)
(sc-poll)
(mslp 10)
(ld r3 -1)
(sc-mbtn _ MBTN_LEFT)
(ld.nz r3 1)
(sc-mbtn _ MBTN_RIGHT)
(ld.nz r3 0)
(tst r3 (nneg?
(sc-mouse r0 r1)
(tst r3)
(sc-wr.nz r0 r1 #ffffff)
(sc-wr.z r0 r1 #000000)
(sc-blit)
(mul r1 W)
(add r0 r1)
(ld8 r3:8 r3)
(bfwr @ng r0 r3)
))
(sc-mbtn _ MBTN_MID
(z? (j :mousing)))
(:release2)
(sc-poll)
(mslp 10)
(sc-mbtn _ MBTN_MID (nz? (j :release2)))
)
)
(call Display)
(sc-blit)
(mslp GENERATION_MS)
(j :loop)
(proc CountNeighbors x y
(sym xx r4)
(sym yy r5)
(sym i r6)
(sym count r7)
; yeah this sucks. it's correct, though
(:a)
(add yy y -1 (neg? (j :d)))
(add xx x -1 (neg? (j :b)))
(mul i yy W)
(add i xx)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:b)
(mul i yy W)
(add i x)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:c)
(add xx x 1)
(cmp xx W (ge? (j :d)))
(mul i yy W)
(add i xx)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:d)
(add xx x -1 (neg? (j :f)))
(mul i y W)
(add i xx)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
; there is no E
(:f)
(add xx x 1)
(cmp xx W (ge? (j :g)))
(mul i y W)
(add i xx)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:g)
(add yy y 1)
(cmp yy H (ge? (j :end)))
(add xx x -1 (neg? (j :h)))
(mul i yy W)
(add i xx)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:h)
(add yy y 1)
(cmp yy H (ge? (j :end)))
(mul i yy W)
(add i x)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:i)
(add yy y 1)
(cmp yy H (ge? (j :end)))
(add xx x 1)
(cmp xx W (ge? (j :end)))
(mul i yy W)
(add i xx)
(bfrd r0 @ng i)
(and r0 0xFF (nz? (inc count)))
(:end)
(ret count)
)
(proc Display
; display and calc next gen
(sym x r4)
(sym y r5)
(sym i r6)
(:next)
; The lower byte contains 0 or 1
; the second byte will be filled with the next gen
(bfrd r0 @ng i)
; Show this gen
(and r0 0xFF
(nz? (sc-wr x y 0xffffff))
(z? (sc-wr x y 0x000000)))
(call CountNeighbors x y)
; stay: 2,3
; die: >3
; born: 3
(cmp res0 2
(eq? (ld8 r0:8 r0)) ; stasis
(ne?
(tst r0
(z?
(cmp res0 3 (eq? (ld8 r0:8 1))) ; birth
)
(nz?
(cmp res0 3 (eq? (ld8 r0:8 1))) ; stay alive
)
)
)
)
(bfwr @ng i r0)
(inc i)
(inc x)
(cmp x W
(ne? (j :next)))
(ld x 0)
(inc y)
(cmp y H
(eq? (j :next2)))
(j :next)
; Shift all by 8 to the right (generation shift)
(:next2)
(dec i)
(bfrd r0 @ng i)
(lsr r0 8)
(bfwr @ng i r0)
(tst i)
(j.nz :next2)
(ret)
)
)

@ -0,0 +1,43 @@
(
; Unlabeled loop. Can be exited by jumping out
(loop
(nop)
(nop)
(nop)
)
(barrier)
; The above is equivalent to:
(:label)
(nop)
(nop)
(nop)
(j :label)
(barrier)
; The label name can be specified.
; This adds a start label and ":label-end" at the end of the loop:
(loop :repeat
(nop)
(nop)
(j :repeat-end)
(nop)
)
(barrier)
; The above is equivalent to: (labels changed to avoid a compile error)
(:repeat2)
(nop)
(nop)
(j :repeat2-end)
(nop)
(j :repeat2)
(:repeat2-end)
)

@ -0,0 +1,87 @@
(
(def W 128)
(def H 48)
(def MAXITER 50)
(sym asciigr r10)
(mkbf asciigr (
'.' ',' ':' '+' '=' '%' '@' '#'
'$' '&' '*' '|' '-' ':' '.' ' '
))
(sym x r7)
(sym y r8)
(:row)
(:col)
(call pixel x y)
(sub r0 MAXITER 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd @cout @asciigr r0)
)
(else?
(ld @cout ' ')
)
)
(sc-wr x y r0)
(inc x)
(cmp x W)
(j.ne :col)
(ld x 0)
(inc y)
(ld @cout '\n')
(cmp y H)
(j.ne :row)
(halt)
(proc pixel xi yi
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter MAXITER)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
)

@ -0,0 +1,95 @@
(
; High resolution mandelbrot
(def W 800)
(def H 600)
(def MAXITER 50) ; Increase for more detail but slower render
(sc-init W H)
(sc-opt SCREEN_AUTOBLIT 0)
(sym gradient r9)
(sym asciigr r10)
(mkbf gradient (
0x421e0f 0x19071a 0x09012f 0x040449 0x000764 0x0c2c8a 0x1852b1 0x397dd1
0x86b5e5 0xd3ecf8 0xf1e9bf 0xf8c95f 0xffaa00 0xcc8000 0x995700 0x6a3403))
(sym x r7)
(sym y r8)
(:row)
(:col)
(call pixel x y)
(sub r0 MAXITER 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd r0 @gradient r0)
)
(else? (ld r0 0))
)
(sc-wr x y r0)
(inc x)
(cmp x W)
(j.ne :col)
(ld x 0)
(inc y)
(sc-blit) ; Render after every row
(cmp y H)
(j.ne :row)
;(sc-blit)
(:slp)
(sc-poll)
(mslp 10)
(j :slp)
(proc pixel xi yi
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range x -2.5..1 and y -1..1
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter MAXITER)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
)

@ -0,0 +1,305 @@
(
(def W 1024)
(def H 768)
(def DEF_MAXITER 50)
; ---
(sc-init W H)
(sc-opt SCREEN_AUTOBLIT 0)
(sym maxiter g0)
(ld maxiter DEF_MAXITER)
(lds @cout "Interactive Mandelbrot\n")
(lds @cout "----------------------\n")
(lds @cout "Navigate using WASD, zoom: Q+/E-, detail: R+/F-; fast move/zoom: LShift, force redraw: G\n")
(lds @cout "To get a high-res image, stop interacting for while\n")
(sym mb_x r7)
(sym mb_y r8)
(sym mb_s r9)
(sym mb_row r10)
; index into the skip/size table
(sym mb_skip_index r11)
; Interactive movement speed
(def ZOOM_STEP 0.1)
(def PAN_STEP 0.3)
; size of big pixels
(def COL_SKIP 8)
(def ROW_SKIP 8)
; incrementally renders 1x1, 2x2, 4x4, ...
(def MB_SKIP_TABLE_SIZE 80)
(def MB_SKIP_REPEAT_INDEX 16)
(sym mb_skip_table_x r12)
(sym mb_skip_table_y r13)
(sym mb_size_table r14)
(mkbf mb_skip_table_x (
0 4 0 4 2 6 0 2 4 6 2 6 0 2 4 6
; 8x8 grid
1 3 5 7 1 3 5 7 1 3 5 7 1 3 5 7
0 2 4 6 0 2 4 6 0 2 4 6 0 2 4 6
1 3 5 7 1 3 5 7 1 3 5 7 1 3 5 7
; do top left pixels again
0 2 4 6 0 2 4 6 0 2 4 6 0 2 4 6
))
(mkbf mb_skip_table_y (
0 0 4 4 0 0 2 2 2 2 4 4 6 6 6 6
; 8x8 grid
0 0 0 0 2 2 2 2 4 4 4 4 6 6 6 6
1 1 1 1 3 3 3 3 5 5 5 5 7 7 7 7
1 1 1 1 3 3 3 3 5 5 5 5 7 7 7 7
; do top left pixels again
0 0 0 0 2 2 2 2 4 4 4 4 6 6 6 6
))
(mkbf mb_size_table (
8 4 4 4 2 2 2 2 2 2 2 2 2 2 2 2
; 8x8 grid
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
; do top left pixels again
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
))
(ld mb_x 0.0)
(ld mb_y 0.0)
(ld mb_s 1.0)
(sym did_change r15)
(sym is_first_render r6)
(ld is_first_render 1)
(sym GRADIENT g1)
(mkbf GRADIENT (
0x421e0f 0x19071a 0x09012f 0x040449 0x000764 0x0c2c8a 0x1852b1 0x397dd1
0x86b5e5 0xd3ecf8 0xf1e9bf 0xf8c95f 0xffaa00 0xcc8000 0x995700 0x6a3403))
; render row
; size - pixel size
; col - column offset
; px - offset x
; py - offset y
; scale - scaling
; row - row position
(proc render_row size col px py scale row
(sym x r7)
(sym y r8)
(add y row)
(ld x col)
(:col)
(call pixel x y px py scale)
(sub r0 maxiter 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd r0 @GRADIENT r0)
)
(else? (ld r0 0))
)
(sc-rect x y size size r0)
(add x COL_SKIP)
(cmp x W)
(j.lt :col)
(sc-blit)
(ret)
)
(:loop)
(sc-poll)
; did_change -- did the user interact during this frame?
(ld did_change 0)
(sym pstep r2)
(sym zstep r3)
(ld pstep PAN_STEP)
(ld zstep ZOOM_STEP)
(sc-key _ KEY_ShiftL (nz?
; turbo mode
(fmul pstep 5.0)
(fmul zstep 5.0)
))
(fadd zstep 1.0)
; scaled movement speed
(fdiv pstep mb_s)
(tst is_first_render (z?
; A <
(sc-key _ KEY_A (nz?
(fsub mb_x pstep)
(ld did_change 1)
(lds @cout "Pan left\n")
))
; W ^
(sc-key _ KEY_W (nz?
(fsub mb_y pstep)
(ld did_change 1)
(lds @cout "Pan up\n")
))
; S v
(sc-key _ KEY_S (nz?
(fadd mb_y pstep)
(ld did_change 1)
(lds @cout "Pan down\n")
))
; D >
(sc-key _ KEY_D (nz?
(fadd mb_x pstep)
(ld did_change 1)
(lds @cout "Pan right\n")
))
; Q +
(sc-key r0 KEY_Q (nz?
(fmul mb_s zstep)
(ld did_change 1)
(lds @cout "Zoom in\n")
))
; E -
(sc-key r0 KEY_E (nz?
(fdiv mb_s zstep)
(ld did_change 1)
(lds @cout "Zoom out\n")
))
; R iter+
(sc-key r0 KEY_R (nz?
(add maxiter 50)
(lds @cout "ITER=") (call printnum maxiter) (ld @cout '\n')
(mslp 200) ; Avoid unexpected rapid change
))
; F iter-
(sc-key r0 KEY_F (nz?
(cmp maxiter 50)
(sub.gt maxiter 50)
(lds @cout "ITER=") (call printnum maxiter) (ld @cout '\n')
(mslp 200) ; Avoid unexpected rapid change
))
; G force redraw
(sc-key r0 KEY_G (nz?
(ld did_change 1)
))
(tst did_change (nz?
; something changed...
; mark this as a first render and reset col_skip and row_skip
(ld is_first_render 1)
(ld mb_skip_index 0)
; Start from top
(ld mb_row 0)
))
))
(unsym pstep)
(unsym zstep)
(sym mb_col_skip r2)
(sym mb_row_skip r3)
(bfrd mb_col_skip @mb_skip_table_x mb_skip_index)
(bfrd mb_row_skip @mb_skip_table_y mb_skip_index)
(bfrd r1 @mb_size_table mb_skip_index)
; render row mb_row + row skip
(ld r0 mb_row)
(add r0 mb_row_skip)
(call render_row r1 mb_col_skip mb_x mb_y mb_s r0)
(unsym mb_col_skip)
(unsym mb_row_skip)
(cmp mb_row H
(lt?
; if mb_row < H
(add mb_row ROW_SKIP)
)
(else?
; otherwise, this frame is done
(ld mb_row 0)
(add mb_skip_index 1)
(ld is_first_render 0)
(cmp mb_skip_index MB_SKIP_TABLE_SIZE (ge?
; if skip index is out of bounds, go back to repeating area
(ld mb_skip_index MB_SKIP_REPEAT_INDEX)
))
)
)
(j :loop)
(proc pixel xi yi off_x off_y scale
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range x -2.5..1 and y -1..1
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(fdiv x0 scale)
(fdiv y0 scale)
(fadd x0 off_x)
(fadd y0 off_y)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter maxiter)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
(proc printnum num
(mkbf r15)
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @r15 r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @r15 '-')))
(lds @cout @r15)
(del @r15)
(ret)))
(j :next)
)
)

@ -0,0 +1,97 @@
(
(def PX_W 1024)
(def PX_H 768)
(def UPSCALE 8)
(def MAXITER 50)
(sc-init PX_W PX_H)
(sc-opt SCREEN_AUTOBLIT 0)
(sc-opt SCREEN_UPSCALE UPSCALE)
(def W (=div PX_W UPSCALE))
(def H (=div PX_H UPSCALE))
(sym gradient r9)
(sym asciigr r10)
(mkbf gradient (
0x421e0f 0x19071a 0x09012f 0x040449 0x000764 0x0c2c8a 0x1852b1 0x397dd1
0x86b5e5 0xd3ecf8 0xf1e9bf 0xf8c95f 0xffaa00 0xcc8000 0x995700 0x6a3403))
(sym x r7)
(sym y r8)
(:row)
(:col)
(call pixel x y)
(sub r0 MAXITER 1)
(rcmp res0 1 r0
(eq?
(mod r0 res0 16)
(bfrd r0 @gradient r0)
)
(else? (ld r0 0))
)
(sc-wr x y r0)
(inc x)
(cmp x W)
(j.ne :col)
(ld x 0)
(inc y)
(sc-blit) ; Render after every row
(cmp y H)
(j.ne :row)
;(sc-blit)
(:slp)
(sc-poll)
(mslp 10)
(j :slp)
(proc pixel xi yi
(sym x0 r7)
(sym y0 r8)
(itf x0 xi)
(itf y0 yi)
; Scale to the interesting range x -2.5..1 and y -1..1
(itf r0 W)
(itf r1 H)
(fdiv x0 r0)
(fmul x0 3.5)
(fsub x0 2.5)
(fdiv y0 r1)
(fmul y0 2.4)
(fsub y0 1.2)
(sym x r5)
(sym y r6)
(ld x 0.0)
(ld y 0.0)
(sym iter r4)
(:iter)
(cmp iter MAXITER)
(j.eq :end)
(fmul r0 x x)
(fmul r1 y y)
(fadd r2 r1)
(fcmp r2 4.0)
(j.gt :end)
(fsub r2 r0 r1)
(fadd r2 x0)
(fmul r0 x y)
(fmul r0 2.0)
(fadd r0 y0)
(ld y r0)
(ld x r2)
(inc iter)
(j :iter)
(:end)
(ret iter)
)
)

@ -0,0 +1,48 @@
::::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++==============+++++++++++++++++++=:::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++========%%%%%=@%%%%%++++++++========::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++=========%%@#.&$#%%%%%%%%=============:::::::::::::::::::
:::::::::::::::::::::::::::::::::::::===+++++++++++++++++++++++++++++==========%%%%##.-##@@@%%%===============::::::::::::::::::
::::::::::::::::::::::::::::::::::::=======+++++++++++++++++++++++++==========%%%%##$&::& ||#@%%===============:::::::::::::::::
:::::::::::::::::::::::::::::::::::===========+++++++++++++++++++%%%=========@@@@@##$**:.|&$@@@@================::::::::::::::::
::::::::::::::::::::::::::::::::::=================++++++++++%%%%%%%%%%%%%@@@@@@@$$&*& ::=&$##@@@%================::::::::::::::
::::::::::::::::::::::::::::::::=========================%%%%%%%%%%%%%%%%%@@@@##$:#$.#& :-:$$@@@%%%%%%%%%=========:::::::::::::
:::::::::::::::::::::::::::::::==========================%%%%%%%%%%%%%%%%@@@@@##$*:= :&###%%%%%%%%%%=========+:::::::::::
:::::::::::::::::::::::::::::============================%%%%%%%%%@%%%%####@$$$&&|. *.&$####@%@@@@%%=========++::::::::::
:::::::::::::::::::::::::::==============================%%%%%%%%@@##$$$:*$$&&**|-:$ -**&&$##@@@#|#%========+++++::::::::
:::::::::::::::::::::::::+==============================%%%%%%%%%@@@&|:&,:|*|# , | *..#.*$&$&&: #@@=====++++++++::::::
:::::::::::::::::::::::+++============================@@%%%%%%%%@@@$&|:@ =, * + --,%% :,#%%%%%=++++++++++::::
:::::::::::::::::::::++++++==============%%%========@@@@@@@%%%#####$$*. @-*#%%%%%%++++++++++++::
:::::::::::::::::::++++++++=============%%%%%%%%%@@@@@@@@@@@@###$$&* - : :,*$@@@%%%%++++++++++++++
::::::::::::::::++++++++++==============%%%@####@@@@@#@@@@@@####$*=: -&###@%%%%++++++++++++++
::::::::::::::++++++++++++=============%%%%@$||&#$$##$$##$$$$##&&&- %-*&:@%%%%++++++++++++++
:::::::::::++++++++++++++=============%%%%##&.:**&&$&::**&&$$$&&*% =,#%%%==+++++++++++++
::::::::++++++++++++++++%%%%========@@@@@@##&&|. ,.::.+ .,.****|-&* *#@@@====++++++++++++
::::::+++++++++++++++++%%%%%%%%%%%@@@@@@@@$$$*|.& @ =::-:. |$@@======+++++++++++
:::++++++++++++++++++%%%%%%%%%%%%%@@@@####$$*--.$ , , %-#@@========+++++++++
:++++++++++++++++++==%%%%%%%%%%%%@@@@@#*-&**--= %# :$#%%%=========+++++++
+++++++++++++++++====%%%%%@@@@#####$$$$&|:. .- , $$@@%%%%==========+++++
++++++++++++++======@@@##@$*$$##$#&$$-*|.+ : #$#@@@%%%%%==========+++
+++++++++++++====%@ - |*$$#@@@%%%%%=========+++
++++++++++++++======@@@##@$*$$##$#&$$-*|.+ : #$#@@@%%%%%==========+++
+++++++++++++++++====%%%%%@@@@#####$$$$&|:. .- , $$@@%%%%==========+++++
:++++++++++++++++++==%%%%%%%%%%%%@@@@@#*-&**--= %# :$#%%%=========+++++++
:::++++++++++++++++++%%%%%%%%%%%%%@@@@####$$*--.$ , , %-#@@========+++++++++
::::::+++++++++++++++++%%%%%%%%%%%@@@@@@@@$$$*|.& @ =::-:. |$@@======+++++++++++
::::::::++++++++++++++++%%%%========@@@@@@##&&|. ,.::.+ .,.****|-&* *#@@@====++++++++++++
:::::::::::++++++++++++++=============%%%%##&.:**&&$&::**&&$$$&&*% =,#%%%==+++++++++++++
::::::::::::::++++++++++++=============%%%%@$||&#$$##$$##$$$$##&&&- %-*&:@%%%%++++++++++++++
::::::::::::::::++++++++++==============%%%@####@@@@@#@@@@@@####$*=: -&###@%%%%++++++++++++++
:::::::::::::::::::++++++++=============%%%%%%%%%@@@@@@@@@@@@###$$&* - : :,*$@@@%%%%++++++++++++++
:::::::::::::::::::::++++++==============%%%========@@@@@@@%%%#####$$*. @-*#%%%%%%++++++++++++::
:::::::::::::::::::::::+++============================@@%%%%%%%%@@@$&|:@ =, * + --,%% :,#%%%%%=++++++++++::::
:::::::::::::::::::::::::+==============================%%%%%%%%%@@@&|:&,:|*|# , | *..#.*$&$&&: #@@=====++++++++::::::
:::::::::::::::::::::::::::==============================%%%%%%%%@@##$$$:*$$&&**|-:$ -**&&$##@@@#|#%========+++++::::::::
:::::::::::::::::::::::::::::============================%%%%%%%%%@%%%%####@$$$&&|. *.&$####@%@@@@%%=========++::::::::::
:::::::::::::::::::::::::::::::==========================%%%%%%%%%%%%%%%%@@@@@##$*:= :&###%%%%%%%%%%=========+:::::::::::
::::::::::::::::::::::::::::::::=========================%%%%%%%%%%%%%%%%%@@@@##$:#$.#& :-:$$@@@%%%%%%%%%=========:::::::::::::
::::::::::::::::::::::::::::::::::=================++++++++++%%%%%%%%%%%%%@@@@@@@$$&*& ::=&$##@@@%================::::::::::::::
:::::::::::::::::::::::::::::::::::===========+++++++++++++++++++%%%=========@@@@@##$**:.|&$@@@@================::::::::::::::::
::::::::::::::::::::::::::::::::::::=======+++++++++++++++++++++++++==========%%%%##$&::& ||#@%%===============:::::::::::::::::
:::::::::::::::::::::::::::::::::::::===+++++++++++++++++++++++++++++==========%%%%##.-##@@@%%%===============::::::::::::::::::
::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++=========%%@#.&$#%%%%%%%%=============:::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++========%%%%%=@%%%%%++++++++========::::::::::::::::::::

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@ -0,0 +1,353 @@
(
; This is a demo of a bitmap font and string rendering using buffers.
; You can do this many ways.
(sc-init 800 250)
(sc-opt SCREEN_UPSCALE 5)
; Define a font. This should be less ugly once we have includes implemented...
(sym FONT g15)
(mkbf FONT (
0x0000000000000000 ; 0
0x7e81a581bd99817e ; 1
0x7effdbffc3e7ff7e ; 2
0x367f7f7f3e1c0800 ; 3
0x081c3e7f3e1c0800 ; 4
0x1c3e1c7f7f3e1c3e ; 5
0x08081c3e7f3e1c3e ; 6
0x0000183c3c180000 ; 7
0xffffe7c3c3e7ffff ; 8
0x003c664242663c00 ; 9
0xffc399bdbd99c3ff ; 10
0xf0e0f0be3333331e ; 11
0x3c6666663c187e18 ; 12
0xfcccfc0c0c0e0f07 ; 13
0xfec6fec6c6e66703 ; 14
0x995a3ce7e73c5a99 ; 15
0x01071f7f1f070100 ; 16
0x40707c7f7c704000 ; 17
0x183c7e18187e3c18 ; 18
0x6666666666006600 ; 19
0xfedbdbded8d8d800 ; 20
0x7cc61c36361c331e ; 21
0x000000007e7e7e00 ; 22
0x183c7e187e3c18ff ; 23
0x183c7e1818181800 ; 24
0x181818187e3c1800 ; 25
0x0018307f30180000 ; 26
0x000c067f060c0000 ; 27
0x00000303037f0000 ; 28
0x002466ff66240000 ; 29
0x00183c7effff0000 ; 30
0x00ffff7e3c180000 ; 31
0x0000000000000000 ; 32 ' '
0x0c1e1e0c0c000c00 ; 33 '!'
0x3636360000000000 ; 34 '"'
0x36367f367f363600 ; 35 '#'
0x0c3e031e301f0c00 ; 36 '$'
0x006333180c666300 ; 37 '%'
0x1c361c6e3b336e00 ; 38 '&'
0x0606030000000000 ; 39 '''
0x180c0606060c1800 ; 40 '('
0x060c1818180c0600 ; 41 ')'
0x00663cff3c660000 ; 42 '*'
0x000c0c3f0c0c0000 ; 43 '+'
0x00000000000c0c06 ; 44 ','
0x0000003f00000000 ; 45 '-'
0x00000000000c0c00 ; 46 '.'
0x6030180c06030100 ; 47 '/'
0x3e63737b6f673e00 ; 48 '0'
0x0c0e0c0c0c0c3f00 ; 49 '1'
0x1e33301c06333f00 ; 50 '2'
0x1e33301c30331e00 ; 51 '3'
0x383c36337f307800 ; 52 '4'
0x3f031f3030331e00 ; 53 '5'
0x1c06031f33331e00 ; 54 '6'
0x3f3330180c0c0c00 ; 55 '7'
0x1e33331e33331e00 ; 56 '8'
0x1e33333e30180e00 ; 57 '9'
0x000c0c00000c0c00 ; 58 ':'
0x000c0c00000c0c06 ; 59 ';'
0x180c0603060c1800 ; 60 '<'
0x00003f00003f0000 ; 61 '='
0x060c1830180c0600 ; 62 '>'
0x1e3330180c000c00 ; 63 '?'
0x3e637b7b7b031e00 ; 64 '@'
0x0c1e33333f333300 ; 65 'A'
0x3f66663e66663f00 ; 66 'B'
0x3c66030303663c00 ; 67 'C'
0x1f36666666361f00 ; 68 'D'
0x7f46161e16467f00 ; 69 'E'
0x7f46161e16060f00 ; 70 'F'
0x3c66030373667c00 ; 71 'G'
0x3333333f33333300 ; 72 'H'
0x1e0c0c0c0c0c1e00 ; 73 'I'
0x7830303033331e00 ; 74 'J'
0x6766361e36666700 ; 75 'K'
0x0f06060646667f00 ; 76 'L'
0x63777f7f6b636300 ; 77 'M'
0x63676f7b73636300 ; 78 'N'
0x1c36636363361c00 ; 79 'O'
0x3f66663e06060f00 ; 80 'P'
0x1e3333333b1e3800 ; 81 'Q'
0x3f66663e36666700 ; 82 'R'
0x1e33070e38331e00 ; 83 'S'
0x3f2d0c0c0c0c1e00 ; 84 'T'
0x3333333333333f00 ; 85 'U'
0x33333333331e0c00 ; 86 'V'
0x6363636b7f776300 ; 87 'W'
0x6363361c1c366300 ; 88 'X'
0x3333331e0c0c1e00 ; 89 'Y'
0x7f6331184c667f00 ; 90 'Z'
0x1e06060606061e00 ; 91 '['
0x03060c1830604000 ; 92 '\'
0x1e18181818181e00 ; 93 ']'
0x081c366300000000 ; 94 '^'
0x00000000000000ff ; 95 '_'
0x0c0c180000000000 ; 96 '`'
0x00001e303e336e00 ; 97 'a'
0x0706063e66663b00 ; 98 'b'
0x00001e3303331e00 ; 99 'c'
0x3830303e33336e00 ; 100 'd'
0x00001e333f031e00 ; 101 'e'
0x1c36060f06060f00 ; 102 'f'
0x00006e33333e301f ; 103 'g'
0x0706366e66666700 ; 104 'h'
0x0c000e0c0c0c1e00 ; 105 'i'
0x300030303033331e ; 106 'j'
0x070666361e366700 ; 107 'k'
0x0e0c0c0c0c0c1e00 ; 108 'l'
0x0000337f7f6b6300 ; 109 'm'
0x00001f3333333300 ; 110 'n'
0x00001e3333331e00 ; 111 'o'
0x00003b66663e060f ; 112 'p'
0x00006e33333e3078 ; 113 'q'
0x00003b6e66060f00 ; 114 'r'
0x00003e031e301f00 ; 115 's'
0x080c3e0c0c2c1800 ; 116 't'
0x0000333333336e00 ; 117 'u'
0x00003333331e0c00 ; 118 'v'
0x0000636b7f7f3600 ; 119 'w'
0x000063361c366300 ; 120 'x'
0x00003333333e301f ; 121 'y'
0x00003f190c263f00 ; 122 'z'
0x380c0c070c0c3800 ; 123 '{'
0x1818180018181800 ; 124 '|'
0x070c0c380c0c0700 ; 125 '}'
0x6e3b000000000000 ; 126 '~'
0x00081c3663637f00 ; 127
0x1e3303331e18301e ; 128
0x0033003333337e00 ; 129
0x38001e333f031e00 ; 130
0x7ec33c607c66fc00 ; 131
0x33001e303e337e00 ; 132
0x07001e303e337e00 ; 133
0x0c0c1e303e337e00 ; 134
0x00001e03031e301c ; 135
0x7ec33c667e063c00 ; 136
0x33001e333f031e00 ; 137
0x07001e333f031e00 ; 138
0x33000e0c0c0c1e00 ; 139
0x3e631c1818183c00 ; 140
0x07000e0c0c0c1e00 ; 141
0x631c36637f636300 ; 142
0x0c0c001e333f3300 ; 143
0x38003f061e063f00 ; 144
0x0000fe30fe33fe00 ; 145
0x7c36337f33337300 ; 146
0x1e33001e33331e00 ; 147
0x0033001e33331e00 ; 148
0x0007001e33331e00 ; 149
0x1e33003333337e00 ; 150
0x0007003333337e00 ; 151
0x00330033333e301f ; 152
0xc3183c66663c1800 ; 153
0x3300333333331e00 ; 154
0x18187e03037e1818 ; 155
0x1c36260f06673f00 ; 156
0x33331e3f0c3f0c0c ; 157
0x1f33335f63f363e3 ; 158
0x70d8183c18181b0e ; 159
0x38001e303e337e00 ; 160
0x1c000e0c0c0c1e00 ; 161
0x0038001e33331e00 ; 162
0x0038003333337e00 ; 163
0x001f001f33333300 ; 164
0x3f0033373f3b3300 ; 165
0x3c36367c007e0000 ; 166
0x1c36361c003e0000 ; 167
0x0c000c0603331e00 ; 168
0x0000003f03030000 ; 169
0x0000003f30300000 ; 170
0xc363337bcc6633f0 ; 171
0xc36333dbecf6f3c0 ; 172
0x1818001818181800 ; 173
0x00cc663366cc0000 ; 174
0x003366cc66330000 ; 175
0x4411441144114411 ; 176
0xaa55aa55aa55aa55 ; 177
0xdbeedb77dbeedb77 ; 178
0x1818181818181818 ; 179
0x181818181f181818 ; 180
0x18181f181f181818 ; 181
0x6c6c6c6c6f6c6c6c ; 182
0x000000007f6c6c6c ; 183
0x00001f181f181818 ; 184
0x6c6c6f606f6c6c6c ; 185
0x6c6c6c6c6c6c6c6c ; 186
0x00007f606f6c6c6c ; 187
0x6c6c6f607f000000 ; 188
0x6c6c6c6c7f000000 ; 189
0x18181f181f000000 ; 190
0x000000001f181818 ; 191
0x18181818f8000000 ; 192
0x18181818ff000000 ; 193
0x00000000ff181818 ; 194
0x18181818f8181818 ; 195
0x00000000ff000000 ; 196
0x18181818ff181818 ; 197
0x1818f818f8181818 ; 198
0x6c6c6c6cec6c6c6c ; 199
0x6c6cec0cfc000000 ; 200
0x0000fc0cec6c6c6c ; 201
0x6c6cef00ff000000 ; 202
0x0000ff00ef6c6c6c ; 203
0x6c6cec0cec6c6c6c ; 204
0x0000ff00ff000000 ; 205
0x6c6cef00ef6c6c6c ; 206
0x1818ff00ff000000 ; 207
0x6c6c6c6cff000000 ; 208
0x0000ff00ff181818 ; 209
0x00000000ff6c6c6c ; 210
0x6c6c6c6cfc000000 ; 211
0x1818f818f8000000 ; 212
0x0000f818f8181818 ; 213
0x00000000fc6c6c6c ; 214
0x6c6c6c6cff6c6c6c ; 215
0x1818ff18ff181818 ; 216
0x181818181f000000 ; 217
0x00000000f8181818 ; 218
0xffffffffffffffff ; 219
0x00000000ffffffff ; 220
0x0f0f0f0f0f0f0f0f ; 221
0xf0f0f0f0f0f0f0f0 ; 222
0xffffffff00000000 ; 223
0x00006e3b133b6e00 ; 224
0x001e331f331f0303 ; 225
0x003f330303030300 ; 226
0x007f363636363600 ; 227
0x3f33060c06333f00 ; 228
0x00007e1b1b1b0e00 ; 229
0x00666666663e0603 ; 230
0x006e3b1818181800 ; 231
0x3f0c1e33331e0c3f ; 232
0x1c36637f63361c00 ; 233
0x1c36636336367700 ; 234
0x380c183e33331e00 ; 235
0x00007edbdb7e0000 ; 236
0x60307edbdb7e0603 ; 237
0x1c06031f03061c00 ; 238
0x1e33333333333300 ; 239
0x003f003f003f0000 ; 240
0x0c0c3f0c0c003f00 ; 241
0x060c180c06003f00 ; 242
0x180c060c18003f00 ; 243
0x70d8d81818181818 ; 244
0x18181818181b1b0e ; 245
0x0c0c003f000c0c00 ; 246
0x006e3b006e3b0000 ; 247
0x1c36361c00000000 ; 248
0x0000001818000000 ; 249
0x0000000018000000 ; 250
0xf030303037363c38 ; 251
0x1e36363636000000 ; 252
0x0e180c061e000000 ; 253
0x00003c3c3c3c0000 ; 254
0x0000000000000000 ; 255
))
(mkbf r7 " Hello World ")
(bfpush @r7 1) ; ASCII smileys
(bfrpush @r7 2)
; It sometimes randomly doesn't show... ???
; Test
(sc-wr 0 0 #ff00ff)
(sc-blit)
(call ShowChar 10 10 #ffffff 'A')
(call ShowString 10 20 #ffff00 r7)
(sc-blit)
(bfrsz @r7 0)
(lds @cout "Type something: ")
(:read)
(ld r0 @cin (eof? (halt)))
(ld @cout r0)
(ld @r7 r0)
(call ShowString 10 30 #ff0000 r7)
(sc-blit)
(j :read)
(proc ShowString x y color str
(sym size r10)
(sym index r11)
(sym xx r12)
(bfsz size @str (z? (ret)))
(ld index 0)
(ld xx x)
(:ch)
(bfrd r0 @str index)
(call ShowChar xx y color r0)
(add xx 8)
(inc index)
(dec size (z? (ret)))
(j :ch)
)
(proc ShowChar x y color ch
(rcmp ch 0 255)
(ret.ne)
(sym s r15)
(bfrd s @FONT ch)
(rev s)
(sym xx r5)
(sym yy r6)
(sym nb r7)
(sym xmax r8)
(add xmax x 8)
(ld xx x)
(ld yy y)
(ld nb 64)
(:nb)
(and r0 s 1)
(j.z :skip)
(sc-wr xx yy color)
(:skip)
(ror s 1)
(inc xx)
(cmp xx xmax (eq? (ld xx x)(inc yy)))
(dec nb)
(j.nz :nb)
(ret)
)
(proc WaitForClose
(:j)
(sc-blit)
;(sc-poll)
(mslp 100)
(j :j)
)
)

@ -0,0 +1,58 @@
(
; Helper to check key codes of physical keys
(sc-init 200 200)
(lds @cout "Press keys in the window to see their codes...\n")
(sym pressed r15)
(mkbf pressed 256)
(:loop)
(sc-poll)
(mslp 10)
(sym cnt r14)
(ld cnt 0)
(:next)
(sc-key _ cnt
(inval? (nop))
(nz?
(bfrd r1 @pressed cnt
(z?
(call printnum cnt)
(lds @cout " PRESS\n")
(bfwr @pressed cnt 1)
(rng r0 0 #ffffff)
(sc-erase r0)
(sc-blit)
)
)
)
(z?
(bfrd r1 @pressed cnt
(nz?
(call printnum cnt)
(lds @cout " RELEASE\n")
(bfwr @pressed cnt 0)
)
)
)
)
(inc cnt)
(cmp cnt 256 (ne? (j :next)))
(j :loop)
; this is a version if itoa that prints a number
(proc printnum num
(mkbf r15)
(ld r1 num)
(tst r1 (<0? (mul r1 -1)))
(:next)
(mod r0 r1 10)
(add r0 '0')
(bfrpush @r15 r0)
(div r1 10 (z?
(tst num (<0? (bfrpush @r15 '-')))
(lds @cout @r15)
(del @r15)
(ret)))
(j :next)
)
)

@ -0,0 +1,28 @@
(
(sc-init 512 512)
(sc-opt SCREEN_UPSCALE 16)
(def W 32)
(lds @cout "Drag colors to draw...\n")
(sc-wr 0 0 #ff00ff)
(sc-wr 5 5 #ffff00)
(sc-wr 15 15 #00ff00)
(sc-blit)
(:l)
(sc-poll)
(sc-mbtn r0 0)
(j.z :nope)
(sc-mouse r0 r1)
(sc-rd r3 r0 r1)
(sub r0 1)
(sub r1 1)
(sc-rect r0 r1 3 3 r3)
(sc-blit)
(:nope)
(mslp 10)
(j :l)
)

@ -0,0 +1,14 @@
(
(sc-init 512 512)
(sc-opt SCREEN_UPSCALE 32)
(sc-wr 0 0 #ff00ff)
(sc-wr 1 1 #ffff00)
(sc-wr 15 15 #00ff00)
(sc-blit)
(:l)
(sc-poll)
(mslp 100)
(j :l)
)

@ -0,0 +1,15 @@
(
(sc-init 512 512)
(sc-opt SCREEN_UPSCALE 16)
(def W 32)
(:l)
(sc-poll)
(sc-erase)
(sc-mouse r0 r1)
(sc-rect 0 r1 W 1 #ff0000)
(sc-rect r0 0 1 W #0066ff)
(sc-wr r0 r1 #ffffff)
(mslp 10)
(j :l)
)

@ -0,0 +1,36 @@
(
; test else
(cmp 0 5
(eq? (fault))
(else? (ld r0 15))
(lt? (fault)) ; This should produce a warning
)
(cmp 15 r0
(ne? (fault "else did not run")))
(ld r0 0)
(cmp 0 5
(lt? (nop))
(else? (fault "fallthrough to else"))
)
;
(ld r8 0)
(ld r0 1
(else? (add r8 1))
)
(sub r0 1
(pos? (fault))
(else? (add r8 1))
)
(cmp r8 2
(ne? (fault))
)
)

@ -17,7 +17,6 @@ use crsn_screen::ScreenOps;
use crsn_stdio::StdioOps;
use crsn_buf::BufOps;
mod read_file;
mod serde_duration_millis;
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -38,6 +37,8 @@ struct Config {
assemble_debug: bool,
#[serde(with = "serde_duration_millis")]
cycle_time: Duration,
#[serde(with = "serde_duration_millis")]
switch_time: Duration,
}
impl Default for Config {
@ -51,6 +52,7 @@ impl Default for Config {
assemble_only: false,
assemble_debug: false,
cycle_time: Duration::default(),
switch_time: Duration::from_millis(10),
}
}
}
@ -105,6 +107,14 @@ impl AppConfig for Config {
.help("Cycle time (us, append \"s\" or \"ms\" for coarser time)")
.takes_value(true),
)
.arg(
clap::Arg::with_name("sched")
.long("sched")
.short("S")
.value_name("MILLIS")
.help("Scheduler switch time (ms, append \"u\", \"s\" or \"ms\" for other unit)")
.takes_value(true),
)
}
fn configure(mut self, clap: &ArgMatches) -> anyhow::Result<Self> {
@ -123,11 +133,28 @@ impl AppConfig for Config {
} else if t.ends_with("s") {
(&t[..(t.len()-1)], 1000_000)
} else {
(t, 1)
(t, 1) // us default
};
self.cycle_time = Duration::from_micros(t.parse::<u64>().expect("parse -C value") * mul);
println!("ct = {:?}", self.cycle_time);
}
if let Some(t) = clap.value_of("sched") {
let (t, mul) = if t.ends_with("us") {
(&t[..(t.len()-2)], 1)
} else if t.ends_with("ms") {
(&t[..(t.len()-2)], 1000)
} else if t.ends_with("m") {
(&t[..(t.len()-1)], 1000)
} else if t.ends_with("u") {
(&t[..(t.len()-1)], 1)
} else if t.ends_with("s") {
(&t[..(t.len()-1)], 1000_000)
} else {
(t, 1000) // ms default
};
self.switch_time = Duration::from_micros(t.parse::<u64>().expect("parse -S value") * mul);
}
Ok(self)
}
@ -139,11 +166,9 @@ fn main() -> anyhow::Result<()> {
debug!("Loading {}", config.program_file);
let source = read_file::read_file(&config.program_file)?;
let uniq = CrsnUniq::new();
let parsed = crsn::asm::assemble(&source, &uniq, vec![
let parsed = crsn::asm::assemble(&config.program_file, &uniq, vec![
ArithOps::new(),
BufOps::new(),
ScreenOps::new(),
@ -151,6 +176,7 @@ fn main() -> anyhow::Result<()> {
])?;
if config.assemble_only {
println!("--- {} ---", config.program_file);
for (n, op) in parsed.ops.iter().enumerate() {
if config.assemble_debug {
println!("{:04} : {:?}", n, op);
@ -176,6 +202,7 @@ fn main() -> anyhow::Result<()> {
program: parsed,
pc: Addr(0),
cycle_time: config.cycle_time,
scheduler_interval: config.switch_time,
args
});

Loading…
Cancel
Save