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.

508 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. dec b
  28. ret c ; C flag means we went negative. nothing to do
  29. inc b
  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 (it's 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 de
  231. ld de, argGrpTbl
  232. ; group ids start at 1. decrease it, then multiply by two to have a
  233. ; proper offset in argGrpTbl
  234. dec h
  235. push af
  236. ld a, h
  237. add a, a
  238. call JUMP_ADDDE ; At this point, DE points to our group
  239. pop af
  240. ex hl, de
  241. pop de
  242. ld bc, 4
  243. .loop:
  244. cpi
  245. jr z, .found
  246. jp po, .notfound
  247. jr .loop
  248. .found:
  249. ; we found our result! Now, what we want to put in A is the index of
  250. ; the found argspec. We have this in C (4 - C + 1). The +1 is because
  251. ; cpi always decreases BC, whether we match or not.
  252. ld a, 3 ; 4 - 1
  253. sub c
  254. cp a ; ensure Z is set
  255. jr .end
  256. .notfound:
  257. call unsetZ
  258. .end:
  259. pop hl
  260. pop bc
  261. ret
  262. ; Compare argspec from instruction table in A with argument in (HL).
  263. ; For constant args, it's easy: if A == (HL), it's a success.
  264. ; If A is a group ID, we do something else: we check that (HL) exists in the
  265. ; groupspec (argGrpTbl)
  266. matchArg:
  267. cp a, (hl)
  268. ret z
  269. ; A bit of a delicate situation here: we want A to go in H but also
  270. ; (HL) to go in A. If not careful, we overwrite each other. EXX is
  271. ; necessary to avoid invoving other registers.
  272. push hl
  273. exx
  274. ld h, a
  275. push hl
  276. exx
  277. ld a, (hl)
  278. pop hl
  279. call findInGroup
  280. pop hl
  281. ret
  282. ; Compare primary row at (DE) with string at curWord. Sets Z flag if there's a
  283. ; match, reset if not.
  284. matchPrimaryRow:
  285. push hl
  286. push ix
  287. ld hl, curWord
  288. ld a, 4
  289. call JUMP_STRNCMP
  290. jr nz, .end
  291. ; name matches, let's see the rest
  292. ld ixh, d
  293. ld ixl, e
  294. ld hl, curArg1
  295. ld a, (ix+4)
  296. call matchArg
  297. jr nz, .end
  298. ld hl, curArg2
  299. ld a, (ix+5)
  300. call matchArg
  301. .end:
  302. pop ix
  303. pop hl
  304. ret
  305. ; Parse line at (HL) and write resulting opcode(s) in (DE). Returns the number
  306. ; of bytes written in A.
  307. parseLine:
  308. call readLine
  309. push de
  310. ld de, instrTBlPrimary
  311. ld b, INSTR_TBLP_CNT
  312. .loop:
  313. ld a, (de)
  314. call matchPrimaryRow
  315. jr z, .match
  316. ld a, INSTR_TBLP_ROWSIZE
  317. call JUMP_ADDDE
  318. djnz .loop
  319. ; no match
  320. xor a
  321. pop de
  322. ret
  323. .match:
  324. ; We have our matching instruction row. We're getting pretty near our
  325. ; goal here!
  326. ; First, let's go in IX mode. It's easier to deal with offsets here.
  327. push ix
  328. ld ixh, d
  329. ld ixl, e
  330. ; First, let's see if we're dealing with a group here
  331. ld a, (ix+4) ; first argspec
  332. call isGroupId
  333. jr nz, .notgroup
  334. ; A is a group, good, now let's get its value
  335. push hl
  336. ld h, a
  337. ld a, (curArg1)
  338. call findInGroup ; we don't check for match, it's supposed to
  339. ; always match. Something is very wrong if it
  340. ; doesn't
  341. ; Now, we have our arg "group value" in A. Were going to need to
  342. ; displace it left by the number of steps specified in the table.
  343. push bc
  344. push af
  345. ld a, (ix+6) ; displacement bit
  346. ld b, a
  347. pop af
  348. call rlaX
  349. pop bc
  350. ; At this point, we have a properly displaced value in A. We'll want
  351. ; to OR it with the opcode.
  352. or (ix+7) ; upcode
  353. pop hl
  354. ; Success!
  355. jr .end
  356. .notgroup:
  357. ; not a group? easy as pie: we return the opcode directly.
  358. ld a, (ix+7) ; upcode is on 8th byte
  359. .end:
  360. ; At the end, we have our final opcode in A!
  361. pop ix
  362. pop de
  363. ld (de), a
  364. ld a, 1
  365. ret
  366. ; In instruction metadata below, argument types arge indicated with a single
  367. ; char mnemonic that is called "argspec". This is the table of correspondance.
  368. ; Single letters are represented by themselves, so we don't need as much
  369. ; metadata.
  370. ; Special meaning:
  371. ; 0 : no arg
  372. ; 1-10 : group id (see Groups section)
  373. ; 0xff: error
  374. argspecsSingle:
  375. .db "ABCDEHL"
  376. ; Format: 1 byte argspec + 4 chars string
  377. argspecTbl:
  378. .db 'h', "HL", 0, 0
  379. .db 'l', "(HL)"
  380. .db 'd', "DE", 0, 0
  381. .db 'e', "(DE)"
  382. .db 'b', "BC", 0, 0
  383. .db 'c', "(BC)"
  384. .db 'a', "AF", 0, 0
  385. .db 'f', "AF'", 0
  386. .db 'x', "(IX)"
  387. .db 'y', "(IY)"
  388. .db 's', "SP", 0, 0
  389. .db 'p', "(SP)"
  390. ; we also need argspecs for the condition flags
  391. .db 'Z', "Z", 0, 0, 0
  392. .db 'z', "NZ", 0, 0
  393. .db '^', "C", 0, 0, 0
  394. .db '=', "NC", 0, 0
  395. .db '+', "P", 0, 0, 0
  396. .db '-', "M", 0, 0, 0
  397. .db '1', "PO", 0, 0
  398. .db '2', "PE", 0, 0
  399. ; argspecs not in the list:
  400. ; N -> NN
  401. ; n -> (NN)
  402. ; Groups
  403. ; Groups are specified by strings of argspecs. To facilitate jumping to them,
  404. ; we have a fixed-sized table. Because most of them are 2 or 4 bytes long, we
  405. ; have a table that is 4 in size to minimize consumed space. We treat the two
  406. ; groups that take 8 bytes in a special way.
  407. ;
  408. ; The table below is in order, starting with group 0x01
  409. argGrpTbl:
  410. .db "bdha" ; 0x01
  411. .db "Zz^=" ; 0x02
  412. argGrpCC:
  413. .db "Zz^=+-12" ; 0xa
  414. argGrpABCDEHL:
  415. .db "BCDEHL_A" ; 0xb
  416. ; This is a list of primary instructions (single upcode) that lead to a
  417. ; constant (no group code to insert). Format:
  418. ;
  419. ; 4 bytes for the name (fill with zero)
  420. ; 1 byte for arg constant
  421. ; 1 byte for 2nd arg constant
  422. ; 1 byte displacement for group arguments
  423. ; 1 byte for upcode
  424. instrTBlPrimary:
  425. .db "ADD", 0, 'A', 'h', 0, 0x86 ; ADD A, HL
  426. .db "AND", 0, 'l', 0, 0, 0xa6 ; AND (HL)
  427. .db "AND", 0, 0xa, 0, 0, 0b10100000 ; AND r
  428. .db "CCF", 0, 0, 0, 0, 0x3f ; CCF
  429. .db "CPL", 0, 0, 0, 0, 0x2f ; CPL
  430. .db "DAA", 0, 0, 0, 0, 0x27 ; DAA
  431. .db "DI",0,0, 0, 0, 0, 0xf3 ; DI
  432. .db "EI",0,0, 0, 0, 0, 0xfb ; EI
  433. .db "EX",0,0, 'p', 'h', 0, 0xe3 ; EX (SP), HL
  434. .db "EX",0,0, 'a', 'f', 0, 0x08 ; EX AF, AF'
  435. .db "EX",0,0, 'd', 'h', 0, 0xeb ; EX DE, HL
  436. .db "EXX", 0, 0, 0, 0, 0xd9 ; EXX
  437. .db "HALT", 0, 0, 0, 0x76 ; HALT
  438. .db "INC", 0, 'l', 0, 0, 0x34 ; INC (HL)
  439. .db "JP",0,0, 'l', 0, 0, 0xe9 ; JP (HL)
  440. .db "LD",0,0, 'c', 'A', 0, 0x02 ; LD (BC), A
  441. .db "LD",0,0, 'e', 'A', 0, 0x12 ; LD (DE), A
  442. .db "LD",0,0, 'A', 'c', 0, 0x0a ; LD A, (BC)
  443. .db "LD",0,0, 'A', 'e', 0, 0x0a ; LD A, (DE)
  444. .db "LD",0,0, 's', 'h', 0, 0x0a ; LD SP, HL
  445. .db "NOP", 0, 0, 0, 0, 0x00 ; NOP
  446. .db "OR",0,0, 'l', 0, 0, 0xb6 ; OR (HL)
  447. .db "POP", 0, 0x1, 0, 4, 0b11000001 ; POP qq
  448. .db "RET", 0, 0, 0, 0, 0xc9 ; RET
  449. .db "RET", 0, 0xb, 0, 3, 0b11000000 ; RET cc
  450. .db "RLA", 0, 0, 0, 0, 0x17 ; RLA
  451. .db "RLCA", 0, 0, 0, 0x07 ; RLCA
  452. .db "RRA", 0, 0, 0, 0, 0x1f ; RRA
  453. .db "RRCA", 0, 0, 0, 0x0f ; RRCA
  454. .db "SCF", 0, 0, 0, 0, 0x37 ; SCF
  455. ; *** Variables ***
  456. ; enough space for 4 chars and a null
  457. curWord:
  458. .db 0, 0, 0, 0, 0
  459. ; Args are 3 bytes: argspec, then values of numerical constants (when that's
  460. ; appropriate)
  461. curArg1:
  462. .db 0, 0, 0
  463. curArg2:
  464. .db 0, 0, 0
  465. ; space for tmp stuff
  466. tmpVal:
  467. .db 0, 0, 0, 0, 0