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.

699 line
15KB

  1. #include "user.inc"
  2. ; *** Consts ***
  3. ; Number of rows in the argspec table
  4. ARGSPEC_TBL_CNT .equ 27
  5. ; Number of rows in the primary instructions table
  6. INSTR_TBLP_CNT .equ 50
  7. ; size in bytes of each row in the primary instructions table
  8. INSTR_TBLP_ROWSIZE .equ 8
  9. ; *** Code ***
  10. .org USER_CODE
  11. call parseLine
  12. ld b, 0
  13. ld c, a ; written bytes
  14. ret
  15. unsetZ:
  16. push bc
  17. ld b, a
  18. inc b
  19. cp b
  20. pop bc
  21. ret
  22. ; run RLA the number of times specified in B
  23. rlaX:
  24. ; first, see if B == 0 to see if we need to bail out
  25. inc b
  26. dec b
  27. ret z ; Z flag means we had B = 0
  28. .loop: rla
  29. djnz .loop
  30. ret
  31. ; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
  32. ; result in A.
  33. ;
  34. ; On success, the carry flag is reset. On error, it is set.
  35. parseDecimal:
  36. ; First, let's see if we have an easy 0-9 case
  37. cp '0'
  38. ret c ; if < '0', we have a problem
  39. cp '9'+1
  40. ; We are in the 0-9 range
  41. sub a, '0' ; C is clear
  42. ret
  43. ; Parses the string at (HL) and returns the 16-bit value in IX.
  44. ; As soon as the number doesn't fit 16-bit any more, parsing stops and the
  45. ; number is invalid. If the number is valid, Z is set, otherwise, unset.
  46. parseNumber:
  47. push hl
  48. push de
  49. ld ix, 0
  50. .loop:
  51. ld a, (hl)
  52. cp 0
  53. jr z, .end ; success!
  54. call parseDecimal
  55. jr c, .error
  56. ; Now, let's add A to IX. First, multiply by 10.
  57. ld d, ixh ; we need a copy of the initial copy for later
  58. ld e, ixl
  59. add ix, ix ; x2
  60. add ix, ix ; x4
  61. add ix, ix ; x8
  62. add ix, de ; x9
  63. add ix, de ; x10
  64. add a, ixl
  65. jr nc, .nocarry
  66. inc ixh
  67. .nocarry:
  68. ld ixl, a
  69. ; We didn't bother checking for the C flag at each step because we
  70. ; check for overflow afterwards. If ixh < d, we overflowed
  71. ld a, ixh
  72. cp d
  73. jr c, .error ; carry is set? overflow
  74. inc hl
  75. jr .loop
  76. .error:
  77. call unsetZ
  78. .end:
  79. pop de
  80. pop hl
  81. ret
  82. ; Sets Z is A is ';', CR, LF, or null.
  83. isLineEnd:
  84. cp ';'
  85. ret z
  86. cp 0
  87. ret z
  88. cp 0x0d
  89. ret z
  90. cp 0x0a
  91. ret
  92. ; Sets Z is A is ' ' or ','
  93. isSep:
  94. cp ' '
  95. ret z
  96. cp ','
  97. ret
  98. ; Sets Z is A is ' ', ',', ';', CR, LF, or null.
  99. isSepOrLineEnd:
  100. call isSep
  101. ret z
  102. call isLineEnd
  103. ret
  104. ; read word in (HL) and put it in (DE), null terminated. A is the read
  105. ; length. HL is advanced to the next separator char.
  106. readWord:
  107. push bc
  108. ld b, 4
  109. .loop:
  110. ld a, (hl)
  111. call isSepOrLineEnd
  112. jr z, .success
  113. call JUMP_UPCASE
  114. ld (de), a
  115. inc hl
  116. inc de
  117. djnz .loop
  118. .success:
  119. xor a
  120. ld (de), a
  121. ld a, 4
  122. sub a, b
  123. jr .end
  124. .error:
  125. xor a
  126. ld (de), a
  127. .end:
  128. pop bc
  129. ret
  130. ; (HL) being a string, advance it to the next non-sep character.
  131. ; Set Z if we could do it before the line ended, reset Z if we couldn't.
  132. toWord:
  133. .loop:
  134. ld a, (hl)
  135. call isLineEnd
  136. jr z, .error
  137. call isSep
  138. jr nz, .success
  139. inc hl
  140. jr .loop
  141. .error:
  142. ; we need the Z flag to be unset and it is set now. Let's CP with
  143. ; something it can't be equal to, something not a line end.
  144. cp 'a' ; Z flag unset
  145. ret
  146. .success:
  147. ; We need the Z flag to be set and it is unset. Let's compare it with
  148. ; itself to return a set Z
  149. cp a
  150. ret
  151. ; Read arg from (HL) into argspec at (DE)
  152. ; HL is advanced to the next word. Z is set if there's a next word.
  153. readArg:
  154. push de
  155. ld de, tmpBuf
  156. call readWord
  157. push hl
  158. ld hl, tmpBuf
  159. call parseArg
  160. pop hl
  161. pop de
  162. ld (de), a
  163. ; When A is a number, IX is set with the value of that number. Because
  164. ; We don't use the space allocated to store those numbers in any other
  165. ; occasion, we store IX there unconditonally, LSB first.
  166. inc de
  167. ld a, ixl
  168. ld (de), a
  169. inc de
  170. ld a, ixh
  171. ld (de), a
  172. call toWord
  173. ret
  174. ; Read line from (HL) into (curWord), (curArg1) and (curArg2)
  175. readLine:
  176. push de
  177. xor a
  178. ld (curWord), a
  179. ld (curArg1), a
  180. ld (curArg2), a
  181. ld de, curWord
  182. call readWord
  183. call toWord
  184. jr nz, .end
  185. ld de, curArg1
  186. call readArg
  187. jr nz, .end
  188. ld de, curArg2
  189. call readArg
  190. .end:
  191. pop de
  192. ret
  193. ; Returns length of string at (HL) in A.
  194. strlen:
  195. push bc
  196. push hl
  197. ld bc, 0
  198. ld a, 0 ; look for null char
  199. .loop:
  200. cpi
  201. jp z, .found
  202. jr .loop
  203. .found:
  204. ; How many char do we have? the (NEG BC)-1, which started at 0 and
  205. ; decreased at each CPI call. In this routine, we stay in the 8-bit
  206. ; realm, so C only.
  207. ld a, c
  208. neg
  209. dec a
  210. pop hl
  211. pop bc
  212. ret
  213. ; find argspec for string at (HL). Returns matching argspec in A.
  214. ; Return value 0xff holds a special meaning: arg is not empty, but doesn't match
  215. ; any argspec (A == 0 means arg is empty). A return value of 0xff means an
  216. ; error.
  217. ;
  218. ; If the parsed argument is a number constant, 'N' is returned and IX contains
  219. ; the value of that constant.
  220. parseArg:
  221. call strlen
  222. cp 0
  223. ret z ; empty string? A already has our result: 0
  224. push bc
  225. push de
  226. push hl
  227. ld de, argspecTbl
  228. ; DE now points the the "argspec char" part of the entry, but what
  229. ; we're comparing in the loop is the string next to it. Let's offset
  230. ; DE by one so that the loop goes through strings.
  231. inc de
  232. ld b, ARGSPEC_TBL_CNT
  233. .loop1:
  234. ld a, 4
  235. call JUMP_STRNCMP
  236. jr z, .found ; got it!
  237. ld a, 5
  238. call JUMP_ADDDE
  239. djnz .loop1
  240. ; We exhausted the argspecs. Let's see if it's a number
  241. call parseNumber
  242. jr nz, .notanumber
  243. ; Alright, we have a parsed number in IX. We're done.
  244. ld a, 'N'
  245. jr .end
  246. .notanumber:
  247. ld a, 0xff
  248. jr .end
  249. .found:
  250. ; found the matching argspec row. Our result is one byte left of DE.
  251. dec de
  252. ld a, (de)
  253. .end:
  254. pop hl
  255. pop de
  256. pop bc
  257. ret
  258. ; Returns, with Z, whether A is a groupId
  259. isGroupId:
  260. cp 0xc ; max group id + 1
  261. jr nc, .notgroup ; >= 0xc? not a group
  262. cp 0
  263. jr z, .notgroup ; 0? not supposed to happen. something's wrong.
  264. ; A is a group. ensure Z is set
  265. cp a
  266. ret
  267. .notgroup:
  268. call unsetZ
  269. ret
  270. ; Find argspec A in group id H.
  271. ; Set Z according to whether we found the argspec
  272. ; If found, the value in A is the argspec value in the group (its index).
  273. findInGroup:
  274. push bc
  275. push hl
  276. cp 0 ; is our arg empty? If yes, we have nothing to do
  277. jr z, .notfound
  278. push af
  279. ld a, h
  280. cp 0xa
  281. jr z, .specialGroupCC
  282. cp 0xb
  283. jr z, .specialGroupABCDEHL
  284. pop af
  285. ; regular group
  286. push de
  287. ld de, argGrpTbl
  288. ; group ids start at 1. decrease it, then multiply by 4 to have a
  289. ; proper offset in argGrpTbl
  290. dec h
  291. push af
  292. ld a, h
  293. rla
  294. rla
  295. call JUMP_ADDDE ; At this point, DE points to our group
  296. pop af
  297. ex hl, de ; And now, HL points to the group
  298. pop de
  299. ld bc, 4
  300. jr .find
  301. .specialGroupCC:
  302. ld hl, argGrpCC
  303. jr .specialGroupEnd
  304. .specialGroupABCDEHL:
  305. ld hl, argGrpABCDEHL
  306. .specialGroupEnd:
  307. pop af ; from the push af just before the special group check
  308. ld bc, 8
  309. .find:
  310. ; This part is common to regular and special group. We expect HL to
  311. ; point to the group and BC to contain its length.
  312. push bc ; save the start value loop index so we can sub
  313. .loop:
  314. cpi
  315. jr z, .found
  316. jp po, .notfound
  317. jr .loop
  318. .found:
  319. ; we found our result! Now, what we want to put in A is the index of
  320. ; the found argspec.
  321. pop hl ; we pop from the "push bc" above. L is now 4 or 8
  322. ld a, l
  323. sub c
  324. dec a ; cpi DECs BC even when there's a match, so C == the
  325. ; number of iterations we've made. But our index is
  326. ; zero-based (1 iteration == 0 index).
  327. cp a ; ensure Z is set
  328. jr .end
  329. .notfound:
  330. pop bc ; from the push bc in .find
  331. call unsetZ
  332. .end:
  333. pop hl
  334. pop bc
  335. ret
  336. ; Compare argspec from instruction table in A with argument in (HL).
  337. ; For constant args, it's easy: if A == (HL), it's a success.
  338. ; If it's not this, then we check if it's a numerical arg.
  339. ; If A is a group ID, we do something else: we check that (HL) exists in the
  340. ; groupspec (argGrpTbl)
  341. matchArg:
  342. cp a, (hl)
  343. ret z
  344. ; not an exact match, let's check for numerical constants.
  345. cp 'N'
  346. jr z, .expectsNumber
  347. cp 'n'
  348. jr z, .expectsNumber
  349. jr .notNumber
  350. .expectsNumber:
  351. ld a, (hl)
  352. cp 'N' ; In parsed arg, we don't have 'n', only 'N'
  353. ret ; whether we match or not, the result of Z is the good
  354. ; one
  355. .notNumber:
  356. ; A bit of a delicate situation here: we want A to go in H but also
  357. ; (HL) to go in A. If not careful, we overwrite each other. EXX is
  358. ; necessary to avoid invoving other registers.
  359. push hl
  360. exx
  361. ld h, a
  362. push hl
  363. exx
  364. ld a, (hl)
  365. pop hl
  366. call findInGroup
  367. pop hl
  368. ret
  369. ; Compare primary row at (DE) with string at curWord. Sets Z flag if there's a
  370. ; match, reset if not.
  371. matchPrimaryRow:
  372. push hl
  373. push ix
  374. ld hl, curWord
  375. ld a, 4
  376. call JUMP_STRNCMP
  377. jr nz, .end
  378. ; name matches, let's see the rest
  379. ld ixh, d
  380. ld ixl, e
  381. ld hl, curArg1
  382. ld a, (ix+4)
  383. call matchArg
  384. jr nz, .end
  385. ld hl, curArg2
  386. ld a, (ix+5)
  387. call matchArg
  388. .end:
  389. pop ix
  390. pop hl
  391. ret
  392. ; Parse line at (HL) and write resulting opcode(s) in (DE). Returns the number
  393. ; of bytes written in A.
  394. ;
  395. ; Overwrites IX
  396. parseLine:
  397. call readLine
  398. ; Check whether we have errors. We don't do any parsing if we do.
  399. ld a, (curArg1)
  400. cp 0xff
  401. jr z, .error
  402. ret z
  403. ld a, (curArg2)
  404. cp 0xff
  405. jr nz, .noerror
  406. .error:
  407. ld a, 0
  408. ret
  409. .noerror:
  410. push de
  411. ld de, instrTBlPrimary
  412. ld b, INSTR_TBLP_CNT
  413. .loop:
  414. ld a, (de)
  415. call matchPrimaryRow
  416. jr z, .match
  417. ld a, INSTR_TBLP_ROWSIZE
  418. call JUMP_ADDDE
  419. djnz .loop
  420. ; no match
  421. xor a
  422. pop de
  423. ret
  424. .match:
  425. ; We have our matching instruction row. We're getting pretty near our
  426. ; goal here!
  427. ; First, let's go in IX mode. It's easier to deal with offsets here.
  428. ld ixh, d
  429. ld ixl, e
  430. ; First, let's see if we're dealing with a group here
  431. ld a, (ix+4) ; first argspec
  432. call isGroupId
  433. jr z, .firstArgIsGroup
  434. ; First arg not a group. Maybe second is?
  435. ld a, (ix+5) ; 2nd argspec
  436. call isGroupId
  437. jr nz, .notgroup
  438. ; Second arg is group
  439. ld de, curArg2
  440. jr .isGroup
  441. .firstArgIsGroup:
  442. ld de, curArg1
  443. .isGroup:
  444. ; A is a group, good, now let's get its value. DE is pointing to
  445. ; the argument.
  446. push hl
  447. ld h, a
  448. ld a, (de)
  449. call findInGroup ; we don't check for match, it's supposed to
  450. ; always match. Something is very wrong if it
  451. ; doesn't
  452. ; Now, we have our arg "group value" in A. Were going to need to
  453. ; displace it left by the number of steps specified in the table.
  454. push bc
  455. push af
  456. ld a, (ix+6) ; displacement bit
  457. ld b, a
  458. pop af
  459. call rlaX
  460. pop bc
  461. ; At this point, we have a properly displaced value in A. We'll want
  462. ; to OR it with the opcode.
  463. or (ix+7) ; upcode
  464. pop hl
  465. ; Success!
  466. jr .writeFirstOpcode
  467. .notgroup:
  468. ; not a group? easy as pie: we return the opcode directly.
  469. ld a, (ix+7) ; upcode is on 8th byte
  470. .writeFirstOpcode:
  471. ; At the end, we have our final opcode in A!
  472. pop de
  473. ld (de), a
  474. ; Good, we are probably finished here for many primary opcodes. However,
  475. ; some primary opcodes take 8 or 16 bit constants as an argument and
  476. ; if that's the case here, we need to write it too.
  477. ; We still have our instruction row in IX. Let's revisit it.
  478. push hl ; we use HL to point to the currently read arg
  479. ld a, (ix+4) ; first argspec
  480. ld hl, curArg1
  481. cp 'N'
  482. jr z, .withWord
  483. cp 'n'
  484. jr z, .withByte
  485. ld a, (ix+5) ; second argspec
  486. ld hl, curArg2
  487. cp 'N'
  488. jr z, .withWord
  489. cp 'n'
  490. jr z, .withByte
  491. ; nope, no number, aright, only one opcode
  492. ld a, 1
  493. jr .end
  494. .withByte:
  495. ; verify that the MSB in argument is zero
  496. inc hl
  497. inc hl ; MSB is 2nd byte
  498. ld a, (hl)
  499. dec hl ; HL now points to LSB
  500. cp 0
  501. jr nz, .numberTruncated
  502. ; Clear to proceed. HL already points to our number
  503. inc de
  504. ldi
  505. ld a, 2
  506. jr .end
  507. .withWord:
  508. inc hl ; HL now points to LSB
  509. ldi ; LSB written, we point to MSB now
  510. ldi ; MSB written
  511. ld a, 3
  512. jr .end
  513. .numberTruncated:
  514. ; problem: not zero, so value is truncated. error
  515. xor a
  516. .end:
  517. pop hl
  518. ret
  519. ; In instruction metadata below, argument types arge indicated with a single
  520. ; char mnemonic that is called "argspec". This is the table of correspondance.
  521. ; Single letters are represented by themselves, so we don't need as much
  522. ; metadata.
  523. ; Special meaning:
  524. ; 0 : no arg
  525. ; 1-10 : group id (see Groups section)
  526. ; 0xff: error
  527. ; Format: 1 byte argspec + 4 chars string
  528. argspecTbl:
  529. .db 'A', "A", 0, 0, 0
  530. .db 'B', "B", 0, 0, 0
  531. .db 'C', "C", 0, 0, 0
  532. .db 'D', "D", 0, 0, 0
  533. .db 'E', "E", 0, 0, 0
  534. .db 'H', "H", 0, 0, 0
  535. .db 'L', "L", 0, 0, 0
  536. .db 'h', "HL", 0, 0
  537. .db 'l', "(HL)"
  538. .db 'd', "DE", 0, 0
  539. .db 'e', "(DE)"
  540. .db 'b', "BC", 0, 0
  541. .db 'c', "(BC)"
  542. .db 'a', "AF", 0, 0
  543. .db 'f', "AF'", 0
  544. .db 'x', "(IX)"
  545. .db 'y', "(IY)"
  546. .db 's', "SP", 0, 0
  547. .db 'p', "(SP)"
  548. ; we also need argspecs for the condition flags
  549. .db 'Z', "Z", 0, 0, 0
  550. .db 'z', "NZ", 0, 0
  551. .db '^', "C", 0, 0, 0
  552. .db '=', "NC", 0, 0
  553. .db '+', "P", 0, 0, 0
  554. .db '-', "M", 0, 0, 0
  555. .db '1', "PO", 0, 0
  556. .db '2', "PE", 0, 0
  557. ; argspecs not in the list:
  558. ; N -> NN
  559. ; n -> (NN)
  560. ; Groups
  561. ; Groups are specified by strings of argspecs. To facilitate jumping to them,
  562. ; we have a fixed-sized table. Because most of them are 2 or 4 bytes long, we
  563. ; have a table that is 4 in size to minimize consumed space. We treat the two
  564. ; groups that take 8 bytes in a special way.
  565. ;
  566. ; The table below is in order, starting with group 0x01
  567. argGrpTbl:
  568. .db "bdha" ; 0x01
  569. .db "Zz^=" ; 0x02
  570. .db "bdhs" ; 0x03
  571. argGrpCC:
  572. .db "Zz^=+-12" ; 0xa
  573. argGrpABCDEHL:
  574. .db "BCDEHL_A" ; 0xb
  575. ; This is a list of primary instructions (single upcode) that lead to a
  576. ; constant (no group code to insert). Format:
  577. ;
  578. ; 4 bytes for the name (fill with zero)
  579. ; 1 byte for arg constant
  580. ; 1 byte for 2nd arg constant
  581. ; 1 byte displacement for group arguments
  582. ; 1 byte for upcode
  583. instrTBlPrimary:
  584. .db "ADD", 0, 'A', 'h', 0, 0x86 ; ADD A, HL
  585. .db "ADD", 0, 'A', 0xb, 0, 0b10000000 ; ADD A, r
  586. .db "ADC", 0, 'A', 'h', 0, 0x8e ; ADC A, HL
  587. .db "ADC", 0, 'A', 0xb, 0, 0b10001000 ; ADC A, r
  588. .db "AND", 0, 'l', 0, 0, 0xa6 ; AND (HL)
  589. .db "AND", 0, 0xa, 0, 0, 0b10100000 ; AND r
  590. .db "CCF", 0, 0, 0, 0, 0x3f ; CCF
  591. .db "CP",0,0, 'l', 0, 0, 0xbe ; CP (HL)
  592. .db "CP",0,0, 0xb, 0, 0, 0b10111000 ; CP r
  593. .db "CPL", 0, 0, 0, 0, 0x2f ; CPL
  594. .db "DAA", 0, 0, 0, 0, 0x27 ; DAA
  595. .db "DI",0,0, 0, 0, 0, 0xf3 ; DI
  596. .db "DEC", 0, 'l', 0, 0, 0x35 ; DEC (HL)
  597. .db "DEC", 0, 0xb, 0, 3, 0b00000101 ; DEC r
  598. .db "DEC", 0, 0x3, 0, 4, 0b00001011 ; DEC s
  599. .db "EI",0,0, 0, 0, 0, 0xfb ; EI
  600. .db "EX",0,0, 'p', 'h', 0, 0xe3 ; EX (SP), HL
  601. .db "EX",0,0, 'a', 'f', 0, 0x08 ; EX AF, AF'
  602. .db "EX",0,0, 'd', 'h', 0, 0xeb ; EX DE, HL
  603. .db "EXX", 0, 0, 0, 0, 0xd9 ; EXX
  604. .db "HALT", 0, 0, 0, 0x76 ; HALT
  605. .db "INC", 0, 'l', 0, 0, 0x34 ; INC (HL)
  606. .db "INC", 0, 0xb, 0, 3, 0b00000100 ; INC r
  607. .db "INC", 0, 0x3, 0, 4, 0b00000011 ; INC s
  608. .db "JP",0,0, 'l', 0, 0, 0xe9 ; JP (HL)
  609. .db "LD",0,0, 'c', 'A', 0, 0x02 ; LD (BC), A
  610. .db "LD",0,0, 'e', 'A', 0, 0x12 ; LD (DE), A
  611. .db "LD",0,0, 'A', 'c', 0, 0x0a ; LD A, (BC)
  612. .db "LD",0,0, 'A', 'e', 0, 0x0a ; LD A, (DE)
  613. .db "LD",0,0, 's', 'h', 0, 0x0a ; LD SP, HL
  614. .db "LD",0,0, 'l', 0xb, 0, 0b01110000 ; LD (HL), r
  615. .db "LD",0,0, 0xb, 'l', 3, 0b01000110 ; LD r, (HL)
  616. .db "LD",0,0, 'l', 'n', 0, 0x36 ; LD (HL), n
  617. .db "NOP", 0, 0, 0, 0, 0x00 ; NOP
  618. .db "OR",0,0, 'l', 0, 0, 0xb6 ; OR (HL)
  619. .db "OR",0,0, 0xb, 0, 0, 0b10110000 ; OR r
  620. .db "POP", 0, 0x1, 0, 4, 0b11000001 ; POP qq
  621. .db "PUSH", 0x1, 0, 4, 0b11000101 ; PUSH qq
  622. .db "RET", 0, 0xa, 0, 3, 0b11000000 ; RET cc
  623. .db "RET", 0, 0, 0, 0, 0xc9 ; RET
  624. .db "RLA", 0, 0, 0, 0, 0x17 ; RLA
  625. .db "RLCA", 0, 0, 0, 0x07 ; RLCA
  626. .db "RRA", 0, 0, 0, 0, 0x1f ; RRA
  627. .db "RRCA", 0, 0, 0, 0x0f ; RRCA
  628. .db "SBC", 0, 'A', 'h', 0, 0x9e ; SBC A, HL
  629. .db "SBC", 0, 'A', 0xb, 0, 0b10011000 ; SBC A, r
  630. .db "SCF", 0, 0, 0, 0, 0x37 ; SCF
  631. .db "SUB", 0, 'A', 'h', 0, 0x96 ; SUB A, HL
  632. .db "SUB", 0, 'A', 0xb, 0, 0b10010000 ; SUB A, r
  633. .db "XOR", 0, 'l', 0, 0, 0xae ; XOR (HL)
  634. .db "XOR", 0, 0xb, 0, 0, 0b10101000 ; XOR r
  635. ; *** Variables ***
  636. ; enough space for 4 chars and a null
  637. curWord:
  638. .db 0, 0, 0, 0, 0
  639. ; Args are 3 bytes: argspec, then values of numerical constants (when that's
  640. ; appropriate)
  641. curArg1:
  642. .db 0, 0, 0
  643. curArg2:
  644. .db 0, 0, 0
  645. ; space for tmp stuff
  646. tmpBuf:
  647. .fill 0x20