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.

536 lines
9.5KB

  1. ; *** Variables ***
  2. ; Value of `SP` when basic was first invoked. This is where SP is going back to
  3. ; on restarts.
  4. .equ BAS_INITSP BAS_RAMSTART
  5. ; Pointer to next line to run. If nonzero, it means that the next line is
  6. ; the first of the list. This is used by GOTO to indicate where to jump next.
  7. ; Important note: this is **not** a line number, it's a pointer to a line index
  8. ; in buffer. If it's not zero, its a valid pointer.
  9. .equ BAS_PNEXTLN @+2
  10. ; Points to a routine to call when a command isn't found in the "core" cmd
  11. ; table. This gives the opportunity to glue code to configure extra commands.
  12. .equ BAS_FINDHOOK @+2
  13. .equ BAS_RAMEND @+2
  14. ; *** Code ***
  15. basInit:
  16. ld (BAS_INITSP), sp
  17. call varInit
  18. call bufInit
  19. xor a
  20. ld (BAS_PNEXTLN), a
  21. ld (BAS_PNEXTLN+1), a
  22. ld hl, unsetZ
  23. ld (BAS_FINDHOOK), hl
  24. ret
  25. basStart:
  26. ld hl, .welcome
  27. call printstr
  28. call printcrlf
  29. jr basLoop
  30. .welcome:
  31. .db "Collapse OS", 0
  32. basLoop:
  33. ld hl, .sPrompt
  34. call printstr
  35. call stdioReadLine
  36. call printcrlf
  37. call parseDecimal
  38. jr z, .number
  39. ld de, basCmds1
  40. call basCallCmds
  41. jr z, basLoop
  42. ; Error
  43. call basERR
  44. jr basLoop
  45. .number:
  46. push ix \ pop de
  47. call toSepOrEnd
  48. call rdSep
  49. call bufAdd
  50. jp nz, basERR
  51. jr basLoop
  52. .sPrompt:
  53. .db "> ", 0
  54. ; Tries to find command specified in (DE) (must be null-terminated) in cmd
  55. ; table in (HL). If found, sets IX to point to the associated routine. If
  56. ; not found, calls BAS_FINDHOOK so that we look through extra commands
  57. ; configured by glue code.
  58. ; Destroys HL.
  59. ; Z is set if found, unset otherwise.
  60. basFindCmd:
  61. .loop:
  62. call strcmp
  63. call strskip
  64. inc hl ; point to routine
  65. jr z, .found ; Z from strcmp
  66. inc hl \ inc hl ; skip routine
  67. ld a, (hl)
  68. inc a ; was it 0xff?
  69. jr nz, .loop ; no
  70. dec a ; unset Z
  71. ret
  72. .found:
  73. call intoHL
  74. push hl \ pop ix
  75. ret
  76. ; Call command in (HL) after having looked for it in cmd table in (DE).
  77. ; If found, jump to it. If not found, try (BAS_FINDHOOK). If still not found,
  78. ; unset Z. We expect commands to set Z on success. Therefore, when calling
  79. ; basCallCmd results in NZ, we're not sure where the error come from, but
  80. ; well...
  81. basCallCmd:
  82. ; let's see if it's a variable assignment.
  83. call varTryAssign
  84. ret z ; Done!
  85. push de ; --> lvl 1.
  86. ld de, SCRATCHPAD
  87. call rdWord
  88. ; cmdname to find in (DE)
  89. ; How lucky, we have a legitimate use of "ex (sp), hl"! We have the
  90. ; cmd table in the stack, which we want in HL and we have the rest of
  91. ; the cmdline in (HL), which we want in the stack!
  92. ex (sp), hl
  93. call basFindCmd
  94. jr z, .skip
  95. ; not found, try BAS_FINDHOOK
  96. ld ix, (BAS_FINDHOOK)
  97. call callIX
  98. .skip:
  99. ; regardless of the result, we need to balance the stack.
  100. ; Bring back rest of the command string from the stack
  101. pop hl ; <-- lvl 1
  102. ret nz
  103. ; cmd found, skip whitespace and then jump!
  104. call rdSep
  105. jp (ix)
  106. ; Call a series of ':'-separated commands in (HL) using cmd table in (DE).
  107. ; Stop processing as soon as one command unsets Z.
  108. basCallCmds:
  109. ; Commands are not guaranteed at all to preserve HL and DE, so we
  110. ; preserve them ourselves here.
  111. push hl ; --> lvl 1
  112. push de ; --> lvl 2
  113. call basCallCmd
  114. pop de ; <-- lvl 2
  115. pop hl ; <-- lvl 1
  116. ret nz
  117. call toEnd
  118. ret z ; no more cmds
  119. ; we met a ':', we have more cmds
  120. inc hl
  121. call basCallCmds
  122. ; move the the end of the string so that we don't run cmds following a
  123. ; ':' twice.
  124. call strskip
  125. ret
  126. basERR:
  127. ld hl, .sErr
  128. call printstr
  129. jp printcrlf
  130. .sErr:
  131. .db "ERR", 0
  132. ; *** Commands ***
  133. ; A command receives its argument through (HL), which is already placed to
  134. ; either:
  135. ; 1 - the end of the string if the command has no arg.
  136. ; 2 - the beginning of the arg, with whitespace properly skipped.
  137. ;
  138. ; Commands are expected to set Z on success.
  139. basLIST:
  140. call bufFirst
  141. jr nz, .end
  142. .loop:
  143. ld e, (ix)
  144. ld d, (ix+1)
  145. ld hl, SCRATCHPAD
  146. call fmtDecimal
  147. call printstr
  148. ld a, ' '
  149. call stdioPutC
  150. call bufStr
  151. call printstr
  152. call printcrlf
  153. call bufNext
  154. jr z, .loop
  155. .end:
  156. cp a ; ensure Z
  157. ret
  158. basRUN:
  159. call .maybeGOTO
  160. jr nz, .loop ; IX already set
  161. call bufFirst
  162. ret nz
  163. .loop:
  164. call bufStr
  165. ld de, basCmds2
  166. push ix ; --> lvl 1
  167. call basCallCmds
  168. pop ix ; <-- lvl 1
  169. jp nz, .err
  170. call .maybeGOTO
  171. jr nz, .loop ; IX already set
  172. call bufNext
  173. jr z, .loop
  174. cp a ; ensure Z
  175. ret
  176. .err:
  177. ; Print line number, then return NZ (which will print ERR)
  178. ld e, (ix)
  179. ld d, (ix+1)
  180. ld hl, SCRATCHPAD
  181. call fmtDecimal
  182. call printstr
  183. ld a, ' '
  184. call stdioPutC
  185. jp unsetZ
  186. ; This returns the opposite Z result as the one we usually see: Z is set if
  187. ; we **don't** goto, unset if we do. If we do, IX is properly set.
  188. .maybeGOTO:
  189. ld de, (BAS_PNEXTLN)
  190. ld a, d
  191. or e
  192. ret z
  193. ; we goto
  194. push de \ pop ix
  195. ; we need to reset our goto marker
  196. ld de, 0
  197. ld (BAS_PNEXTLN), de
  198. ret
  199. basPRINT:
  200. ; Do we have arguments at all? if not, it's not an error, just print
  201. ; crlf
  202. ld a, (hl)
  203. or a
  204. jr z, .end
  205. ; Is our arg a string literal?
  206. call spitQuoted
  207. jr z, .chkAnother ; string printed, skip to chkAnother
  208. ld de, SCRATCHPAD
  209. call rdWord
  210. push hl ; --> lvl 1
  211. ex de, hl
  212. call parseExpr
  213. jr nz, .parseError
  214. push ix \ pop de
  215. ld hl, SCRATCHPAD
  216. call fmtDecimalS
  217. call printstr
  218. pop hl ; <-- lvl 1
  219. .chkAnother:
  220. ; Do we have another arg?
  221. call rdSep
  222. jr z, .another
  223. ; no, we can stop here
  224. .end:
  225. cp a ; ensure Z
  226. jp printcrlf
  227. .another:
  228. ; Before we jump to basPRINT, let's print a space
  229. ld a, ' '
  230. call stdioPutC
  231. jr basPRINT
  232. .parseError:
  233. ; unwind the stack before returning
  234. pop hl ; <-- lvl 1
  235. ret
  236. basGOTO:
  237. ld de, SCRATCHPAD
  238. call rdWord
  239. ex de, hl
  240. call parseExpr
  241. ret nz
  242. push ix \ pop de
  243. call bufFind
  244. jr nz, .notFound
  245. push ix \ pop de
  246. ; Z already set
  247. jr .end
  248. .notFound:
  249. ld de, 0
  250. ; Z already unset
  251. .end:
  252. ld (BAS_PNEXTLN), de
  253. ret
  254. ; evaluate truth condition at (HL) and set A to its value
  255. ; Z for success (but not truth!)
  256. _basEvalCond:
  257. push hl ; --> lvl 1. original arg
  258. ld de, SCRATCHPAD
  259. call rdWord
  260. ex de, hl
  261. call parseTruth
  262. pop hl ; <-- lvl 1. restore
  263. ret
  264. basIF:
  265. call _basEvalCond
  266. ret nz ; error
  267. or a
  268. ret z
  269. ; expr is true, execute next
  270. ; (HL) back to beginning of args, skip to next arg
  271. call toSepOrEnd
  272. call rdSep
  273. ret nz
  274. ld de, basCmds2
  275. jp basCallCmds
  276. basWHILE:
  277. push hl ; --> lvl 1
  278. call _basEvalCond
  279. jr nz, .stop ; error
  280. or a
  281. jr z, .stop
  282. ret z
  283. ; expr is true, execute next
  284. ; (HL) back to beginning of args, skip to next arg
  285. call toSepOrEnd
  286. call rdSep
  287. ret nz
  288. ld de, basCmds2
  289. call basCallCmds
  290. pop hl ; <-- lvl 1
  291. jr basWHILE
  292. .stop:
  293. pop hl ; <-- lvl 1
  294. ret
  295. basINPUT:
  296. ; If our first arg is a string literal, spit it
  297. call spitQuoted
  298. call rdSep
  299. call stdioReadLine
  300. call parseExpr
  301. ld (VAR_TBL), ix
  302. call printcrlf
  303. cp a ; ensure Z
  304. ret
  305. basPEEK:
  306. call basDEEK
  307. ret nz
  308. ; set MSB to 0
  309. xor a ; sets Z
  310. ld (VAR_TBL+1), a
  311. ret
  312. basPOKE:
  313. call rdExpr
  314. ret nz
  315. ; peek address in IX. Save it for later
  316. push ix ; --> lvl 1
  317. call rdSep
  318. call rdExpr
  319. push ix \ pop hl
  320. pop ix ; <-- lvl 1
  321. ret nz
  322. ; Poke!
  323. ld (ix), l
  324. ret
  325. basDEEK:
  326. call rdExpr
  327. ret nz
  328. ; peek address in IX. Let's peek and put result in DE
  329. ld e, (ix)
  330. ld d, (ix+1)
  331. ld (VAR_TBL), de
  332. cp a ; ensure Z
  333. ret
  334. basDOKE:
  335. call basPOKE
  336. ld (ix+1), h
  337. ret
  338. basOUT:
  339. call rdExpr
  340. ret nz
  341. ; out address in IX. Save it for later
  342. push ix ; --> lvl 1
  343. call rdSep
  344. call rdExpr
  345. push ix \ pop hl
  346. pop bc ; <-- lvl 1
  347. ret nz
  348. ; Out!
  349. out (c), l
  350. cp a ; ensure Z
  351. ret
  352. basIN:
  353. call rdExpr
  354. ret nz
  355. push ix \ pop bc
  356. ld d, 0
  357. in e, (c)
  358. ld (VAR_TBL), de
  359. ; Z set from rdExpr
  360. ret
  361. basGETC:
  362. call stdioGetC
  363. ld (VAR_TBL), a
  364. xor a
  365. ld (VAR_TBL+1), a
  366. ret
  367. basPUTC:
  368. call rdExpr
  369. ret nz
  370. push ix \ pop hl
  371. ld a, l
  372. call stdioPutC
  373. xor a ; set Z
  374. ret
  375. basPUTH:
  376. call rdExpr
  377. ret nz
  378. push ix \ pop hl
  379. ld a, l
  380. call printHex
  381. xor a ; set Z
  382. ret
  383. basSLEEP:
  384. call rdExpr
  385. ret nz
  386. push ix \ pop hl
  387. .loop:
  388. ld a, h ; 4T
  389. or l ; 4T
  390. ret z ; 5T
  391. dec hl ; 6T
  392. jr .loop ; 12T
  393. basADDR:
  394. call rdWord
  395. ex de, hl
  396. ld de, .specialTbl
  397. .loop:
  398. ld a, (de)
  399. or a
  400. jr z, .notSpecial
  401. cp (hl)
  402. jr z, .found
  403. inc de \ inc de \ inc de
  404. jr .loop
  405. .notSpecial:
  406. ; not found, find cmd. needle in (HL)
  407. ex de, hl ; now in (DE)
  408. ld hl, basCmds1
  409. call basFindCmd
  410. jr z, .foundCmd
  411. ; no core command? let's try the find hook.
  412. ld ix, (BAS_FINDHOOK)
  413. call callIX
  414. ret nz
  415. .foundCmd:
  416. ; We have routine addr in IX
  417. ld (VAR_TBL), ix
  418. cp a ; ensure Z
  419. ret
  420. .found:
  421. ; found special thing. Put in "A".
  422. inc de
  423. call intoDE
  424. ld (VAR_TBL), de
  425. ret ; Z set from .found jump.
  426. .specialTbl:
  427. .db '$'
  428. .dw SCRATCHPAD
  429. .db 0
  430. basUSR:
  431. call rdExpr
  432. ret nz
  433. push ix \ pop iy
  434. ; We have our address to call. Now, let's set up our registers.
  435. ; HL comes from variable H. H's index is 7*2.
  436. ld hl, (VAR_TBL+14)
  437. ; DE comes from variable D. D's index is 3*2
  438. ld de, (VAR_TBL+6)
  439. ; BC comes from variable B. B's index is 1*2
  440. ld bc, (VAR_TBL+2)
  441. ; IX comes from variable X. X's index is 23*2
  442. ld ix, (VAR_TBL+46)
  443. ; and finally, A
  444. ld a, (VAR_TBL)
  445. call callIY
  446. basR2Var: ; Just send reg to vars. Used in basPgmHook
  447. ; Same dance, opposite way
  448. ld (VAR_TBL), a
  449. ld (VAR_TBL+46), ix
  450. ld (VAR_TBL+2), bc
  451. ld (VAR_TBL+6), de
  452. ld (VAR_TBL+14), hl
  453. cp a ; USR never errors out
  454. ret
  455. ; Command table format: Null-terminated string followed by a 2-byte routine
  456. ; pointer.
  457. ; direct only
  458. basCmds1:
  459. .db "list", 0
  460. .dw basLIST
  461. .db "run", 0
  462. .dw basRUN
  463. .db "clear", 0
  464. .dw bufInit
  465. ; statements
  466. basCmds2:
  467. .db "print", 0
  468. .dw basPRINT
  469. .db "goto", 0
  470. .dw basGOTO
  471. .db "if", 0
  472. .dw basIF
  473. .db "while", 0
  474. .dw basWHILE
  475. .db "input", 0
  476. .dw basINPUT
  477. .db "peek", 0
  478. .dw basPEEK
  479. .db "poke", 0
  480. .dw basPOKE
  481. .db "deek", 0
  482. .dw basDEEK
  483. .db "doke", 0
  484. .dw basDOKE
  485. .db "out", 0
  486. .dw basOUT
  487. .db "in", 0
  488. .dw basIN
  489. .db "getc", 0
  490. .dw basGETC
  491. .db "putc", 0
  492. .dw basPUTC
  493. .db "puth", 0
  494. .dw basPUTH
  495. .db "sleep", 0
  496. .dw basSLEEP
  497. .db "addr", 0
  498. .dw basADDR
  499. .db "usr", 0
  500. .dw basUSR
  501. .db 0xff ; end of table