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.

548 lines
11KB

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