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.

461 lines
9.7KB

  1. ; shell
  2. ;
  3. ; Runs a shell over a block device interface.
  4. ; Status: incomplete. As it is now, it spits a welcome prompt, wait for input
  5. ; and compare the first 4 chars of the input with a command table and call the
  6. ; appropriate routine if it's found, an error if it's not.
  7. ;
  8. ; Commands, for now, are partially implemented.
  9. ;
  10. ; See constants below for error codes.
  11. ;
  12. ; All numerical values in the Collapse OS shell are represented and parsed in
  13. ; hexadecimal form, without prefix or suffix.
  14. ; *** DEFINES ***
  15. ; SHELL_GETC: Macro that calls a GetC routine
  16. ; SHELL_PUTC: Macro that calls a PutC routine
  17. ; SHELL_RAMSTART
  18. ; *** CONSTS ***
  19. ; number of entries in shellCmdTbl
  20. SHELL_CMD_COUNT .equ 4
  21. ; maximum number of bytes to receive as args in all commands. Determines the
  22. ; size of the args variable.
  23. SHELL_CMD_ARGS_MAXSIZE .equ 3
  24. ; The command that was type isn't known to the shell
  25. SHELL_ERR_UNKNOWN_CMD .equ 0x01
  26. ; Arguments for the command weren't properly formatted
  27. SHELL_ERR_BAD_ARGS .equ 0x02
  28. ; Size of the shell command buffer. If a typed command reaches this size, the
  29. ; command is flushed immediately (same as pressing return).
  30. SHELL_BUFSIZE .equ 0x20
  31. ; *** VARIABLES ***
  32. ; Memory address that the shell is currently "pointing at" for peek, load, call
  33. ; operations. Set with seek.
  34. SHELL_MEM_PTR .equ SHELL_RAMSTART
  35. ; Used to store formatted hex values just before printing it.
  36. SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
  37. ; Places where we store arguments specifiers and where resulting values are
  38. ; written to after parsing.
  39. SHELL_CMD_ARGS .equ SHELL_HEX_FMT+2
  40. ; Command buffer. We read types chars into this buffer until return is pressed
  41. ; This buffer is null-terminated and we don't keep an index around: we look
  42. ; for the null-termination every time we write to it. Simpler that way.
  43. SHELL_BUF .equ SHELL_CMD_ARGS+SHELL_CMD_ARGS_MAXSIZE
  44. SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
  45. ; *** CODE ***
  46. shellInit:
  47. xor a
  48. ld (SHELL_MEM_PTR), a
  49. ld (SHELL_BUF), a
  50. ; print welcome
  51. ld hl, .welcome
  52. call printstr
  53. ret
  54. .welcome:
  55. .db "Collapse OS", ASCII_CR, ASCII_LF, "> ", 0
  56. shellLoop:
  57. ; First, let's wait until something is typed.
  58. SHELL_GETC
  59. ; got it. Now, is it a CR or LF?
  60. cp ASCII_CR
  61. jr z, .do ; char is CR? do!
  62. cp ASCII_LF
  63. jr z, .do ; char is LF? do!
  64. ; Echo the received character right away so that we see what we type
  65. SHELL_PUTC
  66. ; Ok, gotta add it do the buffer
  67. ; save char for later
  68. ex af, af'
  69. ld hl, SHELL_BUF
  70. call findnull ; HL points to where we need to write
  71. ; A is the number of chars in the buf
  72. cp SHELL_BUFSIZE
  73. jr z, .do ; A == bufsize? then our buffer is full. do!
  74. ; bring the char back in A
  75. ex af, af'
  76. ; Buffer not full, not CR or LF. Let's put that char in our buffer and
  77. ; read again.
  78. ld (hl), a
  79. ; Now, write a zero to the next byte to properly terminate our string.
  80. inc hl
  81. xor a
  82. ld (hl), a
  83. jr shellLoop
  84. .do:
  85. call printcrlf
  86. ld hl, SHELL_BUF
  87. call shellParse
  88. ; empty our buffer by writing a zero to its first char
  89. xor a
  90. ld (hl), a
  91. ld hl, .prompt
  92. call printstr
  93. jr shellLoop
  94. .prompt:
  95. .db "> ", 0
  96. printcrlf:
  97. ld a, ASCII_CR
  98. SHELL_PUTC
  99. ld a, ASCII_LF
  100. SHELL_PUTC
  101. ret
  102. ; Parse command (null terminated) at HL and calls it
  103. shellParse:
  104. push af
  105. push bc
  106. push de
  107. push hl
  108. push ix
  109. ld de, shellCmdTbl
  110. ld a, SHELL_CMD_COUNT
  111. ld b, a
  112. .loop:
  113. push de ; we need to keep that table entry around...
  114. call intoDE ; Jump from the table entry to the cmd addr.
  115. ld a, 4 ; 4 chars to compare
  116. call strncmp
  117. pop de
  118. jr z, .found
  119. inc de
  120. djnz .loop
  121. ; exhausted loop? not found
  122. ld a, SHELL_ERR_UNKNOWN_CMD
  123. call shellPrintErr
  124. jr .end
  125. .found:
  126. ; we found our command. DE points to its table entry. Now, let's parse
  127. ; our args.
  128. call intoDE ; Jump from the table entry to the cmd addr.
  129. ; advance the HL pointer to the beginning of the args.
  130. ld a, 4
  131. call addHL
  132. ; Now, let's have DE point to the argspecs
  133. ld a, 4
  134. call addDE
  135. ; We're ready to parse args
  136. call shellParseArgs
  137. cp 0
  138. jr nz, .parseerror
  139. ld hl, SHELL_CMD_ARGS
  140. ; Args parsed, now we can load the routine address and call it.
  141. ; let's have DE point to the jump line
  142. ld a, SHELL_CMD_ARGS_MAXSIZE
  143. call addDE
  144. ld ixh, d
  145. ld ixl, e
  146. ; Ready to roll!
  147. call callIX
  148. jr .end
  149. .parseerror:
  150. ld a, SHELL_ERR_BAD_ARGS
  151. call shellPrintErr
  152. .end:
  153. pop ix
  154. pop hl
  155. pop de
  156. pop bc
  157. pop af
  158. ret
  159. ; Print the error code set in A (in hex)
  160. shellPrintErr:
  161. push af
  162. push hl
  163. ld hl, .str
  164. call printstr
  165. ld hl, SHELL_HEX_FMT
  166. call fmtHexPair
  167. ld a, 2
  168. call printnstr
  169. call printcrlf
  170. pop hl
  171. pop af
  172. ret
  173. .str:
  174. .db "ERR ", 0
  175. ; Parse arguments at (HL) with specifiers at (DE) into (SHELL_CMD_ARGS).
  176. ; (HL) should point to the character *just* after the name of the command
  177. ; because we verify, in the case that we have args, that we have a space there.
  178. ;
  179. ; Args specifiers are a series of flag for each arg:
  180. ; Bit 0 - arg present: if unset, we stop parsing there
  181. ; Bit 1 - is word: this arg is a word rather than a byte. Because our
  182. ; destination are bytes anyway, this doesn't change much except
  183. ; for whether we expect a space between the hex pairs. If set,
  184. ; you still need to have a specifier for the second part of
  185. ; the multibyte.
  186. ; Bit 2 - optional: If set and not present during parsing, we don't error out
  187. ; and write zero
  188. ;
  189. ; Sets A to nonzero if there was an error during parsing, zero otherwise.
  190. ; If there was an error during parsing, carry is set.
  191. shellParseArgs:
  192. push bc
  193. push de
  194. push hl
  195. push ix
  196. ld ix, SHELL_CMD_ARGS
  197. ld a, SHELL_CMD_ARGS_MAXSIZE
  198. ld b, a
  199. xor c
  200. .loop:
  201. ; init the arg value to a default 0
  202. xor a
  203. ld (ix), a
  204. ld a, (hl)
  205. ; is this the end of the line?
  206. cp 0
  207. jr z, .endofargs
  208. ; do we have a proper space char?
  209. cp ' '
  210. jr z, .hasspace ; We're fine
  211. ; is our previous arg a multibyte? (argspec still in C)
  212. bit 1, c
  213. jr z, .error ; bit not set? error
  214. dec hl ; offset the "inc hl" below
  215. .hasspace:
  216. ; Get the specs
  217. ld a, (de)
  218. bit 0, a ; do we have an arg?
  219. jr z, .error ; not set? then we have too many args
  220. ld c, a ; save the specs for the next loop
  221. inc hl ; (hl) points to a space, go next
  222. call parseHexPair
  223. jr c, .error
  224. ; we have a good arg and we need to write A in (IX).
  225. ld (ix), a
  226. ; Good! increase counters
  227. inc de
  228. inc ix
  229. inc hl ; get to following char (generally a space)
  230. djnz .loop
  231. ; If we get here, it means that our next char *has* to be a null char
  232. ld a, (hl)
  233. cp 0
  234. jr z, .success ; zero? great!
  235. jr .error
  236. .endofargs:
  237. ; We encountered our null char. Let's verify that we either have no
  238. ; more args or that they are optional
  239. ld a, (de)
  240. cp 0
  241. jr z, .success ; no arg? success
  242. bit 2, a
  243. jr nz, .success ; if set, arg is optional. success
  244. jr .error
  245. .success:
  246. xor a
  247. jr .end
  248. .error:
  249. inc a
  250. .end:
  251. pop ix
  252. pop hl
  253. pop de
  254. pop bc
  255. ret
  256. ; *** COMMANDS ***
  257. ; A command is a 4 char names, followed by a SHELL_CMD_ARGS_MAXSIZE bytes of
  258. ; argument specs, followed by the routine. Then, a simple table of addresses
  259. ; is compiled in a block and this is what is iterated upon when we want all
  260. ; available commands.
  261. ;
  262. ; Format: 4 bytes name followed by SHELL_CMD_ARGS_MAXSIZE bytes specifiers,
  263. ; followed by 3 bytes jump. fill names with zeroes
  264. ;
  265. ; When these commands are called, HL points to the first byte of the
  266. ; parsed command args.
  267. ; Set memory pointer to the specified address (word).
  268. ; Example: seek 01fe
  269. shellSeekCmd:
  270. .db "seek", 0b011, 0b001, 0
  271. shellSeek:
  272. push af
  273. push de
  274. push hl
  275. ; z80 is little endian. in a "ld hl, (nn)" op, L is loaded from the
  276. ; first byte, H is loaded from the second.
  277. ld a, (hl)
  278. ld (SHELL_MEM_PTR+1), a
  279. inc hl
  280. ld a, (hl)
  281. ld (SHELL_MEM_PTR), a
  282. ld de, (SHELL_MEM_PTR)
  283. ld hl, SHELL_HEX_FMT
  284. ld a, d
  285. call fmtHexPair
  286. ld a, 2
  287. call printnstr
  288. ld a, e
  289. call fmtHexPair
  290. ld a, 2
  291. call printnstr
  292. call printcrlf
  293. pop hl
  294. pop de
  295. pop af
  296. ret
  297. ; peek byte where memory pointer points to any display its value. If the
  298. ; optional numerical byte arg is supplied, this number of bytes will be printed
  299. ;
  300. ; Example: peek 2 (will print 2 bytes)
  301. shellPeekCmd:
  302. .db "peek", 0b101, 0, 0
  303. shellPeek:
  304. push af
  305. push bc
  306. push de
  307. push hl
  308. ld a, (hl)
  309. cp 0
  310. jr nz, .arg1isset ; if arg1 is set, no need for a default
  311. ld a, 1 ; default for arg1
  312. .arg1isset:
  313. ld b, a
  314. ld hl, (SHELL_MEM_PTR)
  315. .loop: ld a, (hl)
  316. ex hl, de
  317. ld hl, SHELL_HEX_FMT
  318. call fmtHexPair
  319. ld a, 2
  320. call printnstr
  321. ex hl, de
  322. inc hl
  323. djnz .loop
  324. call printcrlf
  325. .end:
  326. pop hl
  327. pop de
  328. pop bc
  329. pop af
  330. ret
  331. ; Load the specified number of bytes (max 0xff) from IO and write them in the
  332. ; current memory pointer (which doesn't change). For now, we can only load from
  333. ; SHELL_GETC, but a method of selecting IO sources is coming, making this
  334. ; command much more useful.
  335. ; Control is returned to the shell only after all bytes are read.
  336. ;
  337. ; Example: load 42
  338. shellLoadCmd:
  339. .db "load", 0b001, 0, 0
  340. shellLoad:
  341. push af
  342. push bc
  343. push hl
  344. ld a, (hl)
  345. ld b, a
  346. ld hl, (SHELL_MEM_PTR)
  347. .loop: SHELL_GETC
  348. ld (hl), a
  349. inc hl
  350. djnz .loop
  351. .end:
  352. pop hl
  353. pop bc
  354. pop af
  355. ret
  356. ; Calls the routine where the memory pointer currently points. This can take two
  357. ; parameters, A and HL. The first one is a byte, the second, a word. These are
  358. ; the values that A and HL are going to be set to just before calling.
  359. ; Example: run 42 cafe
  360. shellCallCmd:
  361. .db "call", 0b101, 0b111, 0b001
  362. shellCall:
  363. push af
  364. push hl
  365. push ix
  366. ; Let's recap here. At this point, we have:
  367. ; 1. The address we want to execute in (SHELL_MEM_PTR)
  368. ; 2. our A arg as the first byte of (HL)
  369. ; 2. our HL arg as (HL+1) and (HL+2)
  370. ; Ready, set, go!
  371. ld a, (SHELL_MEM_PTR)
  372. ld ixl, a
  373. ld a, (SHELL_MEM_PTR+1)
  374. ld ixh, a
  375. ld a, (hl)
  376. ex af, af'
  377. inc hl
  378. ld a, (hl)
  379. exx
  380. ld h, a
  381. exx
  382. inc hl
  383. ld a, (hl)
  384. exx
  385. ld l, a
  386. ex af, af'
  387. call callIX
  388. .end:
  389. pop ix
  390. pop hl
  391. pop af
  392. ret
  393. shellCmdTbl:
  394. .dw shellSeekCmd, shellPeekCmd, shellLoadCmd, shellCallCmd