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.

398 lines
8.7KB

  1. ; shell
  2. ;
  3. ; Runs a shell over a block device interface.
  4. ; The shell spits a welcome prompt, wait for input and compare the first 4 chars
  5. ; of the input with a command table and call the appropriate routine if it's
  6. ; found, an error if it's not.
  7. ;
  8. ; To determine the correct routine to call we first go through cmds in
  9. ; shellCmdTbl. This means that we first go through internal cmds, then cmds
  10. ; "grafted" by glue code.
  11. ;
  12. ; If the command isn't found, SHELL_CMDHOOK is called, which should set A to
  13. ; zero if it executes something. Otherwise, SHELL_ERR_UNKNOWN_CMD will be
  14. ; returned.
  15. ;
  16. ; See constants below for error codes.
  17. ;
  18. ; All numerical values in the Collapse OS shell are represented and parsed in
  19. ; hexadecimal form, without prefix or suffix.
  20. ; *** REQUIREMENTS ***
  21. ; err
  22. ; core
  23. ; parse
  24. ; stdio
  25. ; *** DEFINES ***
  26. ; SHELL_EXTRA_CMD_COUNT: Number of extra cmds to be expected after the regular
  27. ; ones. See comment in COMMANDS section for details.
  28. ; SHELL_RAMSTART
  29. ; *** CONSTS ***
  30. ; number of entries in shellCmdTbl
  31. .equ SHELL_CMD_COUNT 6+SHELL_EXTRA_CMD_COUNT
  32. ; Size of the shell command buffer. If a typed command reaches this size, the
  33. ; command is flushed immediately (same as pressing return).
  34. .equ SHELL_BUFSIZE 0x20
  35. ; *** VARIABLES ***
  36. ; Memory address that the shell is currently "pointing at" for peek, load, call
  37. ; operations. Set with mptr.
  38. .equ SHELL_MEM_PTR SHELL_RAMSTART
  39. ; Places where we store arguments specifiers and where resulting values are
  40. ; written to after parsing.
  41. .equ SHELL_CMD_ARGS SHELL_MEM_PTR+2
  42. ; Command buffer. We read types chars into this buffer until return is pressed
  43. ; This buffer is null-terminated and we don't keep an index around: we look
  44. ; for the null-termination every time we write to it. Simpler that way.
  45. .equ SHELL_BUF SHELL_CMD_ARGS+PARSE_ARG_MAXCOUNT
  46. ; Pointer to a hook to call when a cmd name isn't found
  47. .equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE
  48. .equ SHELL_RAMEND SHELL_CMDHOOK+2
  49. ; *** CODE ***
  50. shellInit:
  51. xor a
  52. ld (SHELL_MEM_PTR), a
  53. ld (SHELL_MEM_PTR+1), a
  54. ld (SHELL_BUF), a
  55. ld hl, noop
  56. ld (SHELL_CMDHOOK), hl
  57. ; print welcome
  58. ld hl, .welcome
  59. jp printstr ; returns
  60. .welcome:
  61. .db "Collapse OS", ASCII_CR, ASCII_LF, "> ", 0
  62. ; Inifite loop that processes input. Because it's infinite, you should jump
  63. ; to it rather than call it. Saves two precious bytes in the stack.
  64. shellLoop:
  65. ; First, let's wait until something is typed.
  66. call stdioGetC
  67. jr nz, shellLoop ; nothing typed? loop
  68. ; got it. Now, is it a CR or LF?
  69. cp ASCII_CR
  70. jr z, .do ; char is CR? do!
  71. cp ASCII_LF
  72. jr z, .do ; char is LF? do!
  73. ; Echo the received character right away so that we see what we type
  74. call stdioPutC
  75. ; Ok, gotta add it do the buffer
  76. ; save char for later
  77. ex af, af'
  78. ld hl, SHELL_BUF
  79. xor a ; look for null
  80. call findchar ; HL points to where we need to write
  81. ; A is the number of chars in the buf
  82. cp SHELL_BUFSIZE
  83. jr z, .do ; A == bufsize? then our buffer is full. do!
  84. ; bring the char back in A
  85. ex af, af'
  86. ; Buffer not full, not CR or LF. Let's put that char in our buffer and
  87. ; read again.
  88. ld (hl), a
  89. ; Now, write a zero to the next byte to properly terminate our string.
  90. inc hl
  91. xor a
  92. ld (hl), a
  93. jr shellLoop
  94. .do:
  95. call printcrlf
  96. ld hl, SHELL_BUF
  97. call shellParse
  98. ; empty our buffer by writing a zero to its first char
  99. xor a
  100. ld (hl), a
  101. ld hl, .prompt
  102. call printstr
  103. jr shellLoop
  104. ; no ret because we never return
  105. .prompt:
  106. .db "> ", 0
  107. ; Parse command (null terminated) at HL and calls it
  108. shellParse:
  109. push af
  110. push bc
  111. push de
  112. push hl
  113. push ix
  114. ld de, shellCmdTbl
  115. ld a, SHELL_CMD_COUNT
  116. ld b, a
  117. .loop:
  118. push de ; we need to keep that table entry around...
  119. call intoDE ; Jump from the table entry to the cmd addr.
  120. ld a, 4 ; 4 chars to compare
  121. call strncmp
  122. pop de
  123. jr z, .found
  124. inc de
  125. inc de
  126. djnz .loop
  127. ; exhausted loop? not found
  128. ld a, SHELL_ERR_UNKNOWN_CMD
  129. ; Before erroring out, let's try SHELL_HOOK.
  130. ld ix, (SHELL_CMDHOOK)
  131. call callIX
  132. jr z, .end ; oh, not an error!
  133. ; still an error. Might be different than SHELL_ERR_UNKNOWN_CMD though.
  134. ; maybe a routine was called, but errored out.
  135. jr .error
  136. .found:
  137. ; we found our command. DE points to its table entry. Now, let's parse
  138. ; our args.
  139. call intoDE ; Jump from the table entry to the cmd addr.
  140. ; advance the HL pointer to the beginning of the args.
  141. ld a, ' '
  142. call findchar
  143. or a ; end of string? don't increase HL
  144. jr z, .noargs
  145. inc hl ; char after space
  146. .noargs:
  147. ; Now, let's have DE point to the argspecs
  148. ld a, 4
  149. call addDE
  150. ; We're ready to parse args
  151. ld ix, SHELL_CMD_ARGS
  152. call parseArgs
  153. or a ; cp 0
  154. jr nz, .parseerror
  155. ; Args parsed, now we can load the routine address and call it.
  156. ; let's have DE point to the jump line
  157. ld hl, SHELL_CMD_ARGS
  158. ld a, PARSE_ARG_MAXCOUNT
  159. call addDE
  160. push de \ pop ix
  161. ; Ready to roll!
  162. call callIX
  163. or a ; cp 0
  164. jr nz, .error ; if A is non-zero, we have an error
  165. jr .end
  166. .parseerror:
  167. ld a, SHELL_ERR_BAD_ARGS
  168. .error:
  169. call shellPrintErr
  170. .end:
  171. pop ix
  172. pop hl
  173. pop de
  174. pop bc
  175. pop af
  176. ret
  177. ; Print the error code set in A (in hex)
  178. shellPrintErr:
  179. push af
  180. push hl
  181. ld hl, .str
  182. call printstr
  183. call printHex
  184. call printcrlf
  185. pop hl
  186. pop af
  187. ret
  188. .str:
  189. .db "ERR ", 0
  190. ; *** COMMANDS ***
  191. ; A command is a 4 char names, followed by a PARSE_ARG_MAXCOUNT bytes of
  192. ; argument specs, followed by the routine. Then, a simple table of addresses
  193. ; is compiled in a block and this is what is iterated upon when we want all
  194. ; available commands.
  195. ;
  196. ; Format: 4 bytes name followed by PARSE_ARG_MAXCOUNT bytes specifiers,
  197. ; followed by 3 bytes jump. fill names with zeroes
  198. ;
  199. ; When these commands are called, HL points to the first byte of the
  200. ; parsed command args.
  201. ;
  202. ; If the command is a success, it should set A to zero. If the command results
  203. ; in an error, it should set an error code in A.
  204. ;
  205. ; Extra commands: Other parts might define new commands. You can add these
  206. ; commands to your shell. First, set SHELL_EXTRA_CMD_COUNT to
  207. ; the number of extra commands to add, then add a ".dw"
  208. ; directive *just* after your '#include "shell.asm"'. Voila!
  209. ;
  210. ; Set memory pointer to the specified address (word).
  211. ; Example: mptr 01fe
  212. shellMptrCmd:
  213. .db "mptr", 0b011, 0b001, 0
  214. shellMptr:
  215. push hl
  216. ; reminder: z80 is little-endian
  217. ld a, (hl)
  218. ld (SHELL_MEM_PTR+1), a
  219. inc hl
  220. ld a, (hl)
  221. ld (SHELL_MEM_PTR), a
  222. ld hl, (SHELL_MEM_PTR)
  223. ld a, h
  224. call printHex
  225. ld a, l
  226. call printHex
  227. call printcrlf
  228. pop hl
  229. xor a
  230. ret
  231. ; peek byte where memory pointer points to any display its value. If the
  232. ; optional numerical byte arg is supplied, this number of bytes will be printed
  233. ;
  234. ; Example: peek 2 (will print 2 bytes)
  235. shellPeekCmd:
  236. .db "peek", 0b101, 0, 0
  237. shellPeek:
  238. push bc
  239. push de
  240. push hl
  241. ld a, (hl)
  242. cp 0
  243. jr nz, .arg1isset ; if arg1 is set, no need for a default
  244. ld a, 1 ; default for arg1
  245. .arg1isset:
  246. ld b, a
  247. ld hl, (SHELL_MEM_PTR)
  248. .loop: ld a, (hl)
  249. call printHex
  250. inc hl
  251. djnz .loop
  252. call printcrlf
  253. .end:
  254. pop hl
  255. pop de
  256. pop bc
  257. xor a
  258. ret
  259. ; poke byte where memory pointer points and set them to bytes types through
  260. ; stdioGetC. If the optional numerical byte arg is supplied, this number of
  261. ; bytes will be expected from stdioGetC. Blocks until all bytes have been
  262. ; fetched.
  263. shellPokeCmd:
  264. .db "poke", 0b101, 0, 0
  265. shellPoke:
  266. push bc
  267. push hl
  268. ld a, (hl)
  269. or a ; cp 0
  270. jr nz, .arg1isset ; if arg1 is set, no need for a default
  271. ld a, 1 ; default for arg1
  272. .arg1isset:
  273. ld b, a
  274. ld hl, (SHELL_MEM_PTR)
  275. .loop: call stdioGetC
  276. jr nz, .loop ; nothing typed? loop
  277. ld (hl), a
  278. inc hl
  279. djnz .loop
  280. pop hl
  281. pop bc
  282. xor a
  283. ret
  284. ; Calls the routine where the memory pointer currently points. This can take two
  285. ; parameters, A and HL. The first one is a byte, the second, a word. These are
  286. ; the values that A and HL are going to be set to just before calling.
  287. ; Example: run 42 cafe
  288. shellCallCmd:
  289. .db "call", 0b101, 0b111, 0b001
  290. shellCall:
  291. push hl
  292. push ix
  293. ; Let's recap here. At this point, we have:
  294. ; 1. The address we want to execute in (SHELL_MEM_PTR)
  295. ; 2. our A arg as the first byte of (HL)
  296. ; 2. our HL arg as (HL+1) and (HL+2)
  297. ; Ready, set, go!
  298. ld ix, (SHELL_MEM_PTR)
  299. ld a, (hl)
  300. ex af, af'
  301. inc hl
  302. ld a, (hl)
  303. exx
  304. ld h, a
  305. exx
  306. inc hl
  307. ld a, (hl)
  308. exx
  309. ld l, a
  310. ex af, af'
  311. call callIX
  312. .end:
  313. pop ix
  314. pop hl
  315. xor a
  316. ret
  317. shellIORDCmd:
  318. .db "iord", 0b001, 0, 0
  319. push bc
  320. ld a, (hl)
  321. ld c, a
  322. in a, (c)
  323. call printHex
  324. xor a
  325. pop bc
  326. ret
  327. shellIOWRCmd:
  328. .db "iowr", 0b001, 0b001, 0
  329. push bc
  330. ld a, (hl)
  331. ld c, a
  332. inc hl
  333. ld a, (hl)
  334. out (c), a
  335. xor a
  336. pop bc
  337. ret
  338. ; This table is at the very end of the file on purpose. The idea is to be able
  339. ; to graft extra commands easily after an include in the glue file.
  340. shellCmdTbl:
  341. .dw shellMptrCmd, shellPeekCmd, shellPokeCmd, shellCallCmd
  342. .dw shellIORDCmd, shellIOWRCmd