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.

562 lines
12KB

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