Closed hackerb9 closed 1 year ago
PS A nice Rosetta Stone of various early BASICs can be found here: BASIC Program Conversion by Bill Crider. It covers the IBM PC & PC jr, C64, Apple //e, Apple ][+, TRS-80 Models III and IV, and TRS-80 Color Computers. It goes over every reserved word and what it means on the various platforms.
Further questions:
- What is the difference between basic-builtin-regexp and basic-keyword-regexp? It seemed like keywords were mostly flow control (IF, GOTO, CALL) and data declarations (DIM, DEFINT), while builtins were everything else (POKE, PRINT), but I couldn't quite tell. For example, why are "AS" and "RANDOMIZE" keywords? Why isn't "PEEK" a function?
The division of reserved words into categories is loosely based on the QuickBasic 4.5 documentation, which for example states that PEEK is a statement. Aside from that, I made some arbitrary choices that are very likely to be incorrect.
To make basic-mode more similar to other programming modes, I think built-in functions (e.g. ABS, CHR$) should be highlighted in builtin-face, statements (e.g. IF, DIM, RANDOMIZE) should be highlighted in keyword-face, and function/subroutine definitions should be highlighted in function-name-face. Whether PEEK (and possibly others) are built-in functions or statements will be up to the derived mode to decide.
Whether PEEK (and possibly others) are built-in functions or statements will be up to the derived mode to decide.
I agree. If the documentation for a particular language specifies one way or another, the derived mode should try to match that expectation of the programmer.
What was confusing to me was how I ought to use the font-lock-builtin-face when I was trying to create a new derived mode. [Update: Please see issue #21 for more about this.]
Just for reference, here are the categories into which Bill Crider's “BASIC Programming Conversion” puts the reserved words from several dialects of BASIC. Actually, "tags" would be a better description, as each word can fit in multiple boxes.
Crider says that he is covering BASIC for "Apple, IBM PC, IBM PCjr, Commodore 64, TRS-80 Model III, and TRS-80 Color Computer". However, I noticed that his list of all 551 reserved words one should avoid using in identifiers as they are defined in one BASIC or another actually includes more keywords than were listed in the categories above. They appear to be from Atari BASIC (e.g., PTRIG
) and Tektronix 4051 Graphic System BASIC Language (e.g., BAPPEN
).
I have extended the changes I showed here so that derived modes can add and remove words from the parent mode instead of redefining the entire list. As an example, I have derived a TRS-80 Model 100 mode from the TRS-80 mode using just the differences: removing a few keywords and adding others. This is just to demonstrate how it could work. I do not know that there actually needs to be a Model 100 mode that is separate from the general TRS-80 mode.
You can see the changes at https://github.com/hackerb9/basic-mode/tree/dervish
The main difference was postponing the regexp-opt until basic-mode-initialize was called.
Open questions:
Here is an example of how one might derive a new mode from a previous one by changing only a few parts. While I still don't know if this will be useful for you, as the developer of basic-mode, I realized that holding off on the regexp-opt is a very good thing for users, particularly anybody who wants to extend basic-mode with their dialect's idiosyncrasies.
(define-derived-mode basic-m100-mode basic-trs80-mode "Basic[M100]"
"Programming mode for BASIC for the TRS-80 Model 100.
It is a test of inheriting and modifying the TRS-80 mode.
Derived from `basic-trs80-mode'."
(mapcar (lambda (x) (delete x basic-functions))
'("mem" "point" "set" "random" "reset" "usr"))
(nconc basic-functions
'("csrlin" "date$" "day$" "eof" "himem"
"instr" "input$" "maxfiles" "maxram"
"rnd" "space$" "time$"))
(mapcar (lambda (x) (delete x basic-builtins))
'("auto" "delete" "system" "troff" "tron"))
(nconc basic-builtins
'("beep" "cloadm" "close" "csave" "csavem"
"files" "ipl" "key" "kill" "lcopy" "line"
"load" "loadm"
"menu" "merge" "motor" "name" "open"
"power" "preset" "print @" "pset" "save" "savem"
"screen" "sound"))
(nconc basic-keywords
'("com" "mdm" "on com gosub" "on error goto"
"on key gosub" "on mdm gosub" "on time$"
"runm" "time"))
(basic-mode-initialize))
Of course, for this to work, you'd need the derivative-mode modifications to basic-mode.el found in my dervish tree.
Side note: The Model 100 keywords and builtins should probably get added to the main basic-mode lists instead of being a separate submode as I notice that many of them are also listed in the QuickBasic 4.5 manual.
I have now created a PR for this issue. It can be considered as a first draft. There is work left to do. Documentation needs to be updated. And the QB4.5 mode needs to be finished.
The changes build mostly on your examples. I have updated how variables are set because I experienced some problems with nconc.
I added in the TRS modes even though they were examples. If you want them updated or removed, let me know.
I also removed some keywords and types. defbool does not seem to exist other than in my imagination. I also removed the boolean type and true and false constants because they do not exist in classic BASIC dialects. Maybe there is something more to remove from the standard basic-mode?
By the way, since the standard basic-mode is getting stripped down to just the core, I suggest that .BAS files should open up in a submode (basic-generic-mode
?) that is widely encompassing. That way people new to basic-mode won't have to do anything to get it to work (usually).
Generic mode could just be an alias for QB45, which seems fairly broad, but I'm imagining it as the union of all the other modes, so that no matter whether a person is typing VARPTR, USR, or PTRIG it'll get fontified. For the most part, the extra syntax highlighting won't hurt as it only happens if those keywords are actually used (e.g., TRUE = -1
) .
A basic-generic-mode as the default is a good idea. That makes the main basic-mode just a template for sub modes.
As discussed in #18, it would be useful if basic-mode let users pick from a set of predefined BASIC flavors and made it easier for them to create customized modes own.
One solution would be to use
define-derived-mode
to create new submodes. Here is an example based on the reference card for the TRS-80 Model III's BASIC. It is similar to most other TRS-80 BASICs (like Color Basic and Model 100 BASIC), but has some notable differences. For example, one of the statements allowed isCLOAD?
, so in this example I've modified the syntax table so that question mark is part of an identifier instead of punctuation. [Side note: An unintended benefit of this is that programs that use?
as shorthand forPRINT
are properly syntax highlighted, as long as the question mark is followed by a space.]Click to see basic-trs80-mode
```elisp (defun basic-mode-initialize () "Initializations for sub-modes of basic-mode. This is called by basic-mode on startup and by its derived modes after making customizations to font-lock keywords and syntax tables." (setq-local basic-font-lock-keywords (list (list basic-comment-regexp 0 'font-lock-comment-face) (list basic-linenum-regexp 0 'font-lock-constant-face) (list basic-label-regexp 0 'font-lock-constant-face) (list basic-constant-regexp 0 'font-lock-constant-face) (list basic-keyword-regexp 0 'font-lock-keyword-face) (list basic-type-regexp 0 'font-lock-type-face) (list basic-function-regexp 0 'font-lock-function-name-face) (list basic-builtin-regexp 0 'font-lock-builtin-face))) (if basic-syntax-highlighting-require-separator (setq-local font-lock-defaults (list basic-font-lock-keywords nil t)) (setq-local font-lock-defaults (list basic-font-lock-keywords nil t basic-font-lock-syntax))) (unless font-lock-mode (font-lock-mode 1))) (define-derived-mode basic-trs80-mode basic-mode "Basic[TRS-80]" "Programming mode for BASIC for TRS-80 machines. This is just a demo and so only handles TRS-80 Model III. At a minimum, this is missing keywords used in the TRS-80 Model 100 BASIC and TRS-80 Extended Color Computer BASIC. For more details see `basic-mode'." (setq-local basic-function-regexp (regexp-opt '("abs" "asc" "atn" "cdbl" "cint" "chr$" "cos" "csng" "erl" "err" "exp" "fix" "fre" "inkey$" "inp" "int" "left$" "len" "log" "mem" "mid$" "point" "pos" "reset" "right$" "set" "sgn" "sin" "sqr" "str$" "string$" "tan" "time$" "usr" "val" "varptr") 'symbols)) (setq-local basic-builtin-regexp (regexp-opt '("?" "auto" "clear" "cload" "cload?" "cls" "data" "delete" "edit" "input" "input #" "let" "list" "llist" "lprint" "lprint tab" "lprint using" "new" "mod" "not" "or" "out" "peek" "poke" "print" "print tab" "print using" "read" "restore" "system" "troff" "tron") 'symbols)) (setq-local basic-keyword-regexp (regexp-opt '("as" "defdbl" "defint" "defsng" "defstr" "dim" "do" "else" "end" "error" "for" "gosub" "go sub" "goto" "go to" "if" "next" "on" "step" "random" "resume" "return" "then" "to") 'symbols)) (modify-syntax-entry ?? "w " basic-mode-syntax-table) ; Treat ? as part of identifier ("cload?") (modify-syntax-entry ?# "w " basic-mode-syntax-table) ; Treat # as part of identifier ("input #") (basic-mode-initialize)) ```As you can see, I'm setting some of the variables, like basic-keyword-regexp, that were declared constant via
defconst
, so I changed those todefvar
.In addition, I removed some of the font-lock initialization from end of the definition of basic-mode and moved it into basic-mode-initialize. I may have moved more than necessary, but it seems to work.
basic-mode
```elisp (define-derived-mode basic-mode prog-mode "Basic" "Major mode for editing BASIC code." :group 'basic (add-hook 'xref-backend-functions #'basic-xref-backend nil t) (setq-local indent-line-function 'basic-indent-line) (setq-local comment-start "'") (setq-local syntax-propertize-function (syntax-propertize-rules ("\\(\\_Further questions:
What is the difference between basic-builtin-regexp and basic-keyword-regexp? It seemed like keywords were mostly control-flow (IF, GOTO, CALL) and data declarations (DIM, DEFINT), while builtins were everything else (POKE, PRINT), but I couldn't quite tell. For example, why are "AS" and "RANDOMIZE" keywords? Why isn't "PEEK" a function?
It would be nice to be able to just add-to-list/delete a few keywords instead of having to redefine everything when deriving a submode. To do that would require creating variables to hold the basic-builtins, basic-keywords, and basic-functions, and not run
regexp-opt
until in basic-mode-initialize. That shouldn't be too hard, but I haven't done it in my example code.Conf-mode is often able to autodetect the proper submode. Are there ways to detect a BASIC dialect?
Are submodes the best way to provide this functionality? They have very nice hierarchical inheritance properties that would be hard to beat. (For example, if there were different submodes for QBasic, QuickBasic, GW-BASIC, and BASICA, they might all derive from a common Microsoft-BASIC ancestor). Are there any other possibilities besides define-derived-mode?
Trivia: According to the Small BASIC FAQ, there are over 230 different BASIC dialects.