diff --git a/core/dev/Makefile b/core/dev/Makefile
index 7a8d69e..9d6c516 100644
--- a/core/dev/Makefile
+++ b/core/dev/Makefile
@@ -261,6 +261,13 @@ tests-mcu:
--tee ./build/tests-mcu.log ./build/tests-mcu.frt
awk -f $(AMFORTH)/tests/results-mcu.awk ./build/tests-mcu.log
+## Run tests-mcu.frt through amforth-shell on hardware
+tests-sock:
+ # export EDITOR in your .env file to make the #edit shell directive work.
+ $(SKIP) -x assembler-with-cpp -I $(AMFORTH) $(AMFORTH)/tests/tests-mcu.frt > ./build/tests-mcu.frt
+ $(AMFORTH)/tools/socket-shell.py -I $(AMFORTH) --no-error-on-output ./build/tests-mcu.frt
+ #awk -f $(AMFORTH)/tests/results-mcu.awk ./build/tests-mcu.log
+
## Detailed dump of ELF sections for debugging linker issues
sections: sec-type sec-addr sec-sum
diff --git a/core/words/see.s b/core/words/see.s
index c4a07fc..b0f3049 100644
--- a/core/words/see.s
+++ b/core/words/see.s
@@ -361,7 +361,7 @@ SEEDOTDODOTXT_0001: /* then */
.word XT_DP_RAM
.word XT_DP
.word XT_MAX
- .word XT_LESS
+ .word XT_ULESS
.word XT_DOCONDBRANCH,SEEDOTDODOTXT_0002 /* if */
.word XT_SEEDOTIP
.word XT_FETCH
diff --git a/rv/mcu/fe310/Makefile b/rv/mcu/fe310/Makefile
new file mode 100644
index 0000000..c73da81
--- /dev/null
+++ b/rv/mcu/fe310/Makefile
@@ -0,0 +1,86 @@
+# SPDX-License-Identifier: GPL-3.0-only
+
+-include .env
+
+AMFORTH ?= $(shell git rev-parse --show-toplevel)
+
+QEMU_RV32 ?= qemu-system-riscv32
+
+# Default target to build
+TARGET ?= REDFIVE
+TARGETS = REDFIVE HIFIVE1
+
+CHIPSET=$(TARGET)
+MCU_LD := ./$(TARGET).ld
+LIB_WCH := ./lib_$(TARGET)
+LIB_USER := ./lib_$(TARGET)_user
+
+ASFLAGS += -march=rv32imac_zicsr_zifencei -mabi=ilp32
+
+# Included after various configuration bits are defined (importantly MCU_LD and TARGET)
+include $(AMFORTH)/rv/dev/Makefile
+
+.PRECIOUS: amforth.s
+
+#? --- QEM ---
+
+# This is quite different from the -kernel option, which will for the RV 'virt' jump to
+# 0x8000_0000 no matter what ENTRY() has in the linker file. The cpu-num=0 sets the PC
+# to the value indicated by ENTRY(), which is much more suitable our needs. The commented
+# out line gives a hint as to how to set the PC to an absolute address at boot time.
+
+QEMU := $(QEMU_RV32) -M virt -bios none -device loader,file=build/amforth.elf,cpu-num=0
+# -device loader,addr=0x20010000,cpu-num=0
+
+## Start AmForth under QEMU with operator uart connected to stdio
+stdio:
+ $(QEMU) -display none -serial stdio
+
+## Start AmForth under QEMU with operator uart connected to a pty
+pty:
+ $(QEMU) -nographic -serial pty
+
+# Add -S if you want QEMU to not start the quest.
+## Like pty but allows GDB to attach to it (-s).
+debug:
+ $(QEMU) -nographic -serial pty -s
+
+## Connect GDB to amforth running on QEMU (must be running with -s, see debug)
+gdb:
+ $(GDBPY) build/amforth.elf --quiet \
+ -ex 'target remote :1234' \
+ -ex "source tui-full.gdb"
+
+## Serve AmForth on :4444 (make nc|socshell for client) - logs to /tmp/serial.log
+socket:
+ $(QEMU) -display none \
+ -chardev socket,id=sock0,host=127.0.0.1,port=4444,server=on,wait=on,logfile=/tmp/serial.log \
+ -serial chardev:sock0
+
+## Serve AmForth on :4444 with debug on :1234 - logs to /tmp/serial.log
+socbug:
+ $(QEMU) -display none \
+ -chardev socket,id=sock0,host=127.0.0.1,port=4444,server=on,wait=on,logfile=/tmp/serial.log \
+ -serial chardev:sock0 -s
+
+## rlwrap nc client connecting to :4444
+nc:
+ rlwrap nc localhost 4444
+
+## socket-shell as client connecting to :4444
+socshell:
+ $(AMFORTH)/tools/socket-shell.py --no-error-on-output -i --ignore-error
+
+read-elf:
+ $(TC_DIR)/bin/$(CROSS)readelf -h build/amforth.elf
+
+ifeq ($(strip $(TARGET)),REDFIVE)
+QEMU:=$(QEMU_RV32) -M sifive_e,revb=true -bios none -kernel build/amforth.elf
+## Upload hex file to the device
+upload:
+ cp build/amforth.hex /Volumes/HiFive
+else
+QEMU:=$(QEMU_RV32) -M sifive_e -bios none -kernel build/amforth.elf
+upload:
+ echo "no upload for $(TARGET) available"
+endif
diff --git a/rv/mcu/fe310/amforth.s b/rv/mcu/fe310/amforth.s
new file mode 100644
index 0000000..1fc7ade
--- /dev/null
+++ b/rv/mcu/fe310/amforth.s
@@ -0,0 +1,32 @@
+# This is a template to start from.
+# Copy it into your mcu/ directory and modify as needed.
+
+.text
+
+.include "mcu/qemu/config.inc" # most configuration options are in this file
+.include "build-config.inc"
+
+.print "INFO: using FE310 startup"
+.include "fe310.startup" # startup and master_int handler for both C and Forth
+
+.section amforth, "ax"
+
+.option push # save option set
+.option norvc # don't use compressed
+.option norelax # don't relax
+
+.include "config.inc"
+.include "build-config.inc"
+.include "rv/macros.inc"
+.include "user.inc"
+
+STARTDICT
+
+.include "dict_prims.inc"
+.include "dict_secs.inc"
+.include "dict_env.inc"
+.include "dict_mcu.inc"
+
+ENDDICT
+
+.option pop # restore option set
diff --git a/rv/mcu/fe310/config.inc b/rv/mcu/fe310/config.inc
new file mode 100644
index 0000000..87bb975
--- /dev/null
+++ b/rv/mcu/fe310/config.inc
@@ -0,0 +1,18 @@
+
+.include "core/config.inc"
+
+.equ datastack_size, 32 * cellsize
+.equ returnstack_size, 32 * cellsize
+
+/* TODO: this is made up, needs correcting */
+.equ flash_page, 1*1024
+.equ flash_cell, 1
+.equ flash_erased, 0xFFFFFFFF /* a word in erased flash */
+
+.equ rampool_size, 1024*1
+
+.equ WANT_DEBUGGER , YES
+.equ WANT_IGNORECASE , YES
+.equ WANT_SEE , YES
+.equ WANT_TRANSPILER , NO
+
diff --git a/rv/mcu/fe310/dict_mcu.inc b/rv/mcu/fe310/dict_mcu.inc
new file mode 100644
index 0000000..8e3f590
--- /dev/null
+++ b/rv/mcu/fe310/dict_mcu.inc
@@ -0,0 +1,21 @@
+
+.include "words/gpio.s"
+.include "words/usart.s"
+
+.if TARGET==HIFIVE1
+ .include "words/leds.s"
+.else
+ .include "words/redvled.s"
+.endif
+
+.include "words/mtime.s"
+
+.include "words/applturnkey.s"
+
+.include "words/type0.s"
+
+.include "words/pll.s"
+.include "words/qspidiv.s"
+.include "words/csr.s"
+
+
diff --git a/rv/mcu/fe310/eval.f b/rv/mcu/fe310/eval.f
new file mode 100644
index 0000000..4f46ac9
--- /dev/null
+++ b/rv/mcu/fe310/eval.f
@@ -0,0 +1,64 @@
+variable strlen
+variable str
+variable str-buf $20 allot
+
+: source-string str @ strlen @ dup . ;
+: refill-false ( -- false ) false ;
+
+: (evaluate)
+ break
+ ['] source defer@ >r
+ ['] refill defer@ >r
+ >in @ >r
+ 0 >in !
+ strlen !
+ str !
+ ['] source-string is source
+ ['] interpret catch
+ r> >in !
+ r> is refill
+ r> is source
+ throw
+;
+
+\ : (evaluate) \ i*x addr len -- j*y
+\ break
+\ ['] source defer@ >r \ 1. Save current source implementation
+\ >in @ >r \ 2. Save current input offset
+\ 0 >in ! \ 3. Reset offset to start of buffer
+\ strlen ! \ 4. Store string length
+\ str ! \ 5. Store string address
+\ ['] source-string is source \ 6. Redirect source to our buffer
+\ ['] interpret catch \ 7. Execute interpreter (with error trapping)
+\ r> >in ! \ 8. Restore original input offset
+\ r> is source \ 9. Restore original source
+\ throw \ 10. Re-throw any caught errors
+\ ;
+
+: evaluate
+ state @ if
+ postpone (evaluate)
+ else
+ (evaluate)
+ then
+; immediate
+
+
+: ww ( addr len -- )
+ dup strlen ! \ a n
+ str-buf swap move \
+ str-buf str !
+ \ (evaluate)
+;
+
+: (evaluate-test)
+ strlen !
+ str !
+ ['] source-string is source
+ source
+ ." len=" .
+ ." addr=" hex.
+ ['] source is source
+;
+
+\ s" 1 1 +" (evaluate-test)
diff --git a/rv/mcu/fe310/fe310.startup b/rv/mcu/fe310/fe310.startup
new file mode 100755
index 0000000..436c099
--- /dev/null
+++ b/rv/mcu/fe310/fe310.startup
@@ -0,0 +1,16 @@
+
+.option norvc # no opportunistic use of compressed ...
+.option norelax # instructions or other such optimisations
+
+#la gp, __global_pointer$
+
+.global PFA_COLD
+.global _start
+
+.section .init
+_start:
+ la gp, __global_pointer$
+ la sp, _eusrstack
+
+ j PFA_COLD
+
diff --git a/rv/mcu/fe310/hifive1.ld b/rv/mcu/fe310/hifive1.ld
new file mode 100644
index 0000000..7296e7e
--- /dev/null
+++ b/rv/mcu/fe310/hifive1.ld
@@ -0,0 +1,20 @@
+OUTPUT_ARCH( "riscv" )
+
+ENTRY( _start )
+
+/* Not enough memory to support pvalues practically */
+PVFLASH_PAGE = 0;
+PVFLASH_CELL = 0;
+PVFLASH_ERASED = flash_erased;
+
+/* !! Memory regions are defined early and their parameters MUST NOT be computed from object file symbols !! */
+MEMORY
+{
+ /* FLASH(RX) : ORIGIN = 0x20400000, LENGTH = 32K */
+ FLASH(RX) : ORIGIN = 0x20400000, LENGTH = 64K
+ RAM(WAIXL) : ORIGIN = 0x80000000, LENGTH = 16K
+ /* MUST define the PVFLASH region otherwise it will default to occupying the entire memory space */
+ PVFLASH(RW) : ORIGIN = 0 , LENGTH = 0
+}
+
+INCLUDE amforth32.ld
diff --git a/rv/mcu/fe310/readme.md b/rv/mcu/fe310/readme.md
new file mode 100644
index 0000000..981992a
--- /dev/null
+++ b/rv/mcu/fe310/readme.md
@@ -0,0 +1,9 @@
+# AmForth for SiFive Hifive1
+
+AmForth is published under the GNU Public License v3 (GPL).
+
+Arduino-compatible development board featuring the SiFive Freedom E310 (FE310) SoC [32-bit RV32IMAC core].
+
+# References
+
+* https://www.sifive.com/boards/hifive1
\ No newline at end of file
diff --git a/rv/mcu/fe310/redfive.ld b/rv/mcu/fe310/redfive.ld
new file mode 100644
index 0000000..061b060
--- /dev/null
+++ b/rv/mcu/fe310/redfive.ld
@@ -0,0 +1,20 @@
+OUTPUT_ARCH( "riscv" )
+
+ENTRY( _start )
+
+/* Not enough memory to support pvalues practically */
+PVFLASH_PAGE = 0;
+PVFLASH_CELL = 0;
+PVFLASH_ERASED = flash_erased;
+
+/* !! Memory regions are defined early and their parameters MUST NOT be computed from object file symbols !! */
+MEMORY
+{
+ /* FLASH(RX) : ORIGIN = 0x20400000, LENGTH = 32K */
+ FLASH(RX) : ORIGIN = 0x20010000, LENGTH = 64K
+ RAM(WAIXL) : ORIGIN = 0x80000000, LENGTH = 16K
+ /* MUST define the PVFLASH region otherwise it will default to occupying the entire memory space */
+ PVFLASH(RW) : ORIGIN = 0 , LENGTH = 0
+}
+
+INCLUDE amforth32.ld
diff --git a/rv/mcu/fe310/words/applturnkey.s b/rv/mcu/fe310/words/applturnkey.s
new file mode 100755
index 0000000..f5d145c
--- /dev/null
+++ b/rv/mcu/fe310/words/applturnkey.s
@@ -0,0 +1,19 @@
+COLON "hifive-turnkey", APPLTURNKEY
+
+ .word XT_LED_INIT
+ .word XT_DECIMAL
+
+/* setup for 256MHz via PLL with 115200 */
+
+ .word XT_PLL
+ .word XT_QSPIDIV
+ .word XT_INIT_USART
+
+ .word XT_DOT_VER, XT_SPACE
+ .word XT_ENV_BOARD,XT_TYPE, XT_CR
+ .word XT_ENV_DOT_BUILD
+
+ .word XT_EXIT
+END APPLTURNKEY
+
+
diff --git a/rv/mcu/fe310/words/build-info.tmpl b/rv/mcu/fe310/words/build-info.tmpl
new file mode 100644
index 0000000..31e9fd8
--- /dev/null
+++ b/rv/mcu/fe310/words/build-info.tmpl
@@ -0,0 +1,8 @@
+
+COLON "build-info", BUILD_INFO
+ STRING "%d"
+.word XT_EXIT
+
+COLON "rev-info", REV_INFO
+ STRING "%r"
+.word XT_EXIT
diff --git a/rv/mcu/fe310/words/clock.s b/rv/mcu/fe310/words/clock.s
new file mode 100644
index 0000000..4472951
--- /dev/null
+++ b/rv/mcu/fe310/words/clock.s
@@ -0,0 +1,26 @@
+
+.equ PRCI_BASE, 0x10008000
+
+.equ PRCI_HFROSCCFG , PRCI_BASE + 0x00
+.equ PRCI_HFXOSCCFG , PRCI_BASE + 0x04
+.equ PRCI_PLLCFG , PRCI_BASE + 0x08
+.equ PRCI_PLLDIV , PRCI_BASE + 0x0C
+.equ PRCI_PROCMONCFG , PRCI_BASE + 0xF0
+
+CODEWORD "clock", CLOCK
+
+ # set up the clock system and make it run
+
+1:li t0, PRCI_HFXOSCCFG # 0x10008004
+ lw t1, 0(t0)
+ li t2, 0xC0000000
+ bne t1, t2, 1b
+
+ # Select crystal as main clock source
+
+ li t0, PRCI_PLLCFG
+ li t1, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18) # Reset value | PLLSEL | PLLREFSEL | PLLBYPASS
+ sw t1, 0(t0)
+
+ NEXT
+END CLOCK
diff --git a/rv/mcu/fe310/words/cold.s b/rv/mcu/fe310/words/cold.s
new file mode 100644
index 0000000..495c860
--- /dev/null
+++ b/rv/mcu/fe310/words/cold.s
@@ -0,0 +1,51 @@
+
+.equ PRCI_BASE, 0x10008000
+
+.equ PRCI_HFROSCCFG , PRCI_BASE + 0x00
+.equ PRCI_HFXOSCCFG , PRCI_BASE + 0x04
+.equ PRCI_PLLCFG , PRCI_BASE + 0x08
+.equ PRCI_PLLDIV , PRCI_BASE + 0x0C
+.equ PRCI_PROCMONCFG , PRCI_BASE + 0xF0
+
+
+CODEWORD "cold", COLD
+
+ # set up the clock system and make it run
+
+# 1:li x10, PRCI_HFXOSCCFG # 0x10008004
+# lw x11, 0(x10)
+# li x12, 0xC0000000
+# bne x11, x12, 1b
+
+# # Select crystal as main clock source
+
+# li x10, PRCI_PLLCFG
+# li x11, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18) # Reset value | PLLSEL | PLLREFSEL | PLLBYPASS
+# sw x11, 0(x10)
+
+ # set up the clock system and make it run
+
+1:li t0, PRCI_HFXOSCCFG # 0x10008004
+ lw t1, 0(t0)
+ li t2, 0xC0000000
+ bne t1, t2, 1b
+
+ # Select crystal as main clock source
+
+ li t0, PRCI_PLLCFG
+ li t1, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18) # Reset value | PLLSEL | PLLREFSEL | PLLBYPASS
+ sw t1, 0(t0)
+
+ # This is the same as in quit, in order to prepare for whatever the user might want to do within "init".
+
+ la s5, RAM_upper_returnstack
+ la s4, RAM_upper_datastack
+
+# .ifdef initflash
+# call initflash
+# .endif
+
+ la s1, XT_WARM
+ j DO_EXECUTE
+
+END COLD
diff --git a/rv/mcu/fe310/words/constants.s b/rv/mcu/fe310/words/constants.s
new file mode 100755
index 0000000..632e743
--- /dev/null
+++ b/rv/mcu/fe310/words/constants.s
@@ -0,0 +1,44 @@
+#
+# platform_regs.inc: FE310-G002 registers as ASM constants
+#
+# 2021 flabbergast@drak.xyz
+# Unlicense: https://unlicense.org/
+#
+.equ GPIO_BASE, 0x10012000
+.equ INPUT_VAL, 0x00
+.equ INPUT_EN, 0x04
+.equ OUTPUT_EN, 0x08
+.equ OUTPUT_VAL, 0x0C
+.equ PUE, 0x10
+.equ DS, 0x14
+.equ RISE_IE, 0x18
+.equ RISE_IP, 0x1C
+.equ FALL_IE, 0x20
+.equ FALL_IP, 0x24
+.equ HIGH_IE, 0x28
+.equ HIGH_IP, 0x2C
+.equ LOW_IE, 0x30
+.equ LOW_IP, 0x34
+.equ IOF_EN, 0x38
+.equ IOF_SEL, 0x3C
+.equ OUTPUT_XOR, 0x40
+
+CON GPIO , GPIO_BASE
+CON GPIO.INPUT_VAL , GPIO_BASE + INPUT_VAL
+CON GPIO.INPUT_EN , GPIO_BASE + INPUT_EN
+CON GPIO.OUTPUT_EN , GPIO_BASE + OUTPUT_EN
+CON GPIO.OUTPUT_VAL , GPIO_BASE + OUTPUT_VAL
+CON GPIO.PUE , GPIO_BASE + PUE
+CON GPIO.DS , GPIO_BASE + DS
+CON GPIO.RISE_IE , GPIO_BASE + RISE_IE
+CON GPIO.RISE_IP , GPIO_BASE + RISE_IP
+CON GPIO.FALL_IE , GPIO_BASE + FALL_IE
+CON GPIO.FALL_IP , GPIO_BASE + FALL_IP
+CON GPIO.HIGH_IE , GPIO_BASE + HIGH_IE
+CON GPIO.HIGH_IP , GPIO_BASE + HIGH_IP
+CON GPIO.LOW_IE , GPIO_BASE + LOW_IE
+CON GPIO.LOW_IP , GPIO_BASE + LOW_IP
+CON GPIO.IOF_EN , GPIO_BASE + IOF_EN
+CON GPIO.IOF_SEL , GPIO_BASE + IOF_SEL
+CON GPIO.OUTPUT_XOR , GPIO_BASE + OUTPUT_XOR
+
diff --git a/rv/mcu/fe310/words/csr.s b/rv/mcu/fe310/words/csr.s
new file mode 100755
index 0000000..b0dd195
--- /dev/null
+++ b/rv/mcu/fe310/words/csr.s
@@ -0,0 +1,22 @@
+#.option arch, +zicsr
+
+CSR 0x000, ustatus
+CSR 0x004, uie
+CSR 0x005, utvec
+CSR 0x040, uscratch
+CSR 0x041, uepc
+CSR 0x042, ucause
+CSR 0x042, ubadaddress
+
+CSR 0x300, mstatus
+CSR 0x301, misa
+CSR 0x342, mcause
+
+CSR 0xb00, mcycle
+CSR 0xb80, mcycleh
+
+COLON "@cycle", FETCH_CYCLE
+ .word XT_CSR_mcycle
+ .word XT_CSR_mcycleh
+ .word XT_EXIT
+END FETCH_CYCLE
diff --git a/rv/mcu/fe310/words/env-board.s b/rv/mcu/fe310/words/env-board.s
new file mode 100755
index 0000000..8401b5a
--- /dev/null
+++ b/rv/mcu/fe310/words/env-board.s
@@ -0,0 +1,9 @@
+ENVIRONMENT "board", BOARD
+.if TARGET==REDFIVE
+ STRING "sifive_e, Rev B"
+.else
+ STRING "sifive_e"
+.endif
+.word XT_EXIT
+END BOARD
+
diff --git a/rv/mcu/fe310/words/env-cpu.s b/rv/mcu/fe310/words/env-cpu.s
new file mode 100755
index 0000000..79041af
--- /dev/null
+++ b/rv/mcu/fe310/words/env-cpu.s
@@ -0,0 +1,5 @@
+
+ENVIRONMENT "cpu", CPU
+ STRING "RV32IM"
+.word XT_EXIT
+END CPU
diff --git a/rv/mcu/fe310/words/gpio.s b/rv/mcu/fe310/words/gpio.s
new file mode 100755
index 0000000..a44378e
--- /dev/null
+++ b/rv/mcu/fe310/words/gpio.s
@@ -0,0 +1,14 @@
+# -----------------------------------------------------------------------------
+# Labels for a few hardware ports
+# -----------------------------------------------------------------------------
+
+.equ GPIOBASE, 0x10012000
+
+.equ GPIO_VALUE , GPIOBASE + 0x00
+.equ GPIO_INPUT_EN , GPIOBASE + 0x04
+.equ GPIO_OUTPUT_EN , GPIOBASE + 0x08
+.equ GPIO_PORT , GPIOBASE + 0x0C
+.equ GPIO_PUE , GPIOBASE + 0x10
+.equ GPIO_IOF_EN , GPIOBASE + 0x38
+.equ GPIO_IOF_SEL , GPIOBASE + 0x3C
+.equ GPIO_OUT_XOR , GPIOBASE + 0x40
diff --git a/rv/mcu/fe310/words/leds.s b/rv/mcu/fe310/words/leds.s
new file mode 100755
index 0000000..6d0dd87
--- /dev/null
+++ b/rv/mcu/fe310/words/leds.s
@@ -0,0 +1,73 @@
+
+
+# Common Anode tied to Vcc. LEDs shine on low.
+.equ green, 1<<19 # GPIO 19: Green LED
+.equ blue, 1<<21 # GPIO 21: Blue LED
+.equ red, 1<<22 # GPIO 22: Red LED
+
+CODEWORD "led-init", LED_INIT
+ li t3, red|green|blue
+ li t4, GPIO_OUTPUT_EN
+ sw t3, 0(t4)
+ li t3, red|green|blue
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END LED_INIT
+
+CODEWORD "red", RED
+ li t3, blue|green
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END RED
+
+CODEWORD "green", GREEN
+ li t3, blue|red
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END GREEN
+
+CODEWORD "blue", BLUE
+ li t3, red|green
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END BLUE
+
+CODEWORD "white", WHITE
+ li t3, 0
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END WHITE
+
+CODEWORD "yellow", YELLOW
+ li t3, blue
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END YELLOW
+
+CODEWORD "cyan", CYAN
+ li t3, red
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END CYAN
+
+CODEWORD "magenta", MAGENTA
+ li t3, green
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END MAGENTA
+
+CODEWORD "black", BLACK
+ li t3, red|green|blue
+ li t4, GPIO_PORT
+ sw t3, 0(t4)
+NEXT
+END BLACK
+
diff --git a/rv/mcu/fe310/words/mtime.s b/rv/mcu/fe310/words/mtime.s
new file mode 100755
index 0000000..aa30632
--- /dev/null
+++ b/rv/mcu/fe310/words/mtime.s
@@ -0,0 +1,16 @@
+
+.equ CLINT_CTRL_ADDR, 0x02000000
+.equ CLINT_MTIME, 0xBFF8
+.equ CLINT_MTIMECMP, 0x4000
+
+COLON "mtime", MTIME
+ .word XT_DOLITERAL, CLINT_MTIME+CLINT_CTRL_ADDR
+ .word XT_FETCH
+.word XT_EXIT
+END MTIME
+
+COLON "mtimecmp", MTIMECMP
+ .word XT_DOLITERAL, CLINT_MTIMECMP+CLINT_CTRL_ADDR
+.word XT_EXIT
+END MTIMECMP
+
diff --git a/rv/mcu/fe310/words/pll.s b/rv/mcu/fe310/words/pll.s
new file mode 100755
index 0000000..32bfefb
--- /dev/null
+++ b/rv/mcu/fe310/words/pll.s
@@ -0,0 +1,63 @@
+# "PRCI_BASE" : "$10008000",
+# "PRCI_HFROSCCFG":"$1000800",
+# "PRCI_HFXOSCCFG":"$1000804",
+# "PRCI_PLLCFG" :"$1000808",
+# "PRCI_PLLDIV" :"$100080C",
+# "PRCI_PROCMONCFG":"$10008F0"
+
+CONSTANT "sysclock" SYSCLOCK 256
+END SYSCLOCK
+
+CODEWORD "pll", PLL
+
+# PRCI_PLLCFG
+# [02:00] PLL R input clock divider %01 is /2
+# [03:03] RESERVED
+# [09:04] PLL F multiply ratio %11111
+# [11:10] PLL Q divider %11 is /8
+# [15:12] RESERVED
+# [16:16] PLL select
+# [17:17] PLL Reference %1
+# [18:18] PLL bypass
+
+# (16/2)*64*(1/4)=64
+# 9876 5432 1098 7654 3210
+# 000000000000 0111 0000 1101 1111 0001
+# R F Q XTAL
+#.equ TMPPLL, (1) | (31 << 4) | (3 << 10) | (1 << 17) # 64MHz
+.equ TMPPLL, (1) | (31 << 4) | (1 << 10) | (1 << 17) # 256MHz
+#.equ TMPPLL, (1) | (31 << 4) | (2 << 10) | (1 << 17) # 128MHz
+
+ li t3, TMPPLL
+ la t4, PRCI_PLLCFG
+ sw t3, 0(t4)
+
+ li t0, 10000000 # approx 1s, for the hifive1-board
+ li t0, 10000 # approx 0.001s, for the hifive1-board
+1:
+ addi t0,t0,-1
+ bne t0,zero,1b
+
+nope:
+# lw t5, 0(t4)
+# li t6, 1<<31
+# and t3,t5,t6
+# beq t3,zero,nope
+
+ lw t5, 0(t4)
+ bgez t5,nope
+
+
+ lw t5, 0(t4)
+
+# li t6, 1 << 18
+# not a6,t6
+# and t3,t3,a6
+
+ li t6, 1 << 16
+ or t3, t5, t6
+
+ sw t3, 0(t4)
+NEXT
+END PLL
+
diff --git a/rv/mcu/fe310/words/qspidiv.s b/rv/mcu/fe310/words/qspidiv.s
new file mode 100755
index 0000000..c934fed
--- /dev/null
+++ b/rv/mcu/fe310/words/qspidiv.s
@@ -0,0 +1,10 @@
+.equ QSPIBASE , 0x10014000
+.equ QSPIDIV , QSPIBASE + 0
+
+CODEWORD "qspidiv", QSPIDIV
+ li t3, 0x02
+ li t4, QSPIDIV
+ sw t3, 0(t4)
+NEXT
+END QSPIDIV
+
diff --git a/rv/mcu/fe310/words/redvled.s b/rv/mcu/fe310/words/redvled.s
new file mode 100755
index 0000000..1ff91d9
--- /dev/null
+++ b/rv/mcu/fe310/words/redvled.s
@@ -0,0 +1,29 @@
+
+.equ LED , 1 << 5
+
+CODEWORD "+led" , PLUSLED
+ li a1 , GPIO_PORT
+ li a0 , LED
+ amoor.w a2, a0 , 0(a1) # (a1)<-{ {a2<-(a1)} or a0}
+NEXT
+END PLUSLED
+
+CODEWORD "-led" , MINUSLED
+ li a1 , GPIO_PORT
+ li a0 , LED
+ li a3 , -1
+ xor a0 , a0, a3
+ amoand.w a2, a0, 0(a1) # (a1)<-{ {a2<-(a1)} and a0}
+NEXT
+END MINUSLED
+
+CODEWORD "led.init" , LED_INIT
+ li a1 , GPIO_OUTPUT_EN
+ li a0 , LED
+ amoor.w a2, a0 , 0(a1) # (a1)<-{ {a2<-(a1)} or a0}
+
+ li a1 , GPIO_PORT
+ li a0 , LED
+ amoor.w a2, a0 , 0(a1) # (a1)<-{ {a2<-(a1)} or a0}
+NEXT
+END LED_INIT
diff --git a/rv/mcu/fe310/words/serial-emit-pause.s b/rv/mcu/fe310/words/serial-emit-pause.s
new file mode 100644
index 0000000..8f16a67
--- /dev/null
+++ b/rv/mcu/fe310/words/serial-emit-pause.s
@@ -0,0 +1,25 @@
+COLON "serial-key-pause" , SERIAL_KEY_PAUSE
+ .word XT_PAUSE,XT_SERIAL_KEYQ, XT_DOCONDBRANCH, PFA_SERIAL_KEY_PAUSE
+ .word XT_SERIAL_KEY
+ .word XT_EXIT
+END SERIAL_KEY_PAUSE
+
+COLON "serial-emit-pause" , SERIAL_EMIT_PAUSE # ( c -- ) SERIAL: emit c on serial connection or pause if unable
+ .word XT_PAUSE,XT_SERIAL_EMITQ, XT_DOCONDBRANCH, PFA_SERIAL_EMIT_PAUSE
+ .word XT_SERIAL_EMIT
+ .word XT_EXIT
+END SERIAL_EMIT_PAUSE
+
+# # -----------------------------------------------------------------------------
+# CODEWORD "serial-emit?", SERIAL_EMITQ
+# # -----------------------------------------------------------------------------
+# savetos
+# # li t0, UART0_TXDATA
+# # lw t0, 0(t0)
+# # srai t0, t0, 31 # Sign extend the "transmit FIFO full" bit
+# # xori s3, t0, -1 # Invert it
+
+# # a fudge for the moment FIXME
+# li s3, -1
+
+# NEXT
diff --git a/rv/mcu/fe310/words/type0.s b/rv/mcu/fe310/words/type0.s
new file mode 100755
index 0000000..d079fe5
--- /dev/null
+++ b/rv/mcu/fe310/words/type0.s
@@ -0,0 +1,11 @@
+
+COLON "type0", TYPE0
+
+PFA_TYPE0_LOOP:
+ .word XT_DUP,XT_CFETCH,XT_DUP, XT_NOTZEROEQUAL,XT_DOCONDBRANCH,PFA_TYPE0_LEAVE
+ .word XT_DUP,XT_DOLITERAL,10,XT_EQUAL,XT_DOCONDBRANCH,PFA_TYPE0_1,XT_DOLITERAL,13,XT_EMIT
+PFA_TYPE0_1:
+ .word XT_EMIT, XT_1PLUS, XT_DOBRANCH,PFA_TYPE0_LOOP
+PFA_TYPE0_LEAVE:
+ .word XT_2DROP,XT_EXIT
+END TYPE0
diff --git a/rv/mcu/fe310/words/usart.s b/rv/mcu/fe310/words/usart.s
new file mode 100755
index 0000000..b316e60
--- /dev/null
+++ b/rv/mcu/fe310/words/usart.s
@@ -0,0 +1,140 @@
+.equ UART0BASE, 0x10013000
+
+.equ UART0_TXDATA , UART0BASE + 0x00
+.equ UART0_RXDATA , UART0BASE + 0x04
+.equ UART0_TXCTRL , UART0BASE + 0x08
+.equ UART0_RXCTRL , UART0BASE + 0x0C
+.equ UART0_IE , UART0BASE + 0x10
+.equ UART0_IP , UART0BASE + 0x14
+.equ UART0_DIV , UART0BASE + 0x18
+
+# ======================================================================
+
+CODEWORD "+usart", INIT_USART
+
+ # UART RX/TX are selected IOF_SEL on Reset. Set IOF_EN bits.
+
+ # Clear IOF_SEL bits 16,17 first → selects IOF0 (UART0 TX/RX)
+ li t0, GPIO_IOF_SEL
+ lw t2, 0(t0)
+ li t1, ~((1<<17)|(1<<16)) # mask with bits 16,17 clear
+ and t2, t2, t1
+ sw t2, 0(t0)
+
+ # Set IOF_EN bits 16,17 — read/modify/write to preserve other peripherals
+ li t0, GPIO_IOF_EN
+ lw t2, 0(t0)
+ li t1, (1<<17)|(1<<16)
+ or t2, t2, t1
+ sw t2, 0(t0)
+
+ # Set baud rate
+
+ li t0, UART0_DIV
+
+ li t1, 2222-1 # 256 MHz / 115200 Baud = 2222.2
+
+# li a1, 6667-1 # 256 MHz / 38400 Baud = 6666.67
+# li t1, 1111-1 # 128 MHz / 115200 Baud = 1111.1
+# li t1, 555-1 # 64 MHz / 115200 Baud = 555.55
+# li t1, 139-1 # 16 MHz / 115200 Baud = 138.89
+# li t1, 417-1 # 16 MHz / 38400 Baud = 416,67
+# li t1, 3300-1 # 128MHz 38400 Baud
+
+ sw t1, 0(t0)
+
+ # Enable transmit
+
+ li t0, UART0_TXCTRL
+ li t1, 1
+ sw t1, 0(t0)
+
+ # Enable receive
+
+ li t0, UART0_RXCTRL
+ li t1, 1
+ sw t1, 0(t0)
+
+ # initialise the one cell buffer with -1
+
+ la t0, PFA_SERIAL_LASTCHAR
+ lw t0, 0(t0)
+ li t1, -1
+ sw t1, 0(t0)
+
+ NEXT
+END INIT_USART
+
+ VARIABLE "serial-lastchar", SERIAL_LASTCHAR # ( -- addr )
+
+# -----------------------------------------------------------------------------
+ CODEWORD "serial-key", SERIAL_KEY
+# -----------------------------------------------------------------------------
+ savetos
+ la t1, PFA_SERIAL_LASTCHAR
+ lw t1, 0(t1)
+ lw s3, 0(t1)
+
+ li t0, -1
+ sw t0, 0(t1)
+
+ NEXT
+END SERIAL_KEY
+
+# -----------------------------------------------------------------------------
+ CODEWORD "serial-key?", SERIAL_KEYQ
+# -----------------------------------------------------------------------------
+
+ savetos
+
+ # Check buffer for waiting character
+
+ la t1, PFA_SERIAL_LASTCHAR
+# lw t0, 0(t1)
+ lw t1 , 0(t1)
+ lw t0 , 0(t1)
+ srai s3, t0, 31 # Sign extend the "receive FIFO empty" bit
+ beq s3, zero, 1f
+
+ # No character waiting in the buffer variable. Check UART for new character:
+
+ li t1, UART0_RXDATA
+ lw t0, 0(t1)
+ la t1, PFA_SERIAL_LASTCHAR
+ lw t1, 0(t1)
+ sw t0, 0(t1)
+
+ srai s3, t0, 31 # Sign extend the "receive FIFO empty" bit
+
+1:
+ xori s3, s3, -1
+ NEXT
+END SERIAL_KEYQ
+
+# -----------------------------------------------------------------------------
+ CODEWORD "serial-emit", SERIAL_EMIT
+# -----------------------------------------------------------------------------
+
+SERIAL_EMIT_WAIT:
+ li t0, UART0_TXDATA
+ lw t0, 0(t0)
+ blt t0,zero, SERIAL_EMIT_WAIT
+
+ li t1, UART0_TXDATA
+ sw s3, 0(t1)
+ loadtos
+
+ NEXT
+END SERIAL_EMIT
+
+# -----------------------------------------------------------------------------
+ CODEWORD "serial-emit?", SERIAL_EMITQ
+# -----------------------------------------------------------------------------
+ savetos
+ li t0, UART0_TXDATA
+ lw t0, 0(t0)
+ srai t0, t0, 31 # Sign extend the "transmit FIFO full" bit
+ xori s3, t0, -1 # Invert it
+
+ NEXT
+END SERIAL_EMITQ
diff --git a/rv/mcu/fe310/words/warm.s b/rv/mcu/fe310/words/warm.s
new file mode 100644
index 0000000..ddda20d
--- /dev/null
+++ b/rv/mcu/fe310/words/warm.s
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-3.0-only
+COLON "warm", WARM /* ( -- ) high level part of the boot sequence, VM is running */
+/* This is the high level part of the boot sequence with the VM already running.
+ After initializations calls TURNKEY and if TURNKEY returns calls QUIT
+ to enter the endless outer interpreter loop.
+ When called from within forth it is the equivalent of a soft RESET.
+
+ MCUs may override this to inject additional initialization at specific points
+*/
+
+ /* initialize values and defers to their defaults */
+ .word XT_INIT_RAM
+
+.if WANT_DEBUGGER == YES
+ /* initialize debugger to disabled state,
+ needs UP initialized so after INIT_RAM! */
+ .word XT_DEBUGMINUS
+.endif
+
+# /* initialize pvalue system */
+# .word XT_QFIRST_BOOT, XT_DOCONDBRANCH, 1f
+# .word XT_PVARENA1, XT_DOTO, XT_PVARENA, XT_PV_RESET_HARD
+# .word XT_DOBRANCH, 2f
+# 1: /* else */
+# .word XT_PV_INIT
+# 2:
+# /* check and mark first-boot done */
+# .word XT_QFIRST_BOOT, XT_DOCONDBRANCH, 1f
+# .word XT_FIRST_BOOT_DONE
+# 1:
+# /* find the end of the used flash and set DP;
+# do it after first-boot-done so that the first-boot page is erased already */
+# .word XT_INIT_DP_FLASH
+
+ .word XT_LBRACKET
+ .word XT_TURNKEY
+ .word XT_QUIT
+ .word XT_EXIT
+END WARM
diff --git a/tools/socket-shell.py b/tools/socket-shell.py
new file mode 100755
index 0000000..8a66600
--- /dev/null
+++ b/tools/socket-shell.py
@@ -0,0 +1,1386 @@
+#! /usr/local/bin/python3
+# SPDX-License-Identifier: GPL-3.0-only
+#! /Library/Frameworks/Python.framework/Versions/3.9/bin/python3
+#
+# pySerial based upload & interpreter interaction module for amforth.
+#
+# Copyright 2011 Keith Amidon (camalot@picncipark.org)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# Patcher remarks:
+# ================
+#
+# This uploader saves dictionary space and words clutter by substituting
+# uC register names and application constants with numbers. The
+# appl_defs.frt (Forth) file in the application's local directory
+# provides the constant definitions. In appl_defs.frt put each constant
+# on a line of its own. The first line, if it begins with a backslash
+# comment, would be echoed to the screen when uploading the Forth
+# code. It is recommended to place in appl_defs.frt global constant
+# definitions which would affect compilation of the library and the
+# project code. For example:
+#
+# \ Project Name
+# $d5 constant CRC8MSB
+# 10 constant max_number_of_users
+#
+# Invoke the shell with the argument --log log.frt to collect the lines
+# which were uploaded to the AmForth system that received an " ok"
+# response (log.frt is just a file-name example). This file can later be
+# uploaded to another system using a tool simpler than the shell. Leave
+# the shell by #exit to close log.frt properly.
+#
+# Invoke the shell with the argument --rtscts to enable serial port
+# RTS/CTS hardware handshake connection.
+#
+# =====================================================================
+# DOCUMENTATION
+# =====================================================================
+# This module module may be used as a script or imported for use as a
+# part of a larger python program.
+#
+# Script Usage
+# ------------
+# When used as a script this module provides two main functions, the
+# ability to reliably upload files to the amforth interpreter and an
+# interpreter interaction mode with line editing, word completion and
+# previous input history. For information on how to access these
+# features when invoking the module as a script, execute it with the
+# --help option and read the following sections on the interaction
+# protocol and local directives.
+#
+#
+# Interaction Protocol
+# --------------------
+# The amforth interaction protocol used by this module is to send a
+# line to amforth character by character, reading the echos as quickly
+# as possible. Once the entire line has been sent it then reads the
+# response up until the " ok" prompt or a prompt that looks like an
+# error response. The character by character handling of echos
+# appears to eliminate unexpected device resets when compared to the
+# line by line method used by previous tools, possibly by eliminating
+# the possibility of serial tx overrun on the device.
+#
+# To further optimize interaction with the device lines are evaluated
+# before sending and redundant whitespace is compressed out. Lines
+# which are all whitespace or whitespace and comments are not sent and
+# the next line is handled.
+#
+#
+# Local Directives
+# ----------------
+# A number of special directives are supported which instruct the
+# script to do something and are handled locally without being sent to
+# amforth. Directives may be specified within comments or outside
+# comments as controlled by the "#directive" directive. They must be
+# the only contents of a line or they will be ignored. The directives
+# include:
+#
+# #include
+# Upload the named before proceeding further.
+#
+# #require
+# Like #include but would skip if was already uploaded
+# during the shell session.
+#
+# #cd
+# Change the current local directory to the location specified.
+# During uploads, this directive affects the current file and
+# files it includes. Once the current file is complete the old
+# value will be restored.
+#
+# #directive
+# Change how directives are discovered. The valid values for
+# are:
+# none : Stop looking for any directives
+# commented : Only look for directives within comments
+# Commented directives must be the first word of the
+# comment. The remaining text in the comment is the
+# argument provided to the directive. There must
+# not be any other non-whitespace text other than
+# the comment start and (if required) end characters
+# and the directive and any directive argument on a
+# commented directive line. If any other text is
+# present on the line an error will be generated.
+# uncommented : Only look for directives outside comments.
+# Uncommented directives must be the first word of a
+# line and extend to the end of the line. If a
+# directive name exists in a subsequent word of a
+# line it will be sent to the interpreter as a word
+# like any other.
+# all : Allow both commented and uncommented directives.
+# This is the default.
+# During uploads, this directive affects the current file and
+# files it includes. Once the current file is complete the old
+# value will be restored.
+#
+# #timeout
+# Change the timeout value to seconds. Fractional
+# values are supported. During uploads, this directive affects
+# the current file and files it includes. Once the current file
+# is complete the old value will be restored.
+#
+# #timeout-next
+# Change the timeout value for the next line sent to the
+# interpreter to seconds. Fractional values are
+# supported. The timeout returns to its previous value after
+# the next line is sent to the interpreter. If this directive
+# is encountered as the very last line of an upload file it will
+# have no effect.
+#
+# #error-on-output []
+# Controls whether an error is generated if unexpected output
+# occurs during an upload. The default is yes. This directive
+# can not be used in interactive mode as it would not have any
+# effect. During uploads it affects the rest of the current file
+# and any files it includes. The argument is optional. If not
+# given it is assumed to be "yes".
+#
+# #ignore-error []
+# Ignore any error that occurs later in the current upload file
+# or a file it includes. The argument is optional. If given
+# the behavior is set as specified. If not given it is assumed
+# to be "yes".
+#
+# #ignore-error-next []
+# Ignore any error that occurs on the next line. The argument
+# is optional. If given the behavior is set as specified. If
+# not given it is assumed to be "yes".
+#
+# #expect-output-next []
+# Expect specific output on the next line. The argument is
+# optional. If it is not specified a default regular expression
+# of ".*" (match everything) is assumed. This overrides the
+# #error-on-output directive. An error is raised if the output
+# doesn't match the regular expression. It will be ignored if
+# #ignore-error is yes. Use of this directive without an
+# argument is the way to prevent an error on output when
+# #error-on-output is yes
+#
+# #start-string-word
+# Add a word that starts a string. The string will end when a
+# double quote character is read.
+#
+# #quote-char-word
+# Add a word that quotes the immediately next word
+#
+# #interact
+# Start an interactive session before proceeding with file upload.
+# This only makes sense during a file upload.
+#
+# #edit []
+# Edit a file. The filename is optional. If it is provided the
+# named file will be edited. If it is not provided and the last
+# upload ended in an error the file that had the error will be
+# edited at the location of the error. If there was no previous
+# upload or the last upload completed successfully but an #edit
+# directive was previously issued with a filename, edit the file
+# previously named. Finally, if none of these apply an error is
+# printed. The editor used can be specified with the --editor
+# option when starting the program or through the EDITOR
+# environment variable.
+#
+# #update-words
+# This directive is only available in an interactive session.
+# It cause the interaction code to reload the list of words used
+# for completion from the amforth interpreter. Typically it is
+# not required as words are updated automatically when the
+# session starts and any time a file is uploaded as a results of
+# a #include directive. The case where it is required is when
+# completion is needed for words defined interactively during
+# the session.
+#
+# #update-cpu
+# This directive is only available in an interactive session.
+# It causes the interaction code to read the controller name
+# from the device and tries to load a specific python module
+# which contains names for registers and addresses. These names
+# can be used in forth code and get replace with the corresponding
+# numbers.
+#
+# #exit
+# Exit an interactive session or the current upload immediately.
+# If encountered during an upload, no further lines from the
+# file will be processed.
+#
+#
+# Programmatic Usage
+# ------------------
+# For programmatic usage, a single class named AMForth is provided.
+# It can be instantiated with no arguments but typically a serial port
+# device and port speed will be provided as the defaults are unlikely
+# to be correct.
+#
+# Once an instance is obtained, and connected the high-level entry
+# points are the "upload_file" and "interact" methods, the former
+# uploading a file to the AMForth interperter and the latter providing
+# an interative interpreter shell with command history and word
+# completion. These methods provide progress information in various
+# cases by calling the function stored in the "progress_callback"
+# property with three arguments, the type of progress being reported,
+# a line number if available (otherwise it is None) and a message with
+# further information. The default progress callback prints this
+# information to the screen in a terse format. Other programs may
+# wish to replace this with their own progress presentation function.
+#
+# Low-level interaction with the AMForth interpreter would typically
+# use the "send_line" and "read_response" methods. Before these can
+# be used the serial connection must be established. The
+# serial_connected property indicates whether a connection currently
+# exists. A good way to obtain a connection and rule out errors in
+# serial communication is to call "find_prompt" which ensures the
+# existence of a serial connection and sends a newline to the AMForth
+# interperter and watches for the echo. This is usually the best way
+# of establishing a connection but the "serial_connect" method will
+# open a connection without sending anything if that is required.
+#
+# Elimination of whitespace and discovery of directives (see below) is
+# provided through the "preprocess_line" method and directives that
+# have common implementations can be handled with the
+# "handle_common_directives" method.
+
+# TODO: - Update comments on most functions explaining what they do.
+
+import argparse
+import atexit
+import copy
+import glob
+import os
+import re
+import readline
+import serial
+import serial.rfc2217
+
+from io import StringIO
+import subprocess
+import sys
+import traceback
+
+class AMForthException(Exception):
+ pass
+
+class Behaviors(object):
+ """Simple class for storing configurable processing behaviors"""
+ def __init__(self):
+ self.working_directory = os.getcwd()
+ self.filename = None
+ self.timeout = 15.0
+ self.quote_char_words = ["[char]", "char"]
+ self.start_string_words = ['s"', '."', 'abort"']
+ self.error_on_output = True
+ self.ignore_errors = False
+ self.directive_uncommented = True
+ self.directive_commented = True
+ self.expected_output_regexp = None
+
+ @property
+ def directive_config(self):
+ "Get the current directive configuration"
+ if self.directive_uncommented:
+ if self.directive_commented:
+ return "all"
+ else:
+ return "uncommented"
+ else:
+ if self.directive_commented:
+ return "commented"
+ else:
+ return "none"
+
+ @directive_config.setter
+ def directive_config(self, value):
+ "Set the directive configuration"
+ if value == "none":
+ self.directive_uncommented = False
+ self.directive_commented = False
+ elif value == "all":
+ self.directive_uncommented = True
+ self.directive_commented = True
+ elif value == "uncommented":
+ self.directive_uncommented = True
+ self.directive_commented = False
+ elif value == "commented":
+ self.directive_uncommented = False
+ self.directive_commented = True
+ else:
+ raise AMForthException("Unknown directive config: %s" % value)
+
+
+class BehaviorManager(object):
+ """Class for determining currently configured behavior
+
+ This class manages the lifetime of behaviors established through
+ configuration options and directives to minimize the impact of
+ that support on the AMForth class. """
+ def __init__(self):
+ self.default_behavior = Behaviors()
+ self.clear()
+
+ def clear(self):
+ "Clear out accumulated behavior"
+ self._next_line_behavior = None
+ self._current_line_behavior = None
+ self._file_behaviors = []
+
+ @property
+ def current_behavior(self):
+ """The behavior currently in effect"""
+ if self._current_line_behavior:
+ return self._current_line_behavior
+ elif self._file_behaviors:
+ return self._file_behaviors[0]
+ else:
+ return self.default_behavior
+
+ def advance_line(self):
+ """Call when changing to the next line"""
+ self._current_line_behavior = self._next_line_behavior
+ self._next_line_behavior = None
+
+ def push_file(self, filename):
+ """Call when starting processing a new nested file"""
+ behavior = copy.deepcopy(self.current_behavior)
+ behavior.filename = filename
+ self._file_behaviors.insert(0, behavior)
+
+ def pop_file(self):
+ """Call when returning from a nested file"""
+ del(self._file_behaviors[0])
+
+ @property
+ def next_line_behavior(self):
+ """The behavior to use for the next line"""
+ return self._next_line_behavior
+
+ @next_line_behavior.setter
+ def next_line_behavior(self, behavior):
+ self._next_line_behavior = behavior
+
+ @property
+ def current_file_behavior(self):
+ """The behavior for the current file.
+
+ Will raise an exception if there is no file currently."""
+ return self._file_behaviors[0]
+
+ @current_file_behavior.setter
+ def current_file_behavior(self, behavior):
+ self._file_behaviors[0] = behavior
+
+
+class AMForth(object):
+ "Class for interacting with the AMForth interpreter"
+
+ amforth_error_cre = re.compile(r" \?\? -\d+ \d+ \r\n> $")
+ upload_directives = [
+ "#cd", "#require", "#include", "#directive", "#ignore-error",
+ "#ignore-error-next", "#error-on-output", "#expect-output-next",
+ "#string-start-word", "#quote-char-word",
+ "#timeout", "#timeout-next", "#interact", "#exit"
+ ]
+ interact_directives = [
+ "#cd", "#edit", "#require", "#include", "#directive", "#ignore-error",
+ "#error-on-output", "#string-start-word", "#quote-char-word",
+ "#timeout", "#timeout-next", "#update-words", "#exit",
+ "#update-cpu", "#update-files"
+ ]
+ # standard words are usually uppercase, but amforth needs
+ # them in lowercase.
+ stdwords = [
+ # *** Wordset BLOCK
+ "BLK","BLOCK","BUFFER","EVALUATE","FLUSH","LOAD","SAVE-BUFFERS",
+ "UPDATE",
+ # *** Wordset BLOCK-EXT
+ "EMPTY-BUFFERS","LIST","REFILL","SCR","THRU",
+ # *** Wordset CORE
+ "#S","*/MOD","+LOOP","/MOD","0<","0=","1+","1-","2!",
+ "2*","2/","2@","2DROP","2DUP","2OVER","2SWAP",">BODY",
+ ">IN",">NUMBER",">R","?DUP","ABORT","ABORT\"","ABS",
+ "ACCEPT","ALIGN","ALIGNED","ALLOT","AND","BASE","BEGIN",
+ "BL","C!","C,","C@","CELL+","CELLS","CHAR","CHAR+",
+ "CHARS","CONSTANT","COUNT","CR","CREATE","DECIMAL",
+ "DEPTH","DO","DOES>","DROP","DUP","ELSE","EMIT","ENVIRONMENT?",
+ "EVALUATE","EXECUTE","EXIT","FILL","FIND","FM/MOD",
+ "HERE","HOLD","I","IF","IMMEDIATE","INVERT","J","KEY",
+ "LEAVE","LITERAL","LOOP","LSHIFT","M*","MAX","MIN",
+ "MOD","MOVE","NEGATE","OR","OVER","POSTPONE","QUIT",
+ "R>","R@","RECURSE","REPEAT","ROT","RSHIFT","S\"","S>D",
+ "SIGN","SM/REM","SOURCE","SPACE","SPACES","STATE","SWAP",
+ "THEN","TYPE","U.","U<","UM*","UM/MOD","UNLOOP","UNTIL",
+ "VARIABLE","WHILE","WORD","XOR","[CHAR]",
+ # *** Wordset CORE-EXT
+ ".R","0<>",
+ "0>","2>R","2R>","2R@",":NONAME","?DO","AGAIN","C\"",
+ "CASE","COMPILE,","ENDCASE","ENDOF","ERASE","FALSE",
+ "HEX","MARKER","NIP","OF","PAD","PARSE","PICK","REFILL",
+ "RESTORE-INPUT","ROLL","SAVE-INPUT","SOURCE-ID","TO",
+ "TRUE","TUCK","U.R","U>","UNUSED","VALUE","WITHIN",
+ "[COMPILE]",
+ # *** Wordset CORE-EXT-obsolescent
+ "#TIB","CONVERT","EXPECT","QUERY","SPAN",
+ "TIB",
+ # *** Wordset DOUBLE
+ "2CONSTANT","2LITERAL","2VARIABLE","D+","D-",
+ "D.","D.R","D0<","D0=","D2*","D2/","D<","D=","D>S",
+ "DABS","DMAX","DMIN","DNEGATE","M*/","M+",
+ # *** Wordset DOUBLE-EXT
+ "2ROT","DU<",
+ # *** Wordset EXCEPTION
+ "CATCH","THROW",
+ # *** Wordset EXCEPTION-EXT
+ "ABORT","ABORT\"",
+ # *** Wordset FACILITY
+ "AT-XY","KEY?","PAGE",
+ # *** Wordset FACILITY-EXT
+ "EKEY","EKEY>CHAR","EKEY?","EMIT?","MS","TIME&DATE",
+ # *** Wordset FILE
+ "BIN","CLOSE-FILE","CREATE-FILE","DELETE-FILE","FILE-POSITION",
+ "FILE-SIZE","INCLUDE-FILE","INCLUDED","OPEN-FILE","R/O",
+ "R/W","READ-FILE","READ-LINE","REPOSITION-FILE","RESIZE-FILE",
+ "S\"","SOURCE-ID","W/O","WRITE-FILE","WRITE-LINE",
+ # *** Wordset FILE-EXT
+ "FILE-STATUS",
+ "FLUSH-FILE","REFILL","RENAME-FILE",
+ # *** Wordset FLOAT
+ ">FLOAT","D>F",
+ "F!","F*","F+","F-","F/","F0<","F0=","F<","F>D","F@",
+ "FALIGN","FALIGNED","FCONSTANT","FDEPTH","FDROP","FDUP",
+ "FLITERAL","FLOAT+","FLOATS","FLOOR","FMAX","FMIN",
+ "FNEGATE","FOVER","FROT","FROUND","FSWAP","FVARIABLE",
+ "REPRESENT",
+ # *** Wordset FLOAT-EXT
+ "DF!","DF@","DFALIGN","DFALIGNED","DFLOAT+",
+ "DFLOATS","F**","F.","FABS","FACOS","FACOSH","FALOG",
+ "FASIN","FASINH","FATAN","FATAN2","FATANH","FCOS","FCOSH",
+ "FE.","FEXP","FEXPM1","FLN","FLNP1","FLOG","FS.","FSIN",
+ "FSINCOS","FSINH","FSQRT","FTAN","FTANH","F~","PRECISION",
+ "SET-PRECISION","SF!","SF@","SFALIGN","SFALIGNED","SFLOAT+",
+ "SFLOATS",
+ # *** Wordset LOCAL
+ "(LOCAL)","TO",
+ # *** Wordset LOCAL-EXT
+ "LOCALS|",
+ # *** Wordset MEMORY
+ "ALLOCATE","FREE",
+ "RESIZE",
+ # *** Wordset SEARCH
+ "DEFINITIONS","FIND","FORTH-WORDLIST","GET-CURRENT",
+ "GET-ORDER","SEARCH-WORDLIST","SET-CURRENT","SET-ORDER",
+ "WORDLIST",
+ # *** Wordset SEARCH-EXT
+ "ALSO","FORTH","ONLY","ORDER","PREVIOUS",
+ # *** Wordset STRING
+ "-TRAILING","/STRING","BLANK","CMOVE","CMOVE>","COMPARE",
+ "SEARCH","SLITERAL",
+ # *** Wordset TOOLS
+ ".S","DUMP","SEE","WORDS",
+ # *** Wordset TOOLS-EXT
+ ";CODE",
+ "AHEAD","ASSEMBLER","BYE","CODE","CS-PICK","CS-ROLL",
+ "EDITOR","STATE","[ELSE]","[IF]","[THEN]",
+ # *** Wordset TOOLS-EXT-obsolescent
+ "FORGET",
+ # *** Tester wordset
+ "T{", "}T",
+ ]
+ def __init__(self, serial_port="/dev/amforth", rtscts=False, speed=38400):
+ self.debug = False
+ self.max_line_length = 100
+ self.progress_callback = self.print_progress
+ self.editor = None
+ self._serial_port = serial_port
+ self._serial_rtscts = rtscts
+ self._serial_speed = speed
+ self._serialconn = None
+ self._readline_initialized = False
+ self._amforth_dp = None
+ self._filedirs = {}
+ self._search_path = []
+ self._uploaded = set()
+ self._update_uploaded = False
+ self._amforth_words = []
+ self._amforth_regs = {}
+ self._amforth_cpu = ""
+ self._last_error = ()
+ self._last_edited_file = None
+ self._config = BehaviorManager()
+ if "AMFORTH_LIB" in os.environ:
+ self._search_list = os.environ["AMFORTH_LIB"].split(":")
+ else:
+ self._search_list=["."]
+
+ # define application constants to substitute
+ try:
+ ad_file = open("appl_defs.frt")
+ ad_line = ad_file.readline()
+ ad_mat = re.match(r"^\\\\\s+(\S.*)\n", ad_line)
+ if ad_mat:
+ self.progress_callback("Information", None, "appl_defs: " + ad_mat.group(1))
+ ad_pat=re.compile(r"^\s*(\S+)\s+constant\s+(\S+)\s")
+ ad_def = {}
+ while ad_line:
+ ad_mat = ad_pat.match(ad_line)
+ if ad_mat:
+ ad_def[ad_mat.group(2)] = ad_mat.group(1)
+ ad_line = ad_file.readline()
+ except:
+ ad_def = {}
+ self.progress_callback("Information", None, "appl_defs: %d loaded" % len(ad_def))
+ self._appl_defs = ad_def
+
+ @property
+ def serial_port(self):
+ "Serial port device attached to AMForth"
+ return self._serial_port
+
+ @serial_port.setter
+ def serial_port(self, value):
+ """Set the serial port device attached to AMForth
+
+ If the value provided is different than the current value any
+ existing serial connection will be closed and a new connection
+ opened."""
+ if self._serial_port != value:
+ self._serial_port = value
+ self.serial_reconnect()
+
+ @property
+ def serial_rtscts(self):
+ "RTS/CTS enable of serial connection to AMForth"
+ return self._serial_rtscts
+
+ @serial_rtscts.setter
+ def serial_rtscts(self, value):
+ if self._serial_rtscts != value:
+ self._serial_rtscts = value
+ self.serial_reconnect()
+
+ @property
+ def serial_speed(self):
+ "Speed of the serial connection to AMForth"
+ return self._serial_speed
+
+ @serial_speed.setter
+ def serial_speed(self, value):
+ if self._serial_speed != value:
+ self._serial_speed = value
+ self.serial_reconnect()
+
+ @property
+ def serial_connected(self):
+ "Boolean status for whether currently connected to AMForth"
+ return self._serialconn is not None
+
+ def main(self):
+ "Main function called when module is used as a script"
+ upload_files, interact = self.parse_arg()
+ try:
+ for fn in upload_files:
+ if fn == "-":
+ self.interact()
+ else:
+ self.upload_file(fn, install=True)
+ if interact:
+ self.interact()
+ except AMForthException:
+ return 1
+ except KeyboardInterrupt:
+ print( "\nBye bye")
+ except Exception as e:
+ print( "\n---- Unexpected exception ----")
+ traceback.print_exc()
+ return 1
+ finally:
+ self.serial_disconnect()
+ if self._log:
+ self._log.close()
+ return 0
+
+ def parse_arg(self):
+ "Argument parsing used when module is used as a script"
+ parser = argparse.ArgumentParser(description="Interact with AMForth",
+ epilog="""
+The environment variable AMFORTH_LIB can be set with to a colon (:) separated
+list of directories that are recursivly searched for file names. If not set,
+the current work directory is used instead.
+
+The script assumes to be located in the standard amforth installation under
+the tools/ directory. It uses files from the core/devices directories for
+additional definitions (e.g. register names)
+"""
+ )
+ parser.add_argument("--timeout", "-t", action="store",
+ type=float, default=15.0,
+ help="Response timeout (seconds, float value)")
+ parser.add_argument("--port", "-p", action="store",
+ type=str, default=self.serial_port, help="Serial port name")
+ parser.add_argument("--rtscts", action="store_true",
+ default=self.serial_rtscts, help="Serial port RTS/CTS enable")
+ parser.add_argument("--speed", "-s", action="store",
+ type=int, default=self.serial_speed, help="Serial port speed")
+ parser.add_argument("--log", type=argparse.FileType('w'),
+ help="Uploaded Forth log-file")
+ parser.add_argument("--line-length", "-l", action="store",
+ type=int, default=self.max_line_length,
+ help="Maximum length of amforth input line")
+ parser.add_argument("--interact", "-i", action="store_true",
+ help="Enter interactive prompt after upload")
+ parser.add_argument("--directive", "-d", action="store",
+ default="all",
+ help="Local directive configuration (where found)")
+ parser.add_argument("--editor", action="store",
+ default = os.environ.get("EDITOR", None),
+ help="Editor to use for #edit directive")
+ parser.add_argument("--no-error-on-output", action="store_true",
+ help="Indicate an error if upload causes output")
+ parser.add_argument("--ignore-error", action="store_true",
+ help="Ignore errors during upload (not recommended)")
+ parser.add_argument("--debug-serial", action="store_true",
+ help="Output extra info about serial transfers in stderr")
+ parser.add_argument("--Include", "-I", action="append",
+ help="Add Include directory")
+ parser.add_argument("--uploaded-wl", "-U", action="store_true", default=False,
+ help="Keep the list of uploaded filenames in the dictionary.")
+ parser.add_argument("--no-regsub", "-X", action="store_true", default=False,
+ help="Do NOT replace mcu registers with their values.")
+
+ parser.add_argument("files", nargs="*", help="may be found via the environment variable AMFORTH_LIB")
+ arg = parser.parse_args()
+ self.noregsub = arg.no_regsub
+ self.debug = arg.debug_serial
+ self.max_line_length = arg.line_length
+ self._serial_port = arg.port
+ self._serial_rtscts = arg.rtscts
+ self._serial_speed = arg.speed
+ self._log = arg.log
+ self._update_uploaded = arg.uploaded_wl
+ self.editor = arg.editor
+ if arg.Include:
+ self._search_list.extend(arg.Include)
+ behavior = self._config.current_behavior
+ behavior.error_on_output = not arg.no_error_on_output
+ behavior.directive_config = arg.directive
+ behavior.timeout = arg.timeout
+ behavior.ignore_errors = arg.ignore_error
+ return arg.files, (arg.interact or len(arg.files) == 0)
+
+ def serial_connect(self, port=None, rtscts=None, speed=None):
+ """Connect to AMForth on a serial port
+
+ The port and speed argument are optional. If not specified
+ the current values set in the object are used. These will be
+ the defaults if the have not been changed. If either is
+ specified corresponding property of the instance will be
+ updated to the new value.
+
+ This is safe to call even if a connection already exists as
+ existing an existing connection will be closed before the new
+ connection is made."""
+ if port != None:
+ self.serial_port = port
+ if rtscts != None:
+ self.serial_rtscts = rtscts
+ if speed != None:
+ self.serial_speed = speed
+ if self._serialconn:
+ self.serial_disconnect()
+ try:
+ timeout = self._config.current_behavior.timeout
+ self._serialconn = serial.serial_for_url("socket://localhost:4444")
+# self._serialconn = serial.serial_for_url("socket://localhost:4321?logging=debug")
+
+# self._serialconn = serial.Serial(None, #self.serial_port,
+# self.serial_speed,
+# serial.EIGHTBITS,
+# serial.PARITY_NONE,
+# serial.STOPBITS_ONE,
+# timeout, False,
+# self.serial_rtscts,
+# None, False)
+# self._serialconn.port=self.serial_port
+# self._serialconn.rts=True
+# self._serialconn.dtr=True
+# self._serialconn.open()
+ except serial.SerialException as e:
+ raise AMForthException("Serial port connect failure: %s" % str(e))
+
+ def serial_disconnect(self):
+ """Disconnect the serial connection to AMForth
+
+ This is safe to call even if there is currently no connection."""
+ if self._serialconn:
+ self._serialconn.close()
+ self._serialconn = None
+
+ def serial_reconnect(self):
+ """Reconnect the serial connection to AMForth
+
+ This is the same as calling serial_connect while there is an
+ existing connection. It is provided to make the clear when
+ the intent is to re-establish an existing connection (usually
+ to apply new settings) versus creating a new connectoion."""
+ self.serial_connect()
+
+ def find_prompt(self):
+ "Attempt to find a prompt by sending a newline and verifying echo"
+ if not self.serial_connected:
+ self.serial_connect()
+ # Use a short timeout to quickly detect if can't communicate
+ self._serialconn.timeout = 2.0
+ try:
+ try:
+ self.send_line("\n") # Get empty line echo to make sure ready
+ self.read_response() # Throw away the response.
+ except serial.SerialException as e:
+ self.progress_callback("Error", None, str(e))
+ raise AMForthException("Failed to get prompt: %s" % str(e))
+ finally:
+ # Restore the current timeout
+ self._serialconn.timeout = self._config.current_behavior.timeout
+
+ def upload_file(self, filename, install=False):
+ if not install and filename in self._uploaded:
+ return False
+ else:
+ self._uploaded.add(filename)
+ self._update_files()
+ if os.path.dirname(filename):
+ fpath=filename
+ self.progress_callback("Information", None, "using "+ filename+" verbatim")
+ else:
+ if not filename in self._filedirs:
+ self.progress_callback("Error", None, "file "+ filename+" not found in search path")
+ raise AMForthException("file " + filename + " not found in search path")
+ if len(self._filedirs[filename])!=1:
+ # oops, too many files or no one at all found?
+ self.progress_callback("Error", None, "Wrong # of file " + filename + " found in search path") # add this line above the one below
+ raise AMForthException("Wrong # of file occurances: " + filename + " ("+str(len(self._filedirs[filename]))+")\n\t"+"\n\t".join(self._filedirs[filename]))
+ self.progress_callback("Information", None, "using "+ filename+" from"+ self._filedirs[filename][0])
+ fpath = os.path.join(self._filedirs[filename][0], filename)
+ self._config.push_file(fpath)
+ fdir=os.path.dirname(fpath)
+ print ("**** " + self._config.current_behavior.working_directory)
+ if os.path.isabs(fdir):
+ dirpath = os.path.normpath(fdir)
+ else:
+ oldpath = self._config.current_behavior.working_directory
+ dirpath = os.path.normpath(os.path.join(oldpath, fdir))
+ self._config.current_behavior.working_directory = dirpath
+
+ try:
+ try:
+ self.find_prompt()
+ except AMForthException as e:
+ self.progress_callback("Error", None, str(e))
+ raise
+ if self._amforth_cpu=="":
+ self._update_cpu()
+ self._update_files()
+ self.progress_callback("File", None, fpath)
+ try:
+ with open(fpath, "r") as f:
+ self._send_file_contents(f)
+ except (OSError, IOError) as e:
+ self.progress_callback("Error", None, str(e))
+ raise AMForthException("Unknown file: " + fpath)
+ self._last_error = ()
+ finally:
+ print ("**** " + self._config.current_behavior.working_directory)
+ self._config.pop_file()
+ self._serialconn.timeout = self._config.current_behavior.timeout
+ try:
+ os.chdir(self._config.current_behavior.working_directory)
+ except OSError as e:
+ errmsg = ("Failed to change to directory '%s': %s"
+ % (self._config.current_behavior.working_directory,
+ str(e)))
+ self.progress_callback("Error", None, errmsg)
+ raise AMForthException(errmsg)
+ # update the included-wl on the controller
+ if self._update_uploaded:
+ self.send_line("get-current uploaded-wl set-current create " + filename + " set-current")
+ return True
+
+ def _send_file_contents(self, f):
+ in_comment = False
+ lineno = 0
+ for full_line in f:
+ self._config.advance_line()
+ self._serialconn.timeout = self._config.current_behavior.timeout
+ try:
+ os.chdir(self._config.current_behavior.working_directory)
+ except OSError as e:
+ errmsg = ("Failed to change to directory '%s': %s"
+ % (self._config.current_behavior.working_directory,
+ str(e)))
+ self.progress_callback("Error", None, errmsg)
+ raise AMForthException(errmsg)
+ lineno += 1
+ if full_line and full_line[-1] == "\n":
+ full_line = full_line[:-1]
+ if full_line and full_line[-1] == "\r":
+ full_line = full_line[:-1]
+ line = full_line.strip()
+ if len(line) == 0:
+ if in_comment:
+ self.progress_callback("Comment", lineno, full_line)
+ else:
+ self.progress_callback("Whitespace", lineno, full_line)
+ continue
+ try:
+ (line, in_comment,
+ directive,
+ directive_arg) = self.preprocess_line(full_line, in_comment,
+ self.upload_directives)
+ except AMForthException as e:
+ self._record_error(lineno)
+ self.progress_callback("Error", lineno, full_line)
+ self.progress_callback("Error", None, str(e))
+ raise
+ if directive:
+ self.progress_callback("Directive", lineno, full_line)
+ if directive == "#exit":
+ break
+ elif directive == "#interact":
+ self.interact()
+ continue
+ self.handle_common_directives(directive, directive_arg)
+ continue
+ if len(line) == 0:
+ self.progress_callback("Comment", lineno, full_line)
+ continue
+ try:
+ self.send_line(line)
+ except AMForthException as e:
+ self._record_error(lineno)
+ self.progress_callback("Error", lineno, full_line)
+ self.progress_callback("Error", None, str(e))
+ raise
+ response = self.read_response()
+ self.progress_callback("Sent", lineno, full_line)
+ if response[-3:] == " ok":
+ if len(response) > 3:
+ for l in StringIO(response[:-3]):
+ self.progress_callback("Output", lineno, l.rstrip())
+ r = self._config.current_behavior.expected_output_regexp
+ if r:
+ m = re.match(r, response[:-3], re.MULTILINE)
+ response_ok = m is not None
+ else:
+ response_ok = False
+ if not response_ok:
+ if self._config.current_behavior.error_on_output:
+ errmsg = "Unexpected output after line."
+ errmsg += " To allow, specify --no-error-on-output."
+ self.progress_callback("Error", lineno, errmsg)
+ if not self._config.current_behavior.ignore_errors:
+ self._record_error(lineno)
+ raise AMForthException(errmsg)
+ elif self._log:
+ self._log.write(line + "\n")
+ else:
+ self.progress_callback("Error", None, response)
+ if not self._config.current_behavior.ignore_errors:
+ self._record_error(lineno)
+ raise AMForthException("Error in line sent")
+
+ def preprocess_line(self, line, in_delim_comment=False, directives=[]):
+ # Compresses whitespace, including comments so send minimum
+ # data to atmega
+ result = []
+ comment_words = []
+ char_quote = False
+ in_string = False
+ in_line_comment = False
+ directive = None
+ directive_arg = []
+ words = self._split_space_or_tab(line)
+ for iw,w in enumerate(words):
+ if in_string:
+ try:
+ i = w.index('"')
+ except ValueError:
+ result[-1] += " " + w
+ continue
+ in_string = False
+ result[-1] += " " + w[:i+1]
+ result[-1] = result[-1][1:] # remove extra initial space
+ w = w[i+1:]
+
+ if in_delim_comment:
+ try:
+ i = w.index(")")
+ except ValueError:
+ pass
+ else:
+ in_delim_comment = False
+ w = w[i+1:]
+
+ if not w:
+ continue
+ if w in self._appl_defs:
+ w = self._appl_defs[w]
+ elif w in self._amforth_regs and not self.noregsub :
+ w = self._amforth_regs[w]
+ elif w.upper() in self.stdwords:
+ w = w.lower()
+ if char_quote:
+ result.append(w)
+ char_quote = False
+ continue
+
+ if w == "(":
+ if not in_delim_comment:
+ in_delim_comment = True
+ else:
+ raise AMForthException("Illegal nested comment")
+ continue
+
+ if not in_delim_comment and not in_line_comment:
+ if w == "\\" and (iw == 0 or words[iw-1].lower() != "postpone"):
+ in_line_comment = True
+ continue
+
+ elif w in self._config.current_behavior.start_string_words:
+ in_string = True
+ result.append(w)
+ result.append('')
+ continue
+
+ if w in self._config.current_behavior.quote_char_words:
+ char_quote = True # no continue deliberately
+
+ if directive:
+ directive_arg.append(w)
+ else:
+ if (self._config.current_behavior.directive_uncommented
+ and not result
+ and w in directives):
+ directive = w
+ else:
+ result.append(w)
+ else:
+ if directive:
+ directive_arg.append(w)
+ else:
+ if (self._config.current_behavior.directive_commented
+ and not result
+ and not comment_words
+ and w in directives):
+ directive = w
+ else:
+ comment_words.append(w)
+
+ if directive and len(result):
+ raise AMForthError("Directive must not have other content: %s",
+ " ".join(result))
+
+ return (" ".join(result), in_delim_comment,
+ directive, " ".join(directive_arg))
+
+ def _record_error(self, lineno):
+ fn = self._config.current_behavior.filename
+ if fn:
+ self._last_error = (fn, lineno)
+
+ def _split_space_or_tab(self, line):
+ result = [""]
+ for c in line:
+ if c == " " or c == "\t":
+ result.append("")
+ else:
+ result[-1] += c
+ return result
+
+ def handle_common_directives(self, directive, directive_arg):
+ if directive == "#include" or directive == "#require":
+ fn = directive_arg.strip()
+ if self.upload_file(fn, directive == "#include"):
+ resume_fn = self._config.current_behavior.filename
+ if resume_fn:
+ self.progress_callback("File", None, resume_fn + " (resumed)")
+ else:
+ self.progress_callback("Information", None, "already uploaded")
+ elif directive == "#cd":
+ dirname = directive_arg.strip()
+ if os.path.isabs(dirname):
+ dirpath = os.path.normpath(dirname)
+ else:
+ oldpath = self._config.current_behavior.working_directory
+ dirpath = os.path.normpath(os.path.join(oldpath, dirname))
+ self._config.current_behavior.working_directory = dirpath
+ elif directive == "#timeout":
+ try:
+ timeout = float(directive_arg)
+ except ValueError as e:
+ self.progress_callback("Error", None, "Invalid timeout")
+ return
+ self._config.current_file_behavior.timeout = timeout
+ elif directive == "#timeout-next":
+ try:
+ timeout = float(directive_arg)
+ except ValueError as e:
+ self.progress_callback("Error", None, "Invalid timeout")
+ return
+ behavior = copy.deepcopy(self._config.current_behavior)
+ behavior.timeout = timeout
+ self._config.next_line_behavior = behavior
+ elif directive == "#ignore-error":
+ v = self._yes_or_no_arg(directive_arg)
+ self._config.current_file_behavior.ignore_errors = v
+ elif directive == "#ignore-error-next":
+ v = self._yes_or_no_arg(directive_arg)
+ behavior = copy.deepcopy(self._config.current_behavior)
+ behavior.ignore_errors = v
+ self._config.next_line_behavior = behavior
+ elif directive == "#error-on-output":
+ v = self._yes_or_no_arg(directive_arg)
+ behavior = self._config.current_file_behavior
+ behavior.error_on_output = v
+ elif directive == "#expect-output-next":
+ regexp = directive_arg.strip()
+ if not regexp:
+ regexp = ".*"
+ behavior = copy.deepcopy(self._config.current_behavior)
+ behavior.expected_output_regexp = regexp
+ self._config.next_line_behavior = behavior
+ elif directive == "#start-string-word":
+ behavior = self._config.current_file_behavior
+ behavior.start_string_words.append(directive_arg.strip().split(" "))
+ elif directive == "#quote-char-word":
+ behavior = self._config.current_file_behavior
+ behavior.quote_char_words.append(directive_arg.strip().split(" "))
+ elif directive == "#directive":
+ behavior = self._config.current_file_behavior
+ behavior.directive_config = directive_arg.strip()
+ else:
+ errmsg = "Unknown directive: %s %s" % (directive, directive_arg)
+ raise AMForthException(errmsg)
+
+ def _yes_or_no_arg(self, directive_arg):
+ if not directive_arg:
+ return True
+ else:
+ if directive_arg.lower() == "yes":
+ return True
+ elif directive_arg.lower() == "no":
+ return False
+ else:
+ errmsg = "Invalid directive argument. Must be yes or no."
+ raise AMForthExcetion(errmsg)
+
+ def send_line(self, line):
+ if len(line) > self.max_line_length - 1: # For newline
+ raise AMForthException("Input line > %d char"
+ % self.max_line_length)
+ if self.debug:
+ sys.stderr.write("|a( )" + repr(line)[1:-1] + "\n")
+ sys.stderr.write("|s( )")
+ for c in line + "\n":
+ if self.debug:
+ sys.stderr.write(repr(c)[1:-1]+"->")
+ sys.stderr.flush()
+ self._serialconn.write(c.encode())
+ self._serialconn.flush()
+ r = self._serialconn.read(1) # Read echo of character we just sent
+ r=r.decode()
+ while r and (r != c or (c == '\t' and r != ' ')):
+ if self.debug:
+ sys.stderr.write(repr(r)[1:-1])
+ sys.stderr.flush()
+ r = self._serialconn.read(1).decode()
+ if not r:
+ raise AMForthException("Input character not echoed.")
+ if self.debug:
+ sys.stderr.write(repr(r)[1:-1] + "|")
+ sys.stderr.flush()
+ if self.debug:
+ sys.stderr.write("\n")
+
+ def read_response(self):
+ if self.debug:
+ sys.stderr.write("|r( )")
+ response = ""
+ r = self._serialconn.read(1).decode()
+ while r != "":
+ if self.debug:
+ sys.stderr.write(repr(r)[1:-1])
+ sys.stderr.flush()
+ response = response + r
+ if response[-3:] == " ok":
+ # Interactive prompt read and discarded while handling
+ # echo of next line sent.
+ break
+ elif self.amforth_error_cre.search(response) is not None:
+ response = response[:-3] # Don't return prompt in response
+ break
+ r = self._serialconn.read(1).decode()
+ if not response:
+ response = "Timed out waiting for ok response"
+ if self.debug:
+ sys.stderr.write("\n")
+ return response
+
+ def print_progress(self, type, lineno, info):
+ if not lineno:
+ print("|%s=%s" % (type[:1], info))
+ else:
+ print ("|%s|%5d|%s" % (type[:1], lineno, info))
+
+ def interact(self):
+ self.progress_callback("Interact", None,
+ "Entering amforth interactive interpreter")
+ # Use null filename "file" to capture interactive config
+ self._config.push_file(None)
+ try:
+ self.find_prompt()
+ except AMForthException as e:
+ self.progress_callback("Error", None, str(e))
+ self._config.pop_file()
+ raise
+ self._init_readline()
+ in_comment = False
+ while True:
+ try:
+ if self._amforth_cpu:
+ prompt="("+self._amforth_cpu+")> "
+ else:
+ prompt="> "
+ full_line = input(prompt)
+ except EOFError as e:
+ print("")
+ break
+ self._config.advance_line()
+ self._serialconn.timeout = self._config.current_behavior.timeout
+ try:
+ os.chdir(self._config.current_behavior.working_directory)
+ except OSError as e:
+ errmsg = ("Failed to change to directory '%s': %s"
+ % (self._config.current_behavior.working_directory,
+ str(e)))
+ self.progress_callback("Error", None, errmsg)
+ raise AMForthException(errmsg)
+ (line, in_comment,
+ directive,
+ directive_arg) = self.preprocess_line(full_line, in_comment,
+ self.interact_directives)
+ try:
+ if directive:
+ self.progress_callback("Directive", None, full_line)
+ if directive == "#exit":
+ break
+ elif directive == "#update-words":
+ self._update_words()
+ continue
+ elif directive == "#update-cpu":
+ self._update_cpu()
+ continue
+ elif directive == "#update-files":
+ self._update_files()
+ continue
+ elif directive == "#edit":
+ if directive_arg:
+ self.edit_file(directive_arg.strip())
+ elif self._last_error:
+ self.edit_file(*self._last_error)
+ elif self._last_edited_file:
+ self.edit_file(self._last_edited_file)
+ else:
+ print("No file to edit")
+ continue
+ self.handle_common_directives(directive, directive_arg)
+ if directive == "#include" or directive == "#require":
+ self._update_words()
+ continue
+ if in_comment or not line:
+ continue
+ else:
+ self.send_line(line)
+ print (self.read_response())
+ except AMForthException as e:
+ print ("Error: " + str(e))
+ self._config.pop_file()
+ self._serialconn.timeout = self._config.current_behavior.timeout
+ try:
+ os.chdir(self._config.current_behavior.working_directory)
+ except OSError as e:
+ errmsg = ("Failed to change to directory '%s': %s"
+ % (self._config.current_behavior.working_directory,
+ str(e)))
+ self.progress_callback("Error", None, errmsg)
+ raise AMForthException(errmsg)
+ self.progress_callback("Interact", None,
+ "Leaving interactive interpreter")
+
+ def _init_readline(self):
+ if not self._readline_initialized:
+ readline.set_completer_delims(" ")
+ readline.set_completer(self._rlcompleter)
+ readline.parse_and_bind("tab: complete")
+ histfn = os.path.join(os.path.expanduser("~"),
+ ".frt-interact.history")
+ try:
+ readline.read_history_file(histfn)
+ except IOError as e:
+ pass
+ self._update_words()
+ self._update_cpu()
+ self._update_files()
+ self._update_uploaded_files()
+ atexit.register(readline.write_history_file, histfn)
+
+ def _update_words(self):
+ # get all words that are available in the search order
+ self.send_line("base @ decimal dp u. base !")
+ dp = self.read_response()
+ if dp[-3:] != " ok":
+ return # Something went wrong, just silently ignore
+ dp = int(dp[:-3])
+ if self._amforth_dp != dp:
+ self._amforth_dp = dp
+ self.send_line("words")
+ words = self.read_response()
+ if words[-3:] != " ok":
+ return # Something went wrong, just silently ignore
+ self._amforth_words = words.split(" ") + self.interact_directives
+
+ def _update_cpu(self):
+ return
+ self.progress_callback("Information", None, "getting MCU name..")
+ self.send_line("s\" cpu\" environment search-wordlist drop execute itype")
+ words = self.read_response()
+ if words[-3:] != " ok":
+ return # Something went wrong, just silently ignore
+ mcudef = words[:-3].lower()
+ self._amforth_regs = {}
+ if mcudef.startswith("msp"):
+ flavor="msp430"
+ else:
+ if mcudef.startswith("rv"):
+ flavor="risc-v"
+ else:
+ flavor="avr8"
+ self._search_list.insert(0,os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),"..", flavor, "lib")))
+ self._search_list.insert(0,os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),"..", flavor, "devices",mcudef)))
+ sys.path.insert(1,os.path.join(os.path.dirname(sys.argv[0]),"..", flavor, "devices",mcudef))
+ try:
+ from device import MCUREGS
+ self._amforth_regs=MCUREGS
+ self._amforth_cpu = words[:-3]
+ self.progress_callback("Information", None, "successfully loaded register definitions for " + mcudef)
+ except:
+ self.progress_callback("Warning", None, "failed loading register definitions for " + mcudef + " (" + flavor + ") .. continuing")
+
+ def _update_files(self):
+ self.progress_callback("Information", None, "getting filenames on the host")
+ return
+ self._filedirs = {}
+ for p in self._search_list:
+ self.progress_callback("Information", None, " Reading "+p)
+ for root, dirs, files in os.walk(p):
+ for f in files:
+ print(f) # TWTW
+ fpath=os.path.realpath(os.path.join(root, f))
+ fpathdir=os.path.dirname(fpath)
+ if f in self._filedirs:
+ # check for duplicates
+ for d in self._filedirs[f]:
+ if d==fpathdir:
+ fpath=None
+ if fpath: self._filedirs[f].append(fpathdir)
+ else:
+ self._filedirs[f]=[fpathdir]
+
+ def _update_uploaded_files(self):
+ self.progress_callback("Information", None, "getting filenames from the controller")
+ return
+ self.send_line("uploaded-wl show-wordlist")
+ files = self.read_response()
+ if files[-3:] != " ok":
+ return # Something went wrong, just silently ignore
+ for f in files.split(" "):
+ self._uploaded.add(f)
+ self.progress_callback("Information", None, "already uploaded "+ ", ".join(self._uploaded)+" ")
+ self._update_uploaded = True
+
+ def _rlcompleter(self, text, state):
+ if state == 0:
+ line_words = readline.get_line_buffer().split(" ")
+ if line_words and line_words[-1] == text:
+ line_words = line_words[:-1]
+ while line_words and line_words[-1] == "":
+ line_words = line_words[:-1]
+ if line_words:
+ if line_words[-1] in ["#require", "#include", "#edit"]:
+ self._rl_matches = [f for f in self._filedirs.keys()
+ if f.startswith(text)]
+ elif line_words[-1] == "#cd":
+ fnames = glob.glob(text + '*')
+ self._rl_matches = [f + "/" for f in fnames
+ if os.path.isdir(f)]
+ elif line_words[-1] == "#directive":
+ self._rl_matches = [w for w in ("all ", "uncommented ",
+ "commented ", "none ")
+ if w.startswith(text)]
+ elif line_words[-1] in ["#error-on-output",
+ "#ignore-error", "#ignore-error-next"]:
+ self._rl_matches = [w for w in ["yes", "no"]
+ if w.startswith(text)]
+ elif line_words[-1] in ["#exit", "#update-words",
+ "#timeout", "#timeout-next"]:
+ self._rl_matches = []
+ else:
+ self._rl_matches = [w + " " for w in self._amforth_words+self._amforth_regs.keys()
+ if not text or w.startswith(text)]
+ else:
+ self._rl_matches = [w + " " for w in self._amforth_words+self._amforth_regs.keys()
+ if not text or w.startswith(text)]
+ if self._rl_matches:
+ return self._rl_matches[0]
+ else:
+ return None
+ else:
+ if state < len(self._rl_matches):
+ return self._rl_matches[state]
+ else:
+ return None
+
+ def edit_file(self, filename, lineno=0):
+ if self.editor:
+ # Have to construct command line differently for different
+ # editors to be able to move to specific line...
+ exename = os.path.basename(self.editor)
+ if exename in ["emacs", "emacsclient", "nano"]:
+ cmd = [self.editor, "+" + str(lineno), filename]
+ elif exename in ["vi", "vim"]:
+ cmd = [self.editor, filename, "+" + str(lineno)]
+ elif exename == "mcedit":
+ cmd = [self.editor, " " + filename, ":" + str(lineno)]
+ elif exename == "gedit":
+ cmd = [self.editor, "-b", filename, "+" + str(lineno)]
+ elif exename == "pn.exe":
+ cmd = [self.editor, " --line", " "+str(lineno)+" ", filename]
+ else:
+ cmd = [self.editor, filename]
+ try:
+ subprocess.call(cmd)
+ self._last_edited_file = filename
+ except OSError as e:
+ raise AMForthException("Could not start editor: "+self.editor)
+ else:
+ raise AMForthException("No editor specified. Use --editor or EDITOR environment variable")
+
+if __name__ == "__main__":
+ sys.exit(AMForth().main())
+