Mirror of CollapseOS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1800 lines
30KB

  1. ; Collapse OS' Forth
  2. ;
  3. ; Unlike other assembler parts of Collapse OS, this unit is one huge file.
  4. ;
  5. ; I do this because as Forth takes a bigger place, assembler is bound to take
  6. ; less and less place. I am thus consolidating that assembler code in one
  7. ; place so that I have a better visibility of what to minimize.
  8. ;
  9. ; I also want to reduce the featureset of the assembler so that Collapse OS
  10. ; self-hosts in a more compact manner. File include is a big part of the
  11. ; complexity in zasm. If we can get rid of it, we'll be more compact.
  12. ; *** Defines ***
  13. ; GETC: address of a GetC routine
  14. ; PUTC: address of a PutC routine
  15. ;
  16. ; Those GetC/PutC routines are hooked through defines and have this API:
  17. ;
  18. ; GetC: Blocks until a character is read from the device and return that
  19. ; character in A.
  20. ;
  21. ; PutC: Write character specified in A onto the device.
  22. ;
  23. ; *** ASCII ***
  24. .equ BS 0x08
  25. .equ CR 0x0d
  26. .equ LF 0x0a
  27. .equ DEL 0x7f
  28. ; *** Const ***
  29. ; Base of the Return Stack
  30. .equ RS_ADDR 0xf000
  31. ; Number of bytes we keep as a padding between HERE and the scratchpad
  32. .equ PADDING 0x20
  33. ; Max length of dict entry names
  34. .equ NAMELEN 7
  35. ; Offset of the code link relative to the beginning of the word
  36. .equ CODELINK_OFFSET NAMELEN+3
  37. ; Buffer where WORD copies its read word to. It's significantly larger than
  38. ; NAMELEN, but who knows, in a comment, we might have a very long word...
  39. .equ WORD_BUFSIZE 0x20
  40. ; Allocated space for sysvars (see comment above SYSVCNT)
  41. .equ SYSV_BUFSIZE 0x10
  42. ; Flags for the "flag field" of the word structure
  43. ; IMMEDIATE word
  44. .equ FLAG_IMMED 0
  45. ; *** Variables ***
  46. .equ INITIAL_SP RAMSTART
  47. ; wordref of the last entry of the dict.
  48. .equ CURRENT @+2
  49. ; Pointer to the next free byte in dict.
  50. .equ HERE @+2
  51. ; Interpreter pointer. See Execution model comment below.
  52. .equ IP @+2
  53. ; Global flags
  54. ; Bit 0: whether the interpreter is executing a word (as opposed to parsing)
  55. .equ FLAGS @+2
  56. ; Pointer to the system's number parsing function. It points to then entry that
  57. ; had the "(parse)" name at startup. During stage0, it's out builtin PARSE,
  58. ; but at stage1, it becomes "(parse)" from core.fs. It can also be changed at
  59. ; runtime.
  60. .equ PARSEPTR @+2
  61. ; Pointer to the word executed by "C<". During stage0, this points to KEY.
  62. ; However, KEY ain't very interactive. This is why we implement a readline
  63. ; interface in Forth, which we plug in during init. If "(c<)" exists in the
  64. ; dict, CINPTR is set to it. Otherwise, we set KEY
  65. .equ CINPTR @+2
  66. .equ WORDBUF @+2
  67. ; Sys Vars are variables with their value living in the system RAM segment. We
  68. ; need this mechanisms for core Forth source needing variables. Because core
  69. ; Forth source is pre-compiled, it needs to be able to live in ROM, which means
  70. ; that we can't compile a regular variable in it. SYSVNXT points to the next
  71. ; free space in SYSVBUF. Then, at the word level, it's a regular sysvarWord.
  72. .equ SYSVNXT @+WORD_BUFSIZE
  73. .equ SYSVBUF @+2
  74. .equ RAMEND @+SYSV_BUFSIZE
  75. ; (HERE) usually starts at RAMEND, but in certain situations, such as in stage0,
  76. ; (HERE) will begin at a strategic place.
  77. .equ HERE_INITIAL RAMEND
  78. ; EXECUTION MODEL
  79. ; After having read a line through readline, we want to interpret it. As
  80. ; a general rule, we go like this:
  81. ;
  82. ; 1. read single word from line
  83. ; 2. Can we find the word in dict?
  84. ; 3. If yes, execute that word, goto 1
  85. ; 4. Is it a number?
  86. ; 5. If yes, push that number to PS, goto 1
  87. ; 6. Error: undefined word.
  88. ;
  89. ; EXECUTING A WORD
  90. ;
  91. ; At it's core, executing a word is having the wordref in IY and call
  92. ; EXECUTE. Then, we let the word do its things. Some words are special,
  93. ; but most of them are of the compiledWord type, and that's their execution that
  94. ; we describe here.
  95. ;
  96. ; First of all, at all time during execution, the Interpreter Pointer (IP)
  97. ; points to the wordref we're executing next.
  98. ;
  99. ; When we execute a compiledWord, the first thing we do is push IP to the Return
  100. ; Stack (RS). Therefore, RS' top of stack will contain a wordref to execute
  101. ; next, after we EXIT.
  102. ;
  103. ; At the end of every compiledWord is an EXIT. This pops RS, sets IP to it, and
  104. ; continues.
  105. ; *** Code ***
  106. forthMain:
  107. ; STACK OVERFLOW PROTECTION:
  108. ; To avoid having to check for stack underflow after each pop operation
  109. ; (which can end up being prohibitive in terms of costs), we give
  110. ; ourselves a nice 6 bytes buffer. 6 bytes because we seldom have words
  111. ; requiring more than 3 items from the stack. Then, at each "exit" call
  112. ; we check for stack underflow.
  113. push af \ push af \ push af
  114. ld (INITIAL_SP), sp
  115. ld ix, RS_ADDR
  116. ; LATEST is a *indirect* label to the latest entry of the dict. See
  117. ; default at the bottom of dict.asm. This indirection allows us to
  118. ; override latest to a value set in a binary dict compiled separately,
  119. ; for example by the stage0 bin.
  120. ld hl, LATEST
  121. call intoHL
  122. ld (CURRENT), hl
  123. ld hl, HERE_INITIAL
  124. ld (HERE), hl
  125. ; Set up PARSEPTR
  126. ld hl, PARSE-CODELINK_OFFSET
  127. call find
  128. ld (PARSEPTR), de
  129. ; Set up CINPTR
  130. ; do we have a C< impl?
  131. ld hl, .cinName
  132. call find
  133. jr z, .skip
  134. ; no? then use KEY
  135. ld de, KEY
  136. .skip:
  137. ld (CINPTR), de
  138. ; Set up SYSVNXT
  139. ld hl, SYSVBUF
  140. ld (SYSVNXT), hl
  141. ld hl, BEGIN
  142. push hl
  143. jp EXECUTE+2
  144. .cinName:
  145. .db "C<", 0
  146. BEGIN:
  147. .dw compiledWord
  148. .dw LIT
  149. .db "(c<$)", 0
  150. .dw FIND_
  151. .dw NOT
  152. .dw CSKIP
  153. .dw EXECUTE
  154. .dw INTERPRET
  155. INTERPRET:
  156. .dw compiledWord
  157. ; BBR mark
  158. .dw WORD
  159. .dw FIND_
  160. .dw CSKIP
  161. .dw FBR
  162. .db 34
  163. ; It's a word, execute it
  164. .dw FLAGS_
  165. .dw FETCH
  166. .dw NUMBER
  167. .dw 0x0001 ; Bit 0 on
  168. .dw OR
  169. .dw FLAGS_
  170. .dw STORE
  171. .dw EXECUTE
  172. .dw FLAGS_
  173. .dw FETCH
  174. .dw NUMBER
  175. .dw 0xfffe ; Bit 0 off
  176. .dw AND
  177. .dw FLAGS_
  178. .dw STORE
  179. .dw BBR
  180. .db 41
  181. ; FBR mark, try number
  182. .dw PARSEI
  183. .dw BBR
  184. .db 46
  185. ; infinite loop
  186. ; *** Collapse OS lib copy ***
  187. ; In the process of Forth-ifying Collapse OS, apps will be slowly rewritten to
  188. ; Forth and the concept of ASM libs will become obsolete. To facilitate this
  189. ; transition, I make, right now, a copy of the routines actually used by Forth's
  190. ; native core. This also has the effect of reducing binary size right now and
  191. ; give us an idea of Forth's compactness.
  192. ; These routines below are copy/paste from apps/lib and stdio.
  193. ; copy (HL) into DE, then exchange the two, utilising the optimised HL instructions.
  194. ; ld must be done little endian, so least significant byte first.
  195. intoHL:
  196. push de
  197. ld e, (hl)
  198. inc hl
  199. ld d, (hl)
  200. ex de, hl
  201. pop de
  202. ret
  203. intoDE:
  204. ex de, hl
  205. call intoHL
  206. ex de, hl ; de preserved by intoHL, so no push/pop needed
  207. ret
  208. ; add the value of A into HL
  209. ; affects carry flag according to the 16-bit addition, Z, S and P untouched.
  210. addHL:
  211. push de
  212. ld d, 0
  213. ld e, a
  214. add hl, de
  215. pop de
  216. ret
  217. ; Copy string from (HL) in (DE), that is, copy bytes until a null char is
  218. ; encountered. The null char is also copied.
  219. ; HL and DE point to the char right after the null char.
  220. strcpyM:
  221. ld a, (hl)
  222. ld (de), a
  223. inc hl
  224. inc de
  225. or a
  226. jr nz, strcpyM
  227. ret
  228. ; Like strcpyM, but preserve HL and DE
  229. strcpy:
  230. push hl
  231. push de
  232. call strcpyM
  233. pop de
  234. pop hl
  235. ret
  236. ; Compares strings pointed to by HL and DE until one of them hits its null char.
  237. ; If equal, Z is set. If not equal, Z is reset. C is set if HL > DE
  238. strcmp:
  239. push hl
  240. push de
  241. .loop:
  242. ld a, (de)
  243. cp (hl)
  244. jr nz, .end ; not equal? break early. NZ is carried out
  245. ; to the caller
  246. or a ; If our chars are null, stop the cmp
  247. inc hl
  248. inc de
  249. jr nz, .loop ; Z is carried through
  250. .end:
  251. pop de
  252. pop hl
  253. ; Because we don't call anything else than CP that modify the Z flag,
  254. ; our Z value will be that of the last cp (reset if we broke the loop
  255. ; early, set otherwise)
  256. ret
  257. ; Compares strings pointed to by HL and DE up to A count of characters. If
  258. ; equal, Z is set. If not equal, Z is reset.
  259. strncmp:
  260. push bc
  261. push hl
  262. push de
  263. ld b, a
  264. .loop:
  265. ld a, (de)
  266. cp (hl)
  267. jr nz, .end ; not equal? break early. NZ is carried out
  268. ; to the called
  269. cp 0 ; If our chars are null, stop the cmp
  270. jr z, .end ; The positive result will be carried to the
  271. ; caller
  272. inc hl
  273. inc de
  274. djnz .loop
  275. ; We went through all chars with success, but our current Z flag is
  276. ; unset because of the cp 0. Let's do a dummy CP to set the Z flag.
  277. cp a
  278. .end:
  279. pop de
  280. pop hl
  281. pop bc
  282. ; Because we don't call anything else than CP that modify the Z flag,
  283. ; our Z value will be that of the last cp (reset if we broke the loop
  284. ; early, set otherwise)
  285. ret
  286. ; Given a string at (HL), move HL until it points to the end of that string.
  287. strskip:
  288. push bc
  289. ex af, af'
  290. xor a ; look for null char
  291. ld b, a
  292. ld c, a
  293. cpir ; advances HL regardless of comparison, so goes one too far
  294. dec hl
  295. ex af, af'
  296. pop bc
  297. ret
  298. ; Borrowed from Tasty Basic by Dimitri Theulings (GPL).
  299. ; Divide HL by DE, placing the result in BC and the remainder in HL.
  300. divide:
  301. push hl ; --> lvl 1
  302. ld l, h ; divide h by de
  303. ld h, 0
  304. call .dv1
  305. ld b, c ; save result in b
  306. ld a, l ; (remainder + l) / de
  307. pop hl ; <-- lvl 1
  308. ld h, a
  309. .dv1:
  310. ld c, 0xff ; result in c
  311. .dv2:
  312. inc c ; dumb routine
  313. call .subde ; divide using subtract and count
  314. jr nc, .dv2
  315. add hl, de
  316. ret
  317. .subde:
  318. ld a, l
  319. sub e ; subtract de from hl
  320. ld l, a
  321. ld a, h
  322. sbc a, d
  323. ld h, a
  324. ret
  325. ; Parse string at (HL) as a decimal value and return value in DE.
  326. ; Reads as many digits as it can and stop when:
  327. ; 1 - A non-digit character is read
  328. ; 2 - The number overflows from 16-bit
  329. ; HL is advanced to the character following the last successfully read char.
  330. ; Error conditions are:
  331. ; 1 - There wasn't at least one character that could be read.
  332. ; 2 - Overflow.
  333. ; Sets Z on success, unset on error.
  334. parseDecimal:
  335. ; First char is special: it has to succeed.
  336. ld a, (hl)
  337. ; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
  338. ; result in A.
  339. ; On success, the carry flag is reset. On error, it is set.
  340. add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
  341. sub 0xff-9 ; maps to 0-9 and carries if not a digit
  342. ret c ; Error. If it's C, it's also going to be NZ
  343. ; During this routine, we switch between HL and its shadow. On one side,
  344. ; we have HL the string pointer, and on the other side, we have HL the
  345. ; numerical result. We also use EXX to preserve BC, saving us a push.
  346. exx ; HL as a result
  347. ld h, 0
  348. ld l, a ; load first digit in without multiplying
  349. .loop:
  350. exx ; HL as a string pointer
  351. inc hl
  352. ld a, (hl)
  353. exx ; HL as a numerical result
  354. ; same as other above
  355. add a, 0xff-'9'
  356. sub 0xff-9
  357. jr c, .end
  358. ld b, a ; we can now use a for overflow checking
  359. add hl, hl ; x2
  360. sbc a, a ; a=0 if no overflow, a=0xFF otherwise
  361. ld d, h
  362. ld e, l ; de is x2
  363. add hl, hl ; x4
  364. rla
  365. add hl, hl ; x8
  366. rla
  367. add hl, de ; x10
  368. rla
  369. ld d, a ; a is zero unless there's an overflow
  370. ld e, b
  371. add hl, de
  372. adc a, a ; same as rla except affects Z
  373. ; Did we oveflow?
  374. jr z, .loop ; No? continue
  375. ; error, NZ already set
  376. exx ; HL is now string pointer, restore BC
  377. ; HL points to the char following the last success.
  378. ret
  379. .end:
  380. push hl ; --> lvl 1, result
  381. exx ; HL as a string pointer, restore BC
  382. pop de ; <-- lvl 1, result
  383. cp a ; ensure Z
  384. ret
  385. ; *** Support routines ***
  386. ; Sets Z if (HL) == E and (HL+1) == D
  387. HLPointsDE:
  388. ld a, (hl)
  389. cp e
  390. ret nz ; no
  391. inc hl
  392. ld a, (hl)
  393. dec hl
  394. cp d ; Z has our answer
  395. ret
  396. ; Find the entry corresponding to word where (HL) points to and sets DE to
  397. ; point to that entry.
  398. ; Z if found, NZ if not.
  399. find:
  400. push hl
  401. push bc
  402. ld de, (CURRENT)
  403. ld bc, CODELINK_OFFSET
  404. .inner:
  405. ; DE is a wordref, let's go to beginning of struct
  406. push de ; --> lvl 1
  407. or a ; clear carry
  408. ex de, hl
  409. sbc hl, bc
  410. ex de, hl ; We're good, DE points to word name
  411. ld a, NAMELEN
  412. call strncmp
  413. pop de ; <-- lvl 1, return to wordref
  414. jr z, .end ; found
  415. call .prev
  416. jr nz, .inner
  417. ; Z set? end of dict unset Z
  418. inc a
  419. .end:
  420. pop bc
  421. pop hl
  422. ret
  423. ; For DE being a wordref, move DE to the previous wordref.
  424. ; Z is set if DE point to 0 (no entry). NZ if not.
  425. .prev:
  426. dec de \ dec de \ dec de ; prev field
  427. call intoDE
  428. ; DE points to prev. Is it zero?
  429. xor a
  430. or d
  431. or e
  432. ; Z will be set if DE is zero
  433. ret
  434. ; Checks flags Z and S and sets BC to 0 if Z, 1 if C and -1 otherwise
  435. flagsToBC:
  436. ld bc, 0
  437. ret z ; equal
  438. inc bc
  439. ret m ; >
  440. ; <
  441. dec bc
  442. dec bc
  443. ret
  444. ; Write DE in (HL), advancing HL by 2.
  445. DEinHL:
  446. ld (hl), e
  447. inc hl
  448. ld (hl), d
  449. inc hl
  450. ret
  451. ; *** Stack management ***
  452. ; The Parameter stack (PS) is maintained by SP and the Return stack (RS) is
  453. ; maintained by IX. This allows us to generally use push and pop freely because
  454. ; PS is the most frequently used. However, this causes a problem with routine
  455. ; calls: because in Forth, the stack isn't balanced within each call, our return
  456. ; offset, when placed by a CALL, messes everything up. This is one of the
  457. ; reasons why we need stack management routines below. IX always points to RS'
  458. ; Top Of Stack (TOS)
  459. ;
  460. ; This return stack contain "Interpreter pointers", that is a pointer to the
  461. ; address of a word, as seen in a compiled list of words.
  462. ; Push value HL to RS
  463. pushRS:
  464. inc ix
  465. inc ix
  466. ld (ix), l
  467. ld (ix+1), h
  468. ret
  469. ; Pop RS' TOS to HL
  470. popRS:
  471. ld l, (ix)
  472. ld h, (ix+1)
  473. dec ix
  474. dec ix
  475. ret
  476. popRSIP:
  477. call popRS
  478. ld (IP), hl
  479. ret
  480. ; Verifies that SP and RS are within bounds. If it's not, call ABORT
  481. chkRS:
  482. push ix \ pop hl
  483. push de ; --> lvl 1
  484. ld de, RS_ADDR
  485. or a ; clear carry
  486. sbc hl, de
  487. pop de ; <-- lvl 1
  488. jp c, abortUnderflow
  489. ret
  490. chkPS:
  491. push hl
  492. ld hl, (INITIAL_SP)
  493. ; We have the return address for this very call on the stack and
  494. ; protected registers. Let's compensate
  495. dec hl \ dec hl
  496. dec hl \ dec hl
  497. or a ; clear carry
  498. sbc hl, sp
  499. pop hl
  500. ret nc ; (INITIAL_SP) >= SP? good
  501. jp abortUnderflow
  502. ; *** Dictionary ***
  503. ; It's important that this part is at the end of the resulting binary.
  504. ; A dictionary entry has this structure:
  505. ; - 7b name (zero-padded)
  506. ; - 2b prev pointer
  507. ; - 1b flags (bit 0: IMMEDIATE)
  508. ; - 2b code pointer
  509. ; - Parameter field (PF)
  510. ;
  511. ; The code pointer point to "word routines". These routines expect to be called
  512. ; with IY pointing to the PF. They themselves are expected to end by jumping
  513. ; to the address at (IP). They will usually do so with "jp next".
  514. ;
  515. ; That's for "regular" words (words that are part of the dict chain). There are
  516. ; also "special words", for example NUMBER, LIT, FBR, that have a slightly
  517. ; different structure. They're also a pointer to an executable, but as for the
  518. ; other fields, the only one they have is the "flags" field.
  519. ; This routine is jumped to at the end of every word. In it, we jump to current
  520. ; IP, but we also take care of increasing it my 2 before jumping
  521. next:
  522. ; Before we continue: are stacks within bounds?
  523. call chkPS
  524. call chkRS
  525. ld de, (IP)
  526. ld h, d
  527. ld l, e
  528. inc de \ inc de
  529. ld (IP), de
  530. ; HL is an atom list pointer. We need to go into it to have a wordref
  531. ld e, (hl)
  532. inc hl
  533. ld d, (hl)
  534. push de
  535. jp EXECUTE+2
  536. ; Execute a word containing native code at its PF address (PFA)
  537. nativeWord:
  538. jp (iy)
  539. ; Execute a list of atoms, which always end with EXIT.
  540. ; IY points to that list. What do we do:
  541. ; 1. Push current IP to RS
  542. ; 2. Set new IP to the second atom of the list
  543. ; 3. Execute the first atom of the list.
  544. compiledWord:
  545. ld hl, (IP)
  546. call pushRS
  547. push iy \ pop hl
  548. inc hl
  549. inc hl
  550. ld (IP), hl
  551. ; IY still is our atom reference...
  552. ld l, (iy)
  553. ld h, (iy+1)
  554. push hl ; argument for EXECUTE
  555. jp EXECUTE+2
  556. ; Pushes the PFA directly
  557. cellWord:
  558. push iy
  559. jp next
  560. ; Pushes the address in the first word of the PF
  561. sysvarWord:
  562. ld l, (iy)
  563. ld h, (iy+1)
  564. push hl
  565. jp next
  566. ; The word was spawned from a definition word that has a DOES>. PFA+2 (right
  567. ; after the actual cell) is a link to the slot right after that DOES>.
  568. ; Therefore, what we need to do push the cell addr like a regular cell, then
  569. ; follow the link from the PFA, and then continue as a regular compiledWord.
  570. doesWord:
  571. push iy ; like a regular cell
  572. ld l, (iy+2)
  573. ld h, (iy+3)
  574. push hl \ pop iy
  575. jr compiledWord
  576. ; This is not a word, but a number literal. This works a bit differently than
  577. ; others: PF means nothing and the actual number is placed next to the
  578. ; numberWord reference in the compiled word list. What we need to do to fetch
  579. ; that number is to play with the IP.
  580. numberWord:
  581. ld hl, (IP) ; (HL) is out number
  582. ld e, (hl)
  583. inc hl
  584. ld d, (hl)
  585. inc hl
  586. ld (IP), hl ; advance IP by 2
  587. push de
  588. jp next
  589. .db 0b10 ; Flags
  590. NUMBER:
  591. .dw numberWord
  592. ; Similarly to numberWord, this is not a real word, but a string literal.
  593. ; Instead of being followed by a 2 bytes number, it's followed by a
  594. ; null-terminated string. When called, puts the string's address on PS
  595. litWord:
  596. ld hl, (IP)
  597. push hl
  598. call strskip
  599. inc hl ; after null termination
  600. ld (IP), hl
  601. jp next
  602. .db 0b10 ; Flags
  603. LIT:
  604. .dw litWord
  605. ; Pop previous IP from Return stack and execute it.
  606. ; ( R:I -- )
  607. .db "EXIT"
  608. .fill 3
  609. .dw 0
  610. .db 0
  611. EXIT:
  612. .dw nativeWord
  613. call popRSIP
  614. jp next
  615. ; ( R:I -- )
  616. .db "QUIT"
  617. .fill 3
  618. .dw EXIT
  619. .db 0
  620. QUIT:
  621. .dw compiledWord
  622. .dw NUMBER
  623. .dw 0
  624. .dw FLAGS_
  625. .dw STORE
  626. .dw .private
  627. .dw INTERPRET
  628. .private:
  629. .dw nativeWord
  630. ld ix, RS_ADDR
  631. jp next
  632. .db "ABORT"
  633. .fill 2
  634. .dw QUIT
  635. .db 0
  636. ABORT:
  637. .dw compiledWord
  638. .dw .private
  639. .dw QUIT
  640. .private:
  641. .dw nativeWord
  642. ; Reinitialize PS
  643. ld sp, (INITIAL_SP)
  644. jp next
  645. abortUnderflow:
  646. ld hl, .word
  647. push hl
  648. jp EXECUTE+2
  649. .word:
  650. .dw compiledWord
  651. .dw LIT
  652. .db "stack underflow", 0
  653. .dw PRINT
  654. .dw ABORT
  655. .db "BYE"
  656. .fill 4
  657. .dw ABORT
  658. .db 0
  659. BYE:
  660. .dw nativeWord
  661. ; Goodbye Forth! Before we go, let's restore the stack
  662. ld sp, (INITIAL_SP)
  663. ; unwind stack underflow buffer
  664. pop af \ pop af \ pop af
  665. ; success
  666. xor a
  667. ret
  668. ; ( c -- )
  669. .db "EMIT"
  670. .fill 3
  671. .dw BYE
  672. .db 0
  673. EMIT:
  674. .dw nativeWord
  675. pop hl
  676. call chkPS
  677. ld a, l
  678. call PUTC
  679. jp next
  680. .db "(print)"
  681. .dw EMIT
  682. .db 0
  683. PRINT:
  684. .dw nativeWord
  685. pop hl
  686. call chkPS
  687. .loop:
  688. ld a, (hl) ; load character to send
  689. or a ; is it zero?
  690. jp z, next ; if yes, we're finished
  691. call PUTC
  692. inc hl
  693. jr .loop
  694. .db '.', '"'
  695. .fill 5
  696. .dw PRINT
  697. .db 1 ; IMMEDIATE
  698. PRINTI:
  699. .dw compiledWord
  700. .dw NUMBER
  701. .dw LIT
  702. .dw WR
  703. ; BBR mark
  704. .dw CIN
  705. .dw DUP
  706. .dw NUMBER
  707. .dw '"'
  708. .dw CMP
  709. .dw CSKIP
  710. .dw FBR
  711. .db 6
  712. .dw CWR
  713. .dw BBR
  714. .db 19
  715. ; FBR mark
  716. ; null terminate string
  717. .dw NUMBER
  718. .dw 0
  719. .dw CWR
  720. .dw NUMBER
  721. .dw PRINT
  722. .dw WR
  723. .dw EXIT
  724. ; ( c port -- )
  725. .db "PC!"
  726. .fill 4
  727. .dw PRINTI
  728. .db 0
  729. PSTORE:
  730. .dw nativeWord
  731. pop bc
  732. pop hl
  733. call chkPS
  734. out (c), l
  735. jp next
  736. ; ( port -- c )
  737. .db "PC@"
  738. .fill 4
  739. .dw PSTORE
  740. .db 0
  741. PFETCH:
  742. .dw nativeWord
  743. pop bc
  744. call chkPS
  745. ld h, 0
  746. in l, (c)
  747. push hl
  748. jp next
  749. .db "C,"
  750. .fill 5
  751. .dw PFETCH
  752. .db 0
  753. CWR:
  754. .dw nativeWord
  755. pop de
  756. call chkPS
  757. ld hl, (HERE)
  758. ld (hl), e
  759. inc hl
  760. ld (HERE), hl
  761. jp next
  762. .db ","
  763. .fill 6
  764. .dw CWR
  765. .db 0
  766. WR:
  767. .dw nativeWord
  768. pop de
  769. call chkPS
  770. ld hl, (HERE)
  771. call DEinHL
  772. ld (HERE), hl
  773. jp next
  774. ; ( addr -- )
  775. .db "EXECUTE"
  776. .dw WR
  777. .db 0
  778. EXECUTE:
  779. .dw nativeWord
  780. pop iy ; is a wordref
  781. call chkPS
  782. ld l, (iy)
  783. ld h, (iy+1)
  784. ; HL points to code pointer
  785. inc iy
  786. inc iy
  787. ; IY points to PFA
  788. jp (hl) ; go!
  789. .db ";"
  790. .fill 6
  791. .dw EXECUTE
  792. .db 1 ; IMMEDIATE
  793. ENDDEF:
  794. .dw compiledWord
  795. .dw NUMBER
  796. .dw EXIT
  797. .dw WR
  798. .dw R2P ; exit COMPILE
  799. .dw DROP
  800. .dw R2P ; exit DEFINE
  801. .dw DROP
  802. .dw EXIT
  803. .db ":"
  804. .fill 6
  805. .dw ENDDEF
  806. .db 1 ; IMMEDIATE
  807. DEFINE:
  808. .dw compiledWord
  809. .dw WORD
  810. .dw ENTRYHEAD
  811. .dw NUMBER
  812. .dw compiledWord
  813. .dw WR
  814. ; BBR branch mark
  815. .dw .compile
  816. .dw BBR
  817. .db 4
  818. ; no need for EXIT, ENDDEF takes care of taking us out
  819. .compile:
  820. .dw compiledWord
  821. .dw WORD
  822. .dw FIND_
  823. .dw CSKIP
  824. .dw .maybeNum
  825. .dw DUP
  826. .dw ISIMMED
  827. .dw CSKIP
  828. .dw .word
  829. ; is immediate. just execute.
  830. .dw EXECUTE
  831. .dw EXIT
  832. .word:
  833. .dw compiledWord
  834. .dw WR
  835. .dw R2P ; exit .compile
  836. .dw DROP
  837. .dw EXIT
  838. .maybeNum:
  839. .dw compiledWord
  840. .dw PARSEI
  841. .dw LITN
  842. .dw R2P ; exit .compile
  843. .dw DROP
  844. .dw EXIT
  845. .db "DOES>"
  846. .fill 2
  847. .dw DEFINE
  848. .db 0
  849. DOES:
  850. .dw nativeWord
  851. ; We run this when we're in an entry creation context. Many things we
  852. ; need to do.
  853. ; 1. Change the code link to doesWord
  854. ; 2. Leave 2 bytes for regular cell variable.
  855. ; 3. Write down IP+2 to entry.
  856. ; 3. exit. we're done here.
  857. ld hl, (CURRENT)
  858. ld de, doesWord
  859. call DEinHL
  860. inc hl \ inc hl ; cell variable space
  861. ld de, (IP)
  862. call DEinHL
  863. ld (HERE), hl
  864. jp EXIT+2
  865. .db "IMMEDIA"
  866. .dw DOES
  867. .db 0
  868. IMMEDIATE:
  869. .dw nativeWord
  870. ld hl, (CURRENT)
  871. dec hl
  872. set FLAG_IMMED, (hl)
  873. jp next
  874. .db "IMMED?"
  875. .fill 1
  876. .dw IMMEDIATE
  877. .db 0
  878. ISIMMED:
  879. .dw nativeWord
  880. pop hl
  881. call chkPS
  882. dec hl
  883. ld de, 0
  884. bit FLAG_IMMED, (hl)
  885. jr z, .notset
  886. inc de
  887. .notset:
  888. push de
  889. jp next
  890. ; ( n -- )
  891. .db "LITN"
  892. .fill 3
  893. .dw ISIMMED
  894. .db 0
  895. LITN:
  896. .dw nativeWord
  897. ld hl, (HERE)
  898. ld de, NUMBER
  899. call DEinHL
  900. pop de ; number from stack
  901. call chkPS
  902. call DEinHL
  903. ld (HERE), hl
  904. jp next
  905. .db "LIT<"
  906. .fill 3
  907. .dw LITN
  908. .db 1 ; IMMEDIATE
  909. LITRD:
  910. .dw compiledWord
  911. .dw NUMBER
  912. .dw LIT
  913. .dw WR
  914. .dw WORD
  915. .dw .scpy
  916. .dw EXIT
  917. .scpy:
  918. .dw nativeWord
  919. pop hl
  920. ld de, (HERE)
  921. call strcpyM
  922. ld (HERE), de
  923. jp next
  924. .db "(find)"
  925. .fill 1
  926. .dw LITRD
  927. .db 0
  928. FIND_:
  929. .dw nativeWord
  930. pop hl
  931. call find
  932. jr z, .found
  933. ; not found
  934. push hl
  935. ld de, 0
  936. push de
  937. jp next
  938. .found:
  939. push de
  940. ld de, 1
  941. push de
  942. jp next
  943. .db "'"
  944. .fill 6
  945. .dw FIND_
  946. .db 0
  947. FIND:
  948. .dw compiledWord
  949. .dw WORD
  950. .dw FIND_
  951. .dw CSKIP
  952. .dw FINDERR
  953. .dw EXIT
  954. .db "[']"
  955. .fill 4
  956. .dw FIND
  957. .db 0b01 ; IMMEDIATE
  958. FINDI:
  959. .dw compiledWord
  960. .dw WORD
  961. .dw FIND_
  962. .dw CSKIP
  963. .dw FINDERR
  964. .dw LITN
  965. .dw EXIT
  966. FINDERR:
  967. .dw compiledWord
  968. .dw DROP ; Drop str addr, we don't use it
  969. .dw LIT
  970. .db "word not found", 0
  971. .dw PRINT
  972. .dw ABORT
  973. ; ( -- c )
  974. .db "KEY"
  975. .fill 4
  976. .dw FINDI
  977. .db 0
  978. KEY:
  979. .dw nativeWord
  980. call GETC
  981. ld h, 0
  982. ld l, a
  983. push hl
  984. jp next
  985. ; This is an indirect word that can be redirected through "CINPTR"
  986. ; This is not a real word because it's not meant to be referred to in Forth
  987. ; code: it is replaced in readln.fs.
  988. CIN:
  989. .dw compiledWord
  990. .dw NUMBER
  991. .dw CINPTR
  992. .dw FETCH
  993. .dw EXECUTE
  994. .dw EXIT
  995. ; ( c -- f )
  996. ; 33 CMP 1 + NOT
  997. ; The NOT is to normalize the negative/positive numbers to 1 or 0.
  998. ; Hadn't we wanted to normalize, we'd have written:
  999. ; 32 CMP 1 -
  1000. .db "WS?"
  1001. .fill 4
  1002. .dw KEY
  1003. .db 0
  1004. ISWS:
  1005. .dw compiledWord
  1006. .dw NUMBER
  1007. .dw 33
  1008. .dw CMP
  1009. .dw NUMBER
  1010. .dw 1
  1011. .dw PLUS
  1012. .dw NOT
  1013. .dw EXIT
  1014. .db "NOT"
  1015. .fill 4
  1016. .dw ISWS
  1017. .db 0
  1018. NOT:
  1019. .dw nativeWord
  1020. pop hl
  1021. call chkPS
  1022. ld a, l
  1023. or h
  1024. ld hl, 0
  1025. jr nz, .skip ; true, keep at 0
  1026. ; false, make 1
  1027. inc hl
  1028. .skip:
  1029. push hl
  1030. jp next
  1031. ; ( -- c )
  1032. ; C< DUP 32 CMP 1 - SKIP? EXIT DROP TOWORD
  1033. .db "TOWORD"
  1034. .fill 1
  1035. .dw NOT
  1036. .db 0
  1037. TOWORD:
  1038. .dw compiledWord
  1039. .dw CIN
  1040. .dw DUP
  1041. .dw ISWS
  1042. .dw CSKIP
  1043. .dw EXIT
  1044. .dw DROP
  1045. .dw TOWORD
  1046. .dw EXIT
  1047. ; Read word from C<, copy to WORDBUF, null-terminate, and return, make
  1048. ; HL point to WORDBUF.
  1049. .db "WORD"
  1050. .fill 3
  1051. .dw TOWORD
  1052. .db 0
  1053. WORD:
  1054. .dw compiledWord
  1055. .dw WORDBUF_ ; ( a )
  1056. .dw TOWORD ; ( a c )
  1057. ; branch mark
  1058. .dw OVER ; ( a c a )
  1059. .dw STORE ; ( a )
  1060. .dw NUMBER ; ( a 1 )
  1061. .dw 1
  1062. .dw PLUS ; ( a+1 )
  1063. .dw CIN ; ( a c )
  1064. .dw DUP ; ( a c c )
  1065. .dw ISWS ; ( a c f )
  1066. .dw CSKIP ; ( a c )
  1067. .dw BBR
  1068. .db 20 ; here - mark
  1069. ; at this point, we have ( a WS )
  1070. .dw DROP
  1071. .dw NUMBER
  1072. .dw 0
  1073. .dw SWAP ; ( 0 a )
  1074. .dw STORE ; ()
  1075. .dw WORDBUF_
  1076. .dw EXIT
  1077. .wcpy:
  1078. .dw nativeWord
  1079. ld de, WORDBUF
  1080. push de ; we already have our result
  1081. .loop:
  1082. ld a, (hl)
  1083. cp ' '+1
  1084. jr c, .loopend
  1085. ld (de), a
  1086. inc hl
  1087. inc de
  1088. jr .loop
  1089. .loopend:
  1090. ; null-terminate the string.
  1091. xor a
  1092. ld (de), a
  1093. jp next
  1094. .db "(parsed"
  1095. .dw WORD
  1096. .db 0
  1097. PARSED:
  1098. .dw nativeWord
  1099. pop hl
  1100. call chkPS
  1101. call parseDecimal
  1102. jr z, .success
  1103. ; error
  1104. ld de, 0
  1105. push de ; dummy
  1106. push de ; flag
  1107. jp next
  1108. .success:
  1109. push de
  1110. ld de, 1 ; flag
  1111. push de
  1112. jp next
  1113. .db "(parse)"
  1114. .dw PARSED
  1115. .db 0
  1116. PARSE:
  1117. .dw compiledWord
  1118. .dw PARSED
  1119. .dw CSKIP
  1120. .dw .error
  1121. ; success, stack is already good, we can exit
  1122. .dw EXIT
  1123. .error:
  1124. .dw compiledWord
  1125. .dw LIT
  1126. .db "unknown word", 0
  1127. .dw PRINT
  1128. .dw ABORT
  1129. ; Indirect parse caller. Reads PARSEPTR and calls
  1130. PARSEI:
  1131. .dw compiledWord
  1132. .dw PARSEPTR_
  1133. .dw FETCH
  1134. .dw EXECUTE
  1135. .dw EXIT
  1136. ; Spit name (in (HL)) + prev in (HERE) and adjust (HERE) and (CURRENT)
  1137. ; HL points to new (HERE)
  1138. ENTRYHEAD:
  1139. .dw nativeWord
  1140. pop hl
  1141. ld de, (HERE)
  1142. call strcpy
  1143. ex de, hl ; (HERE) now in HL
  1144. ld de, (CURRENT)
  1145. ld a, NAMELEN
  1146. call addHL
  1147. call DEinHL
  1148. ; Set word flags: not IMMED, so it's 0
  1149. xor a
  1150. ld (hl), a
  1151. inc hl
  1152. ld (CURRENT), hl
  1153. ld (HERE), hl
  1154. jp next
  1155. .db "CREATE"
  1156. .fill 1
  1157. .dw PARSE
  1158. .db 0
  1159. CREATE:
  1160. .dw compiledWord
  1161. .dw WORD
  1162. .dw ENTRYHEAD
  1163. .dw NUMBER
  1164. .dw cellWord
  1165. .dw WR
  1166. .dw EXIT
  1167. ; WARNING: there are no limit checks. We must be cautious, in core code, not
  1168. ; to create more than SYSV_BUFSIZE/2 sys vars.
  1169. ; Also: SYSV shouldn't be used during runtime: SYSVNXT won't point at the
  1170. ; right place. It should only be used during stage1 compilation. This is why
  1171. ; this word is not documented in dictionary.txt
  1172. .db "(sysv)"
  1173. .fill 1
  1174. .dw CREATE
  1175. .db 0
  1176. SYSV:
  1177. .dw compiledWord
  1178. .dw WORD
  1179. .dw ENTRYHEAD
  1180. .dw NUMBER
  1181. .dw sysvarWord
  1182. .dw WR
  1183. .dw NUMBER
  1184. .dw SYSVNXT
  1185. .dw FETCH
  1186. .dw WR
  1187. ; word written, now let's INC SYSVNXT
  1188. .dw NUMBER ; a
  1189. .dw SYSVNXT
  1190. .dw DUP ; a a
  1191. .dw FETCH ; a a@
  1192. .dw NUMBER ; a a@ 2
  1193. .dw 2
  1194. .dw PLUS ; a a@+2
  1195. .dw SWAP ; a@+2 a
  1196. .dw STORE
  1197. .dw EXIT
  1198. .db "HERE"
  1199. .fill 3
  1200. .dw SYSV
  1201. .db 0
  1202. HERE_: ; Caution: conflicts with actual variable name
  1203. .dw sysvarWord
  1204. .dw HERE
  1205. .db "CURRENT"
  1206. .dw HERE_
  1207. .db 0
  1208. CURRENT_:
  1209. .dw sysvarWord
  1210. .dw CURRENT
  1211. .db "(parse*"
  1212. .dw CURRENT_
  1213. .db 0
  1214. PARSEPTR_:
  1215. .dw sysvarWord
  1216. .dw PARSEPTR
  1217. .db "(wbuf)"
  1218. .fill 1
  1219. .dw PARSEPTR_
  1220. .db 0
  1221. WORDBUF_:
  1222. .dw sysvarWord
  1223. .dw WORDBUF
  1224. .db "FLAGS"
  1225. .fill 2
  1226. .dw WORDBUF_
  1227. .db 0
  1228. FLAGS_:
  1229. .dw sysvarWord
  1230. .dw FLAGS
  1231. ; ( n a -- )
  1232. .db "!"
  1233. .fill 6
  1234. .dw FLAGS_
  1235. .db 0
  1236. STORE:
  1237. .dw nativeWord
  1238. pop iy
  1239. pop hl
  1240. call chkPS
  1241. ld (iy), l
  1242. ld (iy+1), h
  1243. jp next
  1244. ; ( n a -- )
  1245. .db "C!"
  1246. .fill 5
  1247. .dw STORE
  1248. .db 0
  1249. CSTORE:
  1250. .dw nativeWord
  1251. pop hl
  1252. pop de
  1253. call chkPS
  1254. ld (hl), e
  1255. jp next
  1256. ; ( a -- n )
  1257. .db "@"
  1258. .fill 6
  1259. .dw CSTORE
  1260. .db 0
  1261. FETCH:
  1262. .dw nativeWord
  1263. pop hl
  1264. call chkPS
  1265. call intoHL
  1266. push hl
  1267. jp next
  1268. ; ( a -- c )
  1269. .db "C@"
  1270. .fill 5
  1271. .dw FETCH
  1272. .db 0
  1273. CFETCH:
  1274. .dw nativeWord
  1275. pop hl
  1276. call chkPS
  1277. ld l, (hl)
  1278. ld h, 0
  1279. push hl
  1280. jp next
  1281. ; ( a -- )
  1282. .db "DROP"
  1283. .fill 3
  1284. .dw CFETCH
  1285. .db 0
  1286. DROP:
  1287. .dw nativeWord
  1288. pop hl
  1289. jp next
  1290. ; ( a b -- b a )
  1291. .db "SWAP"
  1292. .fill 3
  1293. .dw DROP
  1294. .db 0
  1295. SWAP:
  1296. .dw nativeWord
  1297. pop hl
  1298. call chkPS
  1299. ex (sp), hl
  1300. push hl
  1301. jp next
  1302. ; ( a b c d -- c d a b )
  1303. .db "2SWAP"
  1304. .fill 2
  1305. .dw SWAP
  1306. .db 0
  1307. SWAP2:
  1308. .dw nativeWord
  1309. pop de ; D
  1310. pop hl ; C
  1311. pop bc ; B
  1312. call chkPS
  1313. ex (sp), hl ; A in HL
  1314. push de ; D
  1315. push hl ; A
  1316. push bc ; B
  1317. jp next
  1318. ; ( a -- a a )
  1319. .db "DUP"
  1320. .fill 4
  1321. .dw SWAP2
  1322. .db 0
  1323. DUP:
  1324. .dw nativeWord
  1325. pop hl
  1326. call chkPS
  1327. push hl
  1328. push hl
  1329. jp next
  1330. ; ( a b -- a b a b )
  1331. .db "2DUP"
  1332. .fill 3
  1333. .dw DUP
  1334. .db 0
  1335. DUP2:
  1336. .dw nativeWord
  1337. pop hl ; B
  1338. pop de ; A
  1339. call chkPS
  1340. push de
  1341. push hl
  1342. push de
  1343. push hl
  1344. jp next
  1345. ; ( a b -- a b a )
  1346. .db "OVER"
  1347. .fill 3
  1348. .dw DUP2
  1349. .db 0
  1350. OVER:
  1351. .dw nativeWord
  1352. pop hl ; B
  1353. pop de ; A
  1354. call chkPS
  1355. push de
  1356. push hl
  1357. push de
  1358. jp next
  1359. ; ( a b c d -- a b c d a b )
  1360. .db "2OVER"
  1361. .fill 2
  1362. .dw OVER
  1363. .db 0
  1364. OVER2:
  1365. .dw nativeWord
  1366. pop hl ; D
  1367. pop de ; C
  1368. pop bc ; B
  1369. pop iy ; A
  1370. call chkPS
  1371. push iy ; A
  1372. push bc ; B
  1373. push de ; C
  1374. push hl ; D
  1375. push iy ; A
  1376. push bc ; B
  1377. jp next
  1378. .db ">R"
  1379. .fill 5
  1380. .dw OVER2
  1381. .db 0
  1382. P2R:
  1383. .dw nativeWord
  1384. pop hl
  1385. call chkPS
  1386. call pushRS
  1387. jp next
  1388. .db "R>"
  1389. .fill 5
  1390. .dw P2R
  1391. .db 0
  1392. R2P:
  1393. .dw nativeWord
  1394. call popRS
  1395. push hl
  1396. jp next
  1397. .db "I"
  1398. .fill 6
  1399. .dw R2P
  1400. .db 0
  1401. I:
  1402. .dw nativeWord
  1403. ld l, (ix)
  1404. ld h, (ix+1)
  1405. push hl
  1406. jp next
  1407. .db "I'"
  1408. .fill 5
  1409. .dw I
  1410. .db 0
  1411. IPRIME:
  1412. .dw nativeWord
  1413. ld l, (ix-2)
  1414. ld h, (ix-1)
  1415. push hl
  1416. jp next
  1417. .db "J"
  1418. .fill 6
  1419. .dw IPRIME
  1420. .db 0
  1421. J:
  1422. .dw nativeWord
  1423. ld l, (ix-4)
  1424. ld h, (ix-3)
  1425. push hl
  1426. jp next
  1427. ; ( a b -- c ) A + B
  1428. .db "+"
  1429. .fill 6
  1430. .dw J
  1431. .db 0
  1432. PLUS:
  1433. .dw nativeWord
  1434. pop hl
  1435. pop de
  1436. call chkPS
  1437. add hl, de
  1438. push hl
  1439. jp next
  1440. ; ( a b -- c ) A - B
  1441. .db "-"
  1442. .fill 6
  1443. .dw PLUS
  1444. .db 0
  1445. MINUS:
  1446. .dw nativeWord
  1447. pop de ; B
  1448. pop hl ; A
  1449. call chkPS
  1450. or a ; reset carry
  1451. sbc hl, de
  1452. push hl
  1453. jp next
  1454. ; ( a b -- c ) A * B
  1455. .db "*"
  1456. .fill 6
  1457. .dw MINUS
  1458. .db 0
  1459. MULT:
  1460. .dw nativeWord
  1461. pop de
  1462. pop bc
  1463. call chkPS
  1464. ; DE * BC -> DE (high) and HL (low)
  1465. ld hl, 0
  1466. ld a, 0x10
  1467. .loop:
  1468. add hl, hl
  1469. rl e
  1470. rl d
  1471. jr nc, .noinc
  1472. add hl, bc
  1473. jr nc, .noinc
  1474. inc de
  1475. .noinc:
  1476. dec a
  1477. jr nz, .loop
  1478. push hl
  1479. jp next
  1480. .db "/MOD"
  1481. .fill 3
  1482. .dw MULT
  1483. .db 0
  1484. DIVMOD:
  1485. .dw nativeWord
  1486. pop de
  1487. pop hl
  1488. call chkPS
  1489. call divide
  1490. push hl
  1491. push bc
  1492. jp next
  1493. .db "AND"
  1494. .fill 4
  1495. .dw DIVMOD
  1496. .db 0
  1497. AND:
  1498. .dw nativeWord
  1499. pop hl
  1500. pop de
  1501. call chkPS
  1502. ld a, e
  1503. and l
  1504. ld l, a
  1505. ld a, d
  1506. and h
  1507. ld h, a
  1508. push hl
  1509. jp next
  1510. .db "OR"
  1511. .fill 5
  1512. .dw AND
  1513. .db 0
  1514. OR:
  1515. .dw nativeWord
  1516. pop hl
  1517. pop de
  1518. call chkPS
  1519. ld a, e
  1520. or l
  1521. ld l, a
  1522. ld a, d
  1523. or h
  1524. ld h, a
  1525. push hl
  1526. jp next
  1527. .db "XOR"
  1528. .fill 4
  1529. .dw OR
  1530. .db 0
  1531. XOR:
  1532. .dw nativeWord
  1533. pop hl
  1534. pop de
  1535. call chkPS
  1536. ld a, e
  1537. xor l
  1538. ld l, a
  1539. ld a, d
  1540. xor h
  1541. ld h, a
  1542. push hl
  1543. jp next
  1544. ; ( a1 a2 -- b )
  1545. .db "SCMP"
  1546. .fill 3
  1547. .dw XOR
  1548. .db 0
  1549. SCMP:
  1550. .dw nativeWord
  1551. pop de
  1552. pop hl
  1553. call chkPS
  1554. call strcmp
  1555. call flagsToBC
  1556. push bc
  1557. jp next
  1558. ; ( n1 n2 -- f )
  1559. .db "CMP"
  1560. .fill 4
  1561. .dw SCMP
  1562. .db 0
  1563. CMP:
  1564. .dw nativeWord
  1565. pop hl
  1566. pop de
  1567. call chkPS
  1568. or a ; clear carry
  1569. sbc hl, de
  1570. call flagsToBC
  1571. push bc
  1572. jp next
  1573. ; Skip the compword where HL is currently pointing. If it's a regular word,
  1574. ; it's easy: we inc by 2. If it's a NUMBER, we inc by 4. If it's a LIT, we skip
  1575. ; to after null-termination.
  1576. .db "SKIP?"
  1577. .fill 2
  1578. .dw CMP
  1579. .db 0
  1580. CSKIP:
  1581. .dw nativeWord
  1582. pop hl
  1583. call chkPS
  1584. ld a, h
  1585. or l
  1586. jp z, next ; False, do nothing.
  1587. ld hl, (IP)
  1588. ld de, NUMBER
  1589. call HLPointsDE
  1590. jr z, .isNum
  1591. ld de, FBR
  1592. call HLPointsDE
  1593. jr z, .isBranch
  1594. ld de, BBR
  1595. call HLPointsDE
  1596. jr z, .isBranch
  1597. ld de, LIT
  1598. call HLPointsDE
  1599. jr nz, .isWord
  1600. ; We have a literal
  1601. inc hl \ inc hl
  1602. call strskip
  1603. inc hl ; byte after word termination
  1604. jr .end
  1605. .isNum:
  1606. ; skip by 4
  1607. inc hl
  1608. ; continue to isBranch
  1609. .isBranch:
  1610. ; skip by 3
  1611. inc hl
  1612. ; continue to isWord
  1613. .isWord:
  1614. ; skip by 2
  1615. inc hl \ inc hl
  1616. .end:
  1617. ld (IP), hl
  1618. jp next
  1619. ; This word's atom is followed by 1b *relative* offset (to the cell's addr) to
  1620. ; where to branch to. For example, The branching cell of "IF THEN" would
  1621. ; contain 3. Add this value to RS.
  1622. .db "(fbr)"
  1623. .fill 2
  1624. .dw CSKIP
  1625. .db 0
  1626. FBR:
  1627. .dw nativeWord
  1628. push de
  1629. ld hl, (IP)
  1630. ld a, (hl)
  1631. call addHL
  1632. ld (IP), hl
  1633. pop de
  1634. jp next
  1635. .db "(bbr)"
  1636. .fill 2
  1637. .dw FBR
  1638. .db 0
  1639. BBR:
  1640. .dw nativeWord
  1641. ld hl, (IP)
  1642. ld d, 0
  1643. ld e, (hl)
  1644. or a ; clear carry
  1645. sbc hl, de
  1646. ld (IP), hl
  1647. jp next
  1648. LATEST:
  1649. .dw BBR