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.

1203 lines
23KB

  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. ; *** ABI STABILITY ***
  13. ;
  14. ; This unit needs to have some of its entry points stay at a stable offset.
  15. ; These have a comment over them indicating the expected offset. These should
  16. ; not move until the Grand Bootstrapping operation has been completed.
  17. ;
  18. ; When you see random ".fill" here and there, it's to ensure that stability.
  19. ; *** Defines ***
  20. ; GETC: address of a GetC routine
  21. ; PUTC: address of a PutC routine
  22. ;
  23. ; Those GetC/PutC routines are hooked through defines and have this API:
  24. ;
  25. ; GetC: Blocks until a character is read from the device and return that
  26. ; character in A.
  27. ;
  28. ; PutC: Write character specified in A onto the device.
  29. ;
  30. ; *** Const ***
  31. ; Base of the Return Stack
  32. .equ RS_ADDR 0xf000
  33. ; Buffer where WORD copies its read word to.
  34. .equ WORD_BUFSIZE 0x20
  35. ; Allocated space for sysvars (see comment above SYSVCNT)
  36. .equ SYSV_BUFSIZE 0x10
  37. ; *** Variables ***
  38. .equ INITIAL_SP RAMSTART
  39. ; wordref of the last entry of the dict.
  40. .equ CURRENT @+2
  41. ; Pointer to the next free byte in dict.
  42. .equ HERE @+2
  43. ; Interpreter pointer. See Execution model comment below.
  44. .equ IP @+2
  45. ; Global flags
  46. ; Bit 0: whether the interpreter is executing a word (as opposed to parsing)
  47. .equ FLAGS @+2
  48. ; Pointer to the system's number parsing function. It points to then entry that
  49. ; had the "(parse)" name at startup. During stage0, it's out builtin PARSE,
  50. ; but at stage1, it becomes "(parse)" from core.fs. It can also be changed at
  51. ; runtime.
  52. .equ PARSEPTR @+2
  53. ; Pointer to the word executed by "C<". During stage0, this points to KEY.
  54. ; However, KEY ain't very interactive. This is why we implement a readline
  55. ; interface in Forth, which we plug in during init. If "(c<)" exists in the
  56. ; dict, CINPTR is set to it. Otherwise, we set KEY
  57. .equ CINPTR @+2
  58. ; Pointer to (emit) word
  59. .equ EMITPTR @+2
  60. .equ WORDBUF @+2
  61. ; Sys Vars are variables with their value living in the system RAM segment. We
  62. ; need this mechanisms for core Forth source needing variables. Because core
  63. ; Forth source is pre-compiled, it needs to be able to live in ROM, which means
  64. ; that we can't compile a regular variable in it. SYSVNXT points to the next
  65. ; free space in SYSVBUF. Then, at the word level, it's a regular sysvarWord.
  66. .equ SYSVNXT @+WORD_BUFSIZE
  67. .equ SYSVBUF @+2
  68. .equ RAMEND @+SYSV_BUFSIZE
  69. ; (HERE) usually starts at RAMEND, but in certain situations, such as in stage0,
  70. ; (HERE) will begin at a strategic place.
  71. .equ HERE_INITIAL RAMEND
  72. ; EXECUTION MODEL
  73. ; After having read a line through readline, we want to interpret it. As
  74. ; a general rule, we go like this:
  75. ;
  76. ; 1. read single word from line
  77. ; 2. Can we find the word in dict?
  78. ; 3. If yes, execute that word, goto 1
  79. ; 4. Is it a number?
  80. ; 5. If yes, push that number to PS, goto 1
  81. ; 6. Error: undefined word.
  82. ;
  83. ; EXECUTING A WORD
  84. ;
  85. ; At it's core, executing a word is having the wordref in IY and call
  86. ; EXECUTE. Then, we let the word do its things. Some words are special,
  87. ; but most of them are of the compiledWord type, and that's their execution that
  88. ; we describe here.
  89. ;
  90. ; First of all, at all time during execution, the Interpreter Pointer (IP)
  91. ; points to the wordref we're executing next.
  92. ;
  93. ; When we execute a compiledWord, the first thing we do is push IP to the Return
  94. ; Stack (RS). Therefore, RS' top of stack will contain a wordref to execute
  95. ; next, after we EXIT.
  96. ;
  97. ; At the end of every compiledWord is an EXIT. This pops RS, sets IP to it, and
  98. ; continues.
  99. ; *** Stable ABI ***
  100. ; Those jumps below are supposed to stay at these offsets, always. If they
  101. ; change bootstrap binaries have to be adjusted because they rely on them.
  102. ; We're at 0 here
  103. jp forthMain
  104. .fill 0x08-$
  105. JUMPTBL:
  106. jp sysvarWord
  107. jp cellWord
  108. jp compiledWord
  109. jp pushRS
  110. jp popRS
  111. jp nativeWord
  112. jp next
  113. jp chkPS
  114. ; 24
  115. NUMBER:
  116. .dw numberWord
  117. LIT:
  118. .dw litWord
  119. .dw INITIAL_SP
  120. ; *** Code ***
  121. forthMain:
  122. ; STACK OVERFLOW PROTECTION:
  123. ; To avoid having to check for stack underflow after each pop operation
  124. ; (which can end up being prohibitive in terms of costs), we give
  125. ; ourselves a nice 6 bytes buffer. 6 bytes because we seldom have words
  126. ; requiring more than 3 items from the stack. Then, at each "exit" call
  127. ; we check for stack underflow.
  128. ld sp, 0xfffa
  129. ld (INITIAL_SP), sp
  130. ld ix, RS_ADDR
  131. ; LATEST is a label to the latest entry of the dict. This can be
  132. ; overridden if a binary dict has been grafted to the end of this
  133. ; binary
  134. ld hl, LATEST
  135. ld (CURRENT), hl
  136. ld hl, HERE_INITIAL
  137. ld (HERE), hl
  138. ; Set up PARSEPTR
  139. ld hl, .parseName
  140. call find
  141. ld (PARSEPTR), de
  142. ; Set up EMITPTR
  143. ld hl, .emitName
  144. call find
  145. ld (EMITPTR), de
  146. ; Set up CINPTR
  147. ; do we have a (c<) impl?
  148. ld hl, .cinName
  149. call find
  150. jr z, .skip
  151. ; no? then use KEY
  152. ld hl, .keyName
  153. call find
  154. .skip:
  155. ld (CINPTR), de
  156. ; Set up SYSVNXT
  157. ld hl, SYSVBUF
  158. ld (SYSVNXT), hl
  159. ld hl, .bootName
  160. call find
  161. push de
  162. jp EXECUTE+2
  163. .parseName:
  164. .db "(parse)", 0
  165. .cinName:
  166. .db "(c<)", 0
  167. .emitName:
  168. .db "(emit)", 0
  169. .keyName:
  170. .db "KEY", 0
  171. .bootName:
  172. .db "BOOT", 0
  173. INTERPRET:
  174. .dw compiledWord
  175. .dw LIT
  176. .db "INTERPRET", 0
  177. .dw FIND_
  178. .dw DROP
  179. .dw EXECUTE
  180. .fill 41
  181. ; *** Collapse OS lib copy ***
  182. ; In the process of Forth-ifying Collapse OS, apps will be slowly rewritten to
  183. ; Forth and the concept of ASM libs will become obsolete. To facilitate this
  184. ; transition, I make, right now, a copy of the routines actually used by Forth's
  185. ; native core. This also has the effect of reducing binary size right now and
  186. ; give us an idea of Forth's compactness.
  187. ; These routines below are copy/paste from apps/lib and stdio.
  188. ; copy (HL) into DE, then exchange the two, utilising the optimised HL instructions.
  189. ; ld must be done little endian, so least significant byte first.
  190. intoHL:
  191. push de
  192. ld e, (hl)
  193. inc hl
  194. ld d, (hl)
  195. ex de, hl
  196. pop de
  197. ret
  198. ; add the value of A into HL
  199. ; affects carry flag according to the 16-bit addition, Z, S and P untouched.
  200. addHL:
  201. push de
  202. ld d, 0
  203. ld e, a
  204. add hl, de
  205. pop de
  206. ret
  207. ; Copy string from (HL) in (DE), that is, copy bytes until a null char is
  208. ; encountered. The null char is also copied.
  209. ; HL and DE point to the char right after the null char.
  210. ; B indicates the length of the copied string, including null-termination.
  211. strcpy:
  212. ld b, 0
  213. .loop:
  214. ld a, (hl)
  215. ld (de), a
  216. inc hl
  217. inc de
  218. inc b
  219. or a
  220. jr nz, .loop
  221. ret
  222. ; Compares strings pointed to by HL and DE until one of them hits its null char.
  223. ; If equal, Z is set. If not equal, Z is reset. C is set if HL > DE
  224. strcmp:
  225. push hl
  226. push de
  227. .loop:
  228. ld a, (de)
  229. cp (hl)
  230. jr nz, .end ; not equal? break early. NZ is carried out
  231. ; to the caller
  232. or a ; If our chars are null, stop the cmp
  233. inc hl
  234. inc de
  235. jr nz, .loop ; Z is carried through
  236. .end:
  237. pop de
  238. pop hl
  239. ; Because we don't call anything else than CP that modify the Z flag,
  240. ; our Z value will be that of the last cp (reset if we broke the loop
  241. ; early, set otherwise)
  242. ret
  243. ; Given a string at (HL), move HL until it points to the end of that string.
  244. strskip:
  245. push bc
  246. ex af, af'
  247. xor a ; look for null char
  248. ld b, a
  249. ld c, a
  250. cpir ; advances HL regardless of comparison, so goes one too far
  251. dec hl
  252. ex af, af'
  253. pop bc
  254. ret
  255. ; Parse string at (HL) as a decimal value and return value in DE.
  256. ; Reads as many digits as it can and stop when:
  257. ; 1 - A non-digit character is read
  258. ; 2 - The number overflows from 16-bit
  259. ; HL is advanced to the character following the last successfully read char.
  260. ; Error conditions are:
  261. ; 1 - There wasn't at least one character that could be read.
  262. ; 2 - Overflow.
  263. ; Sets Z on success, unset on error.
  264. parseDecimal:
  265. ; First char is special: it has to succeed.
  266. ld a, (hl)
  267. cp '-'
  268. jr z, .negative
  269. ; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
  270. ; result in A.
  271. ; On success, the carry flag is reset. On error, it is set.
  272. add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
  273. sub 0xff-9 ; maps to 0-9 and carries if not a digit
  274. ret c ; Error. If it's C, it's also going to be NZ
  275. ; During this routine, we switch between HL and its shadow. On one side,
  276. ; we have HL the string pointer, and on the other side, we have HL the
  277. ; numerical result. We also use EXX to preserve BC, saving us a push.
  278. exx ; HL as a result
  279. ld h, 0
  280. ld l, a ; load first digit in without multiplying
  281. .loop:
  282. exx ; HL as a string pointer
  283. inc hl
  284. ld a, (hl)
  285. exx ; HL as a numerical result
  286. ; same as other above
  287. add a, 0xff-'9'
  288. sub 0xff-9
  289. jr c, .end
  290. ld b, a ; we can now use a for overflow checking
  291. add hl, hl ; x2
  292. sbc a, a ; a=0 if no overflow, a=0xFF otherwise
  293. ld d, h
  294. ld e, l ; de is x2
  295. add hl, hl ; x4
  296. rla
  297. add hl, hl ; x8
  298. rla
  299. add hl, de ; x10
  300. rla
  301. ld d, a ; a is zero unless there's an overflow
  302. ld e, b
  303. add hl, de
  304. adc a, a ; same as rla except affects Z
  305. ; Did we oveflow?
  306. jr z, .loop ; No? continue
  307. ; error, NZ already set
  308. exx ; HL is now string pointer, restore BC
  309. ; HL points to the char following the last success.
  310. ret
  311. .end:
  312. push hl ; --> lvl 1, result
  313. exx ; HL as a string pointer, restore BC
  314. pop de ; <-- lvl 1, result
  315. cp a ; ensure Z
  316. ret
  317. .negative:
  318. inc hl
  319. call parseDecimal
  320. ret nz
  321. push hl ; --> lvl 1
  322. or a ; clear carry
  323. ld hl, 0
  324. sbc hl, de
  325. ex de, hl
  326. pop hl ; <-- lvl 1
  327. xor a ; set Z
  328. ret
  329. ; *** Support routines ***
  330. ; Find the entry corresponding to word where (HL) points to and sets DE to
  331. ; point to that entry.
  332. ; Z if found, NZ if not.
  333. find:
  334. push bc
  335. push hl
  336. ; First, figure out string len
  337. ld bc, 0
  338. xor a
  339. cpir
  340. ; C has our length, negative, -1
  341. ld a, c
  342. neg
  343. dec a
  344. ; special case. zero len? we never find anything.
  345. jr z, .fail
  346. ld c, a ; C holds our length
  347. ; Let's do something weird: We'll hold HL by the *tail*. Because of our
  348. ; dict structure and because we know our lengths, it's easier to
  349. ; compare starting from the end. Currently, after CPIR, HL points to
  350. ; char after null. Let's adjust
  351. ; Because the compare loop pre-decrements, instead of DECing HL twice,
  352. ; we DEC it once.
  353. dec hl
  354. ld de, (CURRENT)
  355. .inner:
  356. ; DE is a wordref. First step, do our len correspond?
  357. push hl ; --> lvl 1
  358. push de ; --> lvl 2
  359. dec de
  360. ld a, (de)
  361. and 0x7f ; remove IMMEDIATE flag
  362. cp c
  363. jr nz, .loopend
  364. ; match, let's compare the string then
  365. dec de \ dec de ; skip prev field. One less because we
  366. ; pre-decrement
  367. ld b, c ; loop C times
  368. .loop:
  369. ; pre-decrement for easier Z matching
  370. dec de
  371. dec hl
  372. ld a, (de)
  373. cp (hl)
  374. jr nz, .loopend
  375. djnz .loop
  376. .loopend:
  377. ; At this point, Z is set if we have a match. In all cases, we want
  378. ; to pop HL and DE
  379. pop de ; <-- lvl 2
  380. pop hl ; <-- lvl 1
  381. jr z, .end ; match? we're done!
  382. ; no match, go to prev and continue
  383. push hl ; --> lvl 1
  384. dec de \ dec de \ dec de ; prev field
  385. push de ; --> lvl 2
  386. ex de, hl
  387. call intoHL
  388. ex de, hl ; DE contains prev offset
  389. pop hl ; <-- lvl 2
  390. ; HL is prev field's addr
  391. ; Is offset zero?
  392. ld a, d
  393. or e
  394. jr z, .noprev ; no prev entry
  395. ; get absolute addr from offset
  396. ; carry cleared from "or e"
  397. sbc hl, de
  398. ex de, hl ; result in DE
  399. .noprev:
  400. pop hl ; <-- lvl 1
  401. jr nz, .inner ; try to match again
  402. ; Z set? end of dict unset Z
  403. .fail:
  404. xor a
  405. inc a
  406. .end:
  407. pop hl
  408. pop bc
  409. ret
  410. ; Checks flags Z and S and sets BC to 0 if Z, 1 if C and -1 otherwise
  411. flagsToBC:
  412. ld bc, 0
  413. ret z ; equal
  414. inc bc
  415. ret m ; >
  416. ; <
  417. dec bc
  418. dec bc
  419. ret
  420. ; Write DE in (HL), advancing HL by 2.
  421. DEinHL:
  422. ld (hl), e
  423. inc hl
  424. ld (hl), d
  425. inc hl
  426. ret
  427. ; *** Stack management ***
  428. ; The Parameter stack (PS) is maintained by SP and the Return stack (RS) is
  429. ; maintained by IX. This allows us to generally use push and pop freely because
  430. ; PS is the most frequently used. However, this causes a problem with routine
  431. ; calls: because in Forth, the stack isn't balanced within each call, our return
  432. ; offset, when placed by a CALL, messes everything up. This is one of the
  433. ; reasons why we need stack management routines below. IX always points to RS'
  434. ; Top Of Stack (TOS)
  435. ;
  436. ; This return stack contain "Interpreter pointers", that is a pointer to the
  437. ; address of a word, as seen in a compiled list of words.
  438. ; Push value HL to RS
  439. pushRS:
  440. inc ix
  441. inc ix
  442. ld (ix), l
  443. ld (ix+1), h
  444. ret
  445. ; Pop RS' TOS to HL
  446. popRS:
  447. ld l, (ix)
  448. ld h, (ix+1)
  449. dec ix
  450. dec ix
  451. ret
  452. popRSIP:
  453. call popRS
  454. ld (IP), hl
  455. ret
  456. ; Verifies that SP and RS are within bounds. If it's not, call ABORT
  457. chkRS:
  458. push ix \ pop hl
  459. push de ; --> lvl 1
  460. ld de, RS_ADDR
  461. or a ; clear carry
  462. sbc hl, de
  463. pop de ; <-- lvl 1
  464. jp c, abortUnderflow
  465. ret
  466. chkPS:
  467. push hl
  468. ld hl, (INITIAL_SP)
  469. ; We have the return address for this very call on the stack and
  470. ; protected registers. Let's compensate
  471. dec hl \ dec hl
  472. dec hl \ dec hl
  473. or a ; clear carry
  474. sbc hl, sp
  475. pop hl
  476. ret nc ; (INITIAL_SP) >= SP? good
  477. jp abortUnderflow
  478. ; *** Dictionary ***
  479. ; It's important that this part is at the end of the resulting binary.
  480. ; A dictionary entry has this structure:
  481. ; - Xb name. Arbitrary long number of character (but can't be bigger than
  482. ; input buffer, of course). not null-terminated
  483. ; - 2b prev offset
  484. ; - 1b size + IMMEDIATE flag
  485. ; - 2b code pointer
  486. ; - Parameter field (PF)
  487. ;
  488. ; The prev offset is the number of bytes between the prev field and the
  489. ; previous word's code pointer.
  490. ;
  491. ; The size + flag indicate the size of the name field, with the 7th bit
  492. ; being the IMMEDIATE flag.
  493. ;
  494. ; The code pointer point to "word routines". These routines expect to be called
  495. ; with IY pointing to the PF. They themselves are expected to end by jumping
  496. ; to the address at (IP). They will usually do so with "jp next".
  497. ;
  498. ; That's for "regular" words (words that are part of the dict chain). There are
  499. ; also "special words", for example NUMBER, LIT, FBR, that have a slightly
  500. ; different structure. They're also a pointer to an executable, but as for the
  501. ; other fields, the only one they have is the "flags" field.
  502. ; This routine is jumped to at the end of every word. In it, we jump to current
  503. ; IP, but we also take care of increasing it my 2 before jumping
  504. next:
  505. ; Before we continue: are stacks within bounds?
  506. call chkPS
  507. call chkRS
  508. ld de, (IP)
  509. ld h, d
  510. ld l, e
  511. inc de \ inc de
  512. ld (IP), de
  513. ; HL is an atom list pointer. We need to go into it to have a wordref
  514. ld e, (hl)
  515. inc hl
  516. ld d, (hl)
  517. push de
  518. jp EXECUTE+2
  519. ; Execute a word containing native code at its PF address (PFA)
  520. nativeWord:
  521. jp (iy)
  522. ; Execute a list of atoms, which always end with EXIT.
  523. ; IY points to that list. What do we do:
  524. ; 1. Push current IP to RS
  525. ; 2. Set new IP to the second atom of the list
  526. ; 3. Execute the first atom of the list.
  527. compiledWord:
  528. ld hl, (IP)
  529. call pushRS
  530. push iy \ pop hl
  531. inc hl
  532. inc hl
  533. ld (IP), hl
  534. ; IY still is our atom reference...
  535. ld l, (iy)
  536. ld h, (iy+1)
  537. push hl ; argument for EXECUTE
  538. jp EXECUTE+2
  539. ; Pushes the PFA directly
  540. cellWord:
  541. push iy
  542. jp next
  543. ; Pushes the address in the first word of the PF
  544. sysvarWord:
  545. ld l, (iy)
  546. ld h, (iy+1)
  547. push hl
  548. jp next
  549. ; The word was spawned from a definition word that has a DOES>. PFA+2 (right
  550. ; after the actual cell) is a link to the slot right after that DOES>.
  551. ; Therefore, what we need to do push the cell addr like a regular cell, then
  552. ; follow the link from the PFA, and then continue as a regular compiledWord.
  553. doesWord:
  554. push iy ; like a regular cell
  555. ld l, (iy+2)
  556. ld h, (iy+3)
  557. push hl \ pop iy
  558. jr compiledWord
  559. ; This is not a word, but a number literal. This works a bit differently than
  560. ; others: PF means nothing and the actual number is placed next to the
  561. ; numberWord reference in the compiled word list. What we need to do to fetch
  562. ; that number is to play with the IP.
  563. numberWord:
  564. ld hl, (IP) ; (HL) is out number
  565. ld e, (hl)
  566. inc hl
  567. ld d, (hl)
  568. inc hl
  569. ld (IP), hl ; advance IP by 2
  570. push de
  571. jp next
  572. ; Similarly to numberWord, this is not a real word, but a string literal.
  573. ; Instead of being followed by a 2 bytes number, it's followed by a
  574. ; null-terminated string. When called, puts the string's address on PS
  575. litWord:
  576. ld hl, (IP)
  577. push hl
  578. call strskip
  579. inc hl ; after null termination
  580. ld (IP), hl
  581. jp next
  582. ; Pop previous IP from Return stack and execute it.
  583. ; ( R:I -- )
  584. .db "EXIT"
  585. .dw 0
  586. .db 4
  587. EXIT:
  588. .dw nativeWord
  589. call popRSIP
  590. jp next
  591. ; ( R:I -- )
  592. .db "QUIT"
  593. .dw $-EXIT
  594. .db 4
  595. QUIT:
  596. .dw compiledWord
  597. .dw NUMBER
  598. .dw 0
  599. .dw FLAGS_
  600. .dw STORE
  601. .dw .private
  602. .dw INTERPRET
  603. .private:
  604. .dw nativeWord
  605. ld ix, RS_ADDR
  606. jp next
  607. abortUnderflow:
  608. ld hl, .name
  609. call find
  610. push de
  611. jp EXECUTE+2
  612. .name:
  613. .db "(uflw)", 0
  614. .fill 50
  615. ; STABLE ABI
  616. ; Offset: 02aa
  617. .out $
  618. ; ( c -- )
  619. .db "EMIT"
  620. .dw $-QUIT
  621. .db 4
  622. EMIT:
  623. .dw compiledWord
  624. .dw NUMBER
  625. .dw EMITPTR
  626. .dw FETCH
  627. .dw EXECUTE
  628. .dw EXIT
  629. .fill 71
  630. .db ","
  631. .dw $-EMIT
  632. .db 1
  633. WR:
  634. .dw nativeWord
  635. pop de
  636. call chkPS
  637. ld hl, (HERE)
  638. call DEinHL
  639. ld (HERE), hl
  640. jp next
  641. .fill 100
  642. ; ( addr -- )
  643. .db "EXECUTE"
  644. .dw $-WR
  645. .db 7
  646. ; STABLE ABI
  647. ; Offset: 0388
  648. .out $
  649. EXECUTE:
  650. .dw nativeWord
  651. pop iy ; is a wordref
  652. call chkPS
  653. ld l, (iy)
  654. ld h, (iy+1)
  655. ; HL points to code pointer
  656. inc iy
  657. inc iy
  658. ; IY points to PFA
  659. jp (hl) ; go!
  660. .fill 77
  661. .db "DOES>"
  662. .dw $-EXECUTE
  663. .db 5
  664. DOES:
  665. .dw nativeWord
  666. ; We run this when we're in an entry creation context. Many things we
  667. ; need to do.
  668. ; 1. Change the code link to doesWord
  669. ; 2. Leave 2 bytes for regular cell variable.
  670. ; 3. Write down IP+2 to entry.
  671. ; 3. exit. we're done here.
  672. ld hl, (CURRENT)
  673. ld de, doesWord
  674. call DEinHL
  675. inc hl \ inc hl ; cell variable space
  676. ld de, (IP)
  677. call DEinHL
  678. ld (HERE), hl
  679. jp EXIT+2
  680. .fill 51
  681. ; ( n -- )
  682. .db "LITN"
  683. .dw $-DOES
  684. .db 4
  685. LITN:
  686. .dw nativeWord
  687. ld hl, (HERE)
  688. ld de, NUMBER
  689. call DEinHL
  690. pop de ; number from stack
  691. call chkPS
  692. call DEinHL
  693. ld (HERE), hl
  694. jp next
  695. .db "SCPY"
  696. .dw $-LITN
  697. .db 4
  698. SCPY:
  699. .dw nativeWord
  700. pop hl
  701. ld de, (HERE)
  702. call strcpy
  703. ld (HERE), de
  704. jp next
  705. .db "(find)"
  706. .dw $-SCPY
  707. .db 6
  708. ; STABLE ABI
  709. ; Offset: 047c
  710. .out $
  711. FIND_:
  712. .dw nativeWord
  713. pop hl
  714. call find
  715. jr z, .found
  716. ; not found
  717. push hl
  718. ld de, 0
  719. push de
  720. jp next
  721. .found:
  722. push de
  723. ld de, 1
  724. push de
  725. jp next
  726. ; This is an indirect word that can be redirected through "CINPTR"
  727. ; code: it is replaced in readln.fs.
  728. .db "C<"
  729. .dw $-FIND_
  730. .db 2
  731. CIN:
  732. .dw compiledWord
  733. .dw NUMBER
  734. .dw CINPTR
  735. .dw FETCH
  736. .dw EXECUTE
  737. .dw EXIT
  738. ; ( c -- f )
  739. ; 33 CMP 1 + NOT
  740. ; The NOT is to normalize the negative/positive numbers to 1 or 0.
  741. ; Hadn't we wanted to normalize, we'd have written:
  742. ; 32 CMP 1 -
  743. .db "WS?"
  744. .dw $-CIN
  745. .db 3
  746. ISWS:
  747. .dw compiledWord
  748. .dw NUMBER
  749. .dw 33
  750. .dw CMP
  751. .dw NUMBER
  752. .dw 1
  753. .dw PLUS
  754. .dw NOT
  755. .dw EXIT
  756. .db "NOT"
  757. .dw $-ISWS
  758. .db 3
  759. NOT:
  760. .dw nativeWord
  761. pop hl
  762. call chkPS
  763. ld a, l
  764. or h
  765. ld hl, 0
  766. jr nz, .skip ; true, keep at 0
  767. ; false, make 1
  768. inc hl
  769. .skip:
  770. push hl
  771. jp next
  772. ; ( -- c )
  773. ; C< DUP 32 CMP 1 - SKIP? EXIT DROP TOWORD
  774. .db "TOWORD"
  775. .dw $-NOT
  776. .db 6
  777. TOWORD:
  778. .dw compiledWord
  779. .dw CIN
  780. .dw DUP
  781. .dw ISWS
  782. .dw CSKIP
  783. .dw EXIT
  784. .dw DROP
  785. .dw TOWORD
  786. .dw EXIT
  787. ; Read word from C<, copy to WORDBUF, null-terminate, and return, make
  788. ; HL point to WORDBUF.
  789. .db "WORD"
  790. .dw $-TOWORD
  791. .db 4
  792. ; STABLE ABI
  793. ; Offset: 04f7
  794. .out $
  795. WORD:
  796. .dw compiledWord
  797. .dw NUMBER ; ( a )
  798. .dw WORDBUF
  799. .dw TOWORD ; ( a c )
  800. ; branch mark
  801. .dw OVER ; ( a c a )
  802. .dw STORE ; ( a )
  803. .dw NUMBER ; ( a 1 )
  804. .dw 1
  805. .dw PLUS ; ( a+1 )
  806. .dw CIN ; ( a c )
  807. .dw DUP ; ( a c c )
  808. .dw ISWS ; ( a c f )
  809. .dw CSKIP ; ( a c )
  810. .dw BBR
  811. .db 20 ; here - mark
  812. ; at this point, we have ( a WS )
  813. .dw DROP
  814. .dw NUMBER
  815. .dw 0
  816. .dw SWAP ; ( 0 a )
  817. .dw STORE ; ()
  818. .dw NUMBER
  819. .dw WORDBUF
  820. .dw EXIT
  821. .wcpy:
  822. .dw nativeWord
  823. ld de, WORDBUF
  824. push de ; we already have our result
  825. .loop:
  826. ld a, (hl)
  827. cp ' '+1
  828. jr c, .loopend
  829. ld (de), a
  830. inc hl
  831. inc de
  832. jr .loop
  833. .loopend:
  834. ; null-terminate the string.
  835. xor a
  836. ld (de), a
  837. jp next
  838. .db "(parsed)"
  839. .dw $-WORD
  840. .db 8
  841. PARSED:
  842. .dw nativeWord
  843. pop hl
  844. call chkPS
  845. call parseDecimal
  846. jr z, .success
  847. ; error
  848. ld de, 0
  849. push de ; dummy
  850. push de ; flag
  851. jp next
  852. .success:
  853. push de
  854. ld de, 1 ; flag
  855. push de
  856. jp next
  857. .fill 96
  858. .db "JTBL"
  859. .dw $-PARSED
  860. .db 4
  861. JTBL:
  862. .dw sysvarWord
  863. .dw JUMPTBL
  864. ; STABLE ABI (every sysvars)
  865. ; Offset: 05ca
  866. .out $
  867. .db "HERE"
  868. .dw $-JTBL
  869. .db 4
  870. HERE_: ; Caution: conflicts with actual variable name
  871. .dw sysvarWord
  872. .dw HERE
  873. .db "CURRENT"
  874. .dw $-HERE_
  875. .db 7
  876. CURRENT_:
  877. .dw sysvarWord
  878. .dw CURRENT
  879. .db "(parse*)"
  880. .dw $-CURRENT_
  881. .db 8
  882. PARSEPTR_:
  883. .dw sysvarWord
  884. .dw PARSEPTR
  885. .db "FLAGS"
  886. .dw $-PARSEPTR_
  887. .db 5
  888. FLAGS_:
  889. .dw sysvarWord
  890. .dw FLAGS
  891. .db "SYSVNXT"
  892. .dw $-FLAGS_
  893. .db 7
  894. SYSVNXT_:
  895. .dw sysvarWord
  896. .dw SYSVNXT
  897. ; ( n a -- )
  898. .db "!"
  899. .dw $-SYSVNXT_
  900. .db 1
  901. ; STABLE ABI
  902. ; Offset: 0610
  903. .out $
  904. STORE:
  905. .dw nativeWord
  906. pop iy
  907. pop hl
  908. call chkPS
  909. ld (iy), l
  910. ld (iy+1), h
  911. jp next
  912. ; ( a -- n )
  913. .db "@"
  914. .dw $-STORE
  915. .db 1
  916. FETCH:
  917. .dw nativeWord
  918. pop hl
  919. call chkPS
  920. call intoHL
  921. push hl
  922. jp next
  923. ; ( a -- )
  924. .db "DROP"
  925. .dw $-FETCH
  926. .db 4
  927. ; STABLE ABI
  928. DROP:
  929. .dw nativeWord
  930. pop hl
  931. jp next
  932. ; ( a b -- b a )
  933. .db "SWAP"
  934. .dw $-DROP
  935. .db 4
  936. SWAP:
  937. .dw nativeWord
  938. pop hl
  939. call chkPS
  940. ex (sp), hl
  941. push hl
  942. jp next
  943. ; ( a -- a a )
  944. .db "DUP"
  945. .dw $-SWAP
  946. .db 3
  947. DUP:
  948. .dw nativeWord
  949. pop hl
  950. call chkPS
  951. push hl
  952. push hl
  953. jp next
  954. ; ( a b -- a b a )
  955. .db "OVER"
  956. .dw $-DUP
  957. .db 4
  958. OVER:
  959. .dw nativeWord
  960. pop hl ; B
  961. pop de ; A
  962. call chkPS
  963. push de
  964. push hl
  965. push de
  966. jp next
  967. .fill 31
  968. ; ( a b -- c ) A + B
  969. .db "+"
  970. .dw $-OVER
  971. .db 1
  972. PLUS:
  973. .dw nativeWord
  974. pop hl
  975. pop de
  976. call chkPS
  977. add hl, de
  978. push hl
  979. jp next
  980. .fill 18
  981. ; ( a1 a2 -- b )
  982. .db "SCMP"
  983. .dw $-PLUS
  984. .db 4
  985. SCMP:
  986. .dw nativeWord
  987. pop de
  988. pop hl
  989. call chkPS
  990. call strcmp
  991. call flagsToBC
  992. push bc
  993. jp next
  994. ; ( n1 n2 -- f )
  995. .db "CMP"
  996. .dw $-SCMP
  997. .db 3
  998. CMP:
  999. .dw nativeWord
  1000. pop hl
  1001. pop de
  1002. call chkPS
  1003. or a ; clear carry
  1004. sbc hl, de
  1005. call flagsToBC
  1006. push bc
  1007. jp next
  1008. ; Skip the compword where HL is currently pointing. If it's a regular word,
  1009. ; it's easy: we inc by 2. If it's a NUMBER, we inc by 4. If it's a LIT, we skip
  1010. ; to after null-termination.
  1011. .db "SKIP?"
  1012. .dw $-CMP
  1013. .db 5
  1014. ; STABLE ABI
  1015. ; Offset: 06ee
  1016. .out $
  1017. CSKIP:
  1018. .dw nativeWord
  1019. pop hl
  1020. call chkPS
  1021. ld a, h
  1022. or l
  1023. jp z, next ; False, do nothing.
  1024. ld hl, (IP)
  1025. ld de, NUMBER
  1026. call .HLPointsDE
  1027. jr z, .isNum
  1028. ld de, FBR
  1029. call .HLPointsDE
  1030. jr z, .isBranch
  1031. ld de, BBR
  1032. call .HLPointsDE
  1033. jr z, .isBranch
  1034. ld de, LIT
  1035. call .HLPointsDE
  1036. jr nz, .isWord
  1037. ; We have a literal
  1038. inc hl \ inc hl
  1039. call strskip
  1040. inc hl ; byte after word termination
  1041. jr .end
  1042. .isNum:
  1043. ; skip by 4
  1044. inc hl
  1045. ; continue to isBranch
  1046. .isBranch:
  1047. ; skip by 3
  1048. inc hl
  1049. ; continue to isWord
  1050. .isWord:
  1051. ; skip by 2
  1052. inc hl \ inc hl
  1053. .end:
  1054. ld (IP), hl
  1055. jp next
  1056. ; Sets Z if (HL) == E and (HL+1) == D
  1057. .HLPointsDE:
  1058. ld a, (hl)
  1059. cp e
  1060. ret nz ; no
  1061. inc hl
  1062. ld a, (hl)
  1063. dec hl
  1064. cp d ; Z has our answer
  1065. ret
  1066. ; This word's atom is followed by 1b *relative* offset (to the cell's addr) to
  1067. ; where to branch to. For example, The branching cell of "IF THEN" would
  1068. ; contain 3. Add this value to RS.
  1069. .db "(fbr)"
  1070. .dw $-CSKIP
  1071. .db 5
  1072. ; STABLE ABI
  1073. ; Offset: 073e
  1074. .out $
  1075. FBR:
  1076. .dw nativeWord
  1077. push de
  1078. ld hl, (IP)
  1079. ld a, (hl)
  1080. call addHL
  1081. ld (IP), hl
  1082. pop de
  1083. jp next
  1084. .db "(bbr)"
  1085. .dw $-FBR
  1086. .db 5
  1087. ; STABLE ABI
  1088. ; Offset: 0757
  1089. .out $
  1090. BBR:
  1091. .dw nativeWord
  1092. ld hl, (IP)
  1093. ld d, 0
  1094. ld e, (hl)
  1095. or a ; clear carry
  1096. sbc hl, de
  1097. ld (IP), hl
  1098. jp next
  1099. ; To allow dict binaries to "hook themselves up", we always end such binary
  1100. ; with a dummy, *empty* entry. Therefore, we can have a predictable place for
  1101. ; getting a prev label.
  1102. .db "_bend"
  1103. .dw $-BBR
  1104. .db 5