1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-23 12:14:45 +03:00

basic: use FreeBASIC/fbc for qbasic mode

- Update Dockerfile to Ubuntu 24.04.
  - Install FreeBASIC 1.10.1 instead of qb64.
- Convert basicpp.py to remove qb64 specific console vs UI support.
- Convert to use freebasic file open (and error handling specifically),
  and TIMER call.
- Fix bug in step0 revealed by FreeBASIC: X% is not declared. Call
  DIM_MEMORY during startup.
- Remove the whole line version of readline. The character at a time
  version works now for both implementations (and has the ability to
  backspace the line and exit with Ctrl-D).
This commit is contained in:
Joel Martin 2024-11-19 11:33:10 -06:00
parent 05461b75a7
commit f945cc0e56
11 changed files with 67 additions and 88 deletions

View File

@ -252,26 +252,27 @@ bash stepX_YYY.sh
### BASIC (C64 and QBasic)
The BASIC implementation uses a preprocessor that can generate BASIC
code that is compatible with both C64 BASIC (CBM v2) and QBasic. The
code that is compatible with both C64 BASIC (CBM v2) or QBasic. The
C64 mode has been tested with
[cbmbasic](https://github.com/kanaka/cbmbasic) (the patched version is
currently required to fix issues with line input) and the QBasic mode
has been tested with [qb64](http://www.qb64.net/).
has been tested with [FreeBASIC](freebasic.net).
Generate C64 code and run it using cbmbasic:
```
cd impls/basic
make stepX_YYY.bas
STEP=stepX_YYY ./run
make MODE=cbm stepX_YYY.bas
STEP=stepX_YYY basic_MODE=cbm ./run
```
Generate QBasic code and load it into qb64:
Generate QBasic code, compile using FreeBASIC, and execute it:
```
cd impls/basic
make MODE=qbasic stepX_YYY.bas
./qb64 stepX_YYY.bas
make MODE=qbasic stepX_YYY
./stepX_YYY
```
Thanks to [Steven Syrek](https://github.com/sjsyrek) for the original

View File

@ -1,4 +1,4 @@
FROM ubuntu:wily
FROM ubuntu:24.04
MAINTAINER Joel Martin <github@martintribe.org>
##########################################################
@ -9,10 +9,8 @@ MAINTAINER Joel Martin <github@martintribe.org>
RUN apt-get -y update
# Required for running tests
RUN apt-get -y install make python
# Some typical implementation and test requirements
RUN apt-get -y install curl libreadline-dev libedit-dev
RUN apt-get -y install make python3
RUN ln -fs /usr/bin/python3 /usr/local/bin/python
RUN mkdir -p /mal
WORKDIR /mal
@ -21,26 +19,25 @@ WORKDIR /mal
# Specific implementation requirements
##########################################################
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \
ca-certificates curl gcc g++ libasound2-dev \
libglu1-mesa-dev mesa-common-dev patch unzip wget \
xz-utils libncurses-dev
# cbmbasic
RUN apt-get install -y gcc unzip patch
# Remove duplicate RAM (https://github.com/mist64/cbmbasic/commit/352a313313dd0a15a47288c8f8031b54ac8c92a2).
RUN cd /tmp && \
curl -L https://github.com/kanaka/cbmbasic/archive/master.zip -o cbmbasic.zip && \
unzip cbmbasic.zip && \
cd cbmbasic-master && \
sed -i '/unsigned char RAM.65536.;/d' runtime.c && \
make && \
cp cbmbasic /usr/bin/cbmbasic && \
mv cbmbasic /usr/local/bin && \
cd .. && \
rm -r cbmbasic*
RUN apt-get install -y g++ mesa-common-dev libglu1-mesa-dev libasound2-dev wget
RUN cd /tmp && \
curl -L http://www.qb64.net/release/official/2017_02_09__02_14_38-1.1-20170120.51/linux/qb64-1.1-20170120.51-lnx.tar.gz | tar xzf - && \
cd qb64 && \
find . -name '*.sh' -exec sed -i "s/\r//g" {} \; && \
env EUID=1 ./setup_lnx.sh && \
mkdir -p /usr/share/qb64 && \
cp -a qb64 internal LICENSE programs source /usr/share/qb64/ && \
echo '#!/bin/sh\ncd /usr/share/qb64\n./qb64 "${@}"' > /usr/bin/qb64 && \
chmod +x /usr/bin/qb64
# qbasic (using freebasic: `fbc -lang qb`)
RUN cd /opt && \
curl -L https://sourceforge.net/projects/fbc/files/FreeBASIC-1.10.1/Binaries-Linux/FreeBASIC-1.10.1-ubuntu-22.04-x86_64.tar.xz | tar xvJf - && \
ln -sf /opt/FreeBASIC-1.10.1-ubuntu-22.04-x86_64/bin/fbc /usr/local/bin/fbc

View File

@ -1,7 +1,7 @@
basic_MODE = cbm
BASICPP_OPTS = --mode $(basic_MODE)
QB64 = qb64
FBC = fbc -lang qb
STEPS4_A = step4_if_fn_do.bas step5_tco.bas step6_file.bas \
step7_quote.bas step8_macros.bas step9_try.bas stepA_mal.bas
@ -11,8 +11,8 @@ STEPS0_A = step0_repl.bas $(STEPS1_A)
all: $(if $(filter qbasic,$(basic_MODE)),$(subst .bas,,$(STEPS0_A)),$(STEPS0_A))
$(STEPS0_A): readline.in.bas readline_line.in.bas readline_char.in.bas
$(STEPS1_A): debug.in.bas mem.in.bas types.in.bas reader.in.bas printer.in.bas
$(STEPS0_A): debug.in.bas mem.in.bas readline.in.bas
$(STEPS1_A): types.in.bas reader.in.bas printer.in.bas
$(STEPS3_A): env.in.bas
$(STEPS4_A): core.in.bas
@ -26,7 +26,7 @@ tests/%.bas: tests/%.in.bas
# QBasic specific compilation rule
step%: step%.bas
$(QB64) -x $(abspath $<) -o $(abspath $@)
$(FBC) $< -x $@
# CBM/C64 image rules
@ -58,5 +58,5 @@ mal.d64: mal.prg .args.mal.prg core.mal.prg
.PHONY: clean
clean:
rm -f $(STEPS0_A) $(subst .bas,,$(STEPS0_A)) *.d64 *.prg qb64
rm -f $(STEPS0_A) $(subst .bas,,$(STEPS0_A)) *.d64 *.prg
rm -rf ./internal

View File

@ -13,7 +13,6 @@ def parse_args():
parser.add_argument('infiles', type=str, nargs='+',
help='the Basic files to preprocess')
parser.add_argument('--mode', choices=["cbm", "qbasic"], default="cbm")
parser.add_argument('--sub-mode', choices=["noui", "ui"], default="noui")
parser.add_argument('--keep-rems', action='store_true', default=False,
help='The type of REMs to keep (0 (none) -> 4 (all)')
parser.add_argument('--keep-blank-lines', action='store_true', default=False,
@ -26,7 +25,6 @@ def parse_args():
help='Do not combine lines using the ":" separator')
args = parser.parse_args()
args.full_mode = "%s-%s" % (args.mode, args.sub_mode)
if args.keep_rems and not args.skip_combine_lines:
debug("Option --keep-rems implies --skip-combine-lines ")
args.skip_combine_lines = True
@ -48,7 +46,7 @@ def resolve_includes(orig_lines, args):
if m:
mode = m.group(1)
f = m.group(2)
if mode and mode != args.mode and mode != args.full_mode:
if mode and mode != args.mode:
position += 1
elif f not in included:
ilines = [l.rstrip() for l in open(f).readlines()]
@ -68,8 +66,6 @@ def resolve_mode(orig_lines, args):
if m:
if m.group(1) == args.mode:
lines.append(m.group(2))
elif m.group(1) == args.full_mode:
lines.append(m.group(2))
continue
lines.append(line)
return lines
@ -111,7 +107,7 @@ def misc_fixups(orig_lines):
text = re.sub(r"\bIF ", "IF", text)
text = re.sub(r"\bPRINT *", "PRINT", text)
text = re.sub(r"\bDIM ", "DIM", text)
text = re.sub(r"\OPEN ", "OPEN", text)
text = re.sub(r"\bOPEN ", "OPEN", text)
text = re.sub(r"\bGET ", "GET", text)
text = re.sub(r"\bPOKE ", "POKE", text)
text = re.sub(r"\bCLOSE ", "CLOSE", text)
@ -161,7 +157,7 @@ def finalize(lines, args):
if m:
prefix = m.groups(1)[0]
sub = m.groups(1)[1]
if not call_index.has_key(sub):
if not sub in call_index:
call_index[sub] = 0
call_index[sub] += 1
label = sub+"_"+str(call_index[sub])
@ -295,14 +291,6 @@ def finalize(lines, args):
text = update_labels_lines(text, a, b)
lines = text.split("\n")
# Force non-UI QBasic to use text console. LINE INPUT also needs
# to be used instead in character-by-character READLINE
if args.full_mode == "qbasic-noui":
# Add console program prefix for qb64/qbasic
lines = ["$CONSOLE",
"$SCREENHIDE",
"_DEST _CONSOLE"] + lines
return lines
if __name__ == '__main__':

View File

@ -280,8 +280,8 @@ DO_FUNCTION:
EZ=0
#cbm OPEN 2,8,0,S$(A1)
#qbasic A$=S$(A1)
#qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN
#qbasic OPEN A$ FOR INPUT AS #2
#qbasic IF ERR()<>0 THEN ER=-1:E$="File not found":RETURN
DO_SLURP_LOOP:
C$=""
RJ=1:GOSUB READ_FILE_CHAR
@ -323,7 +323,7 @@ DO_FUNCTION:
RETURN
DO_TIME_MS:
#cbm T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC
#qbasic T=2:L=INT((TIMER(0.001)-BT#)*1000):GOSUB ALLOC
#qbasic T=2:L=INT((TIMER()-BT#)*1000):GOSUB ALLOC
RETURN
DO_LIST:

View File

@ -384,7 +384,7 @@ INIT_MEMORY:
REM start of time clock
#cbm BT=TI
#qbasic BT#=TIMER(0.001)
#qbasic BT#=TIMER()
RETURN

View File

@ -267,8 +267,8 @@ READ_FILE:
SD=0: REM sequence read depth
D$="": REM pending read/peek character
#cbm OPEN 2,8,0,A$
#qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN
#qbasic OPEN A$ FOR INPUT AS #2
#qbasic IF ERR()<>0 THEN ER=-1:E$="File not found":RETURN
REM READ_TOKEN adds "(do ... )"
CALL READ_FORM
CLOSE 2

View File

@ -1,4 +1,32 @@
REM READLINE(A$) -> R$
READLINE:
EZ=0
PRINT A$;
C$="":R$="":C=0
READCH:
#cbm GET C$
#qbasic C$=INKEY$
IF C$="" THEN GOTO READCH
C=ASC(C$)
#qbasic IF ASC(C$)=8 THEN C=20:C$=CHR$(20)
IF C=4 OR C=0 THEN EZ=1:GOTO RL_DONE: REM EOF
IF C=127 OR C=20 THEN GOSUB RL_BACKSPACE
IF C=127 OR C=20 THEN GOTO READCH
IF (C<32 OR C>127) AND C<>13 THEN GOTO READCH
PRINT C$;
IF LEN(R$)<255 AND C$<>CHR$(13) THEN R$=R$+C$
IF LEN(R$)<255 AND C$<>CHR$(13) THEN GOTO READCH
RL_DONE:
#qbasic PRINT
RETURN
REM Assumes R$ has input buffer
RL_BACKSPACE:
IF LEN(R$)=0 THEN RETURN
R$=LEFT$(R$,LEN(R$)-1)
#cbm PRINT CHR$(157)+" "+CHR$(157);
#qbasic LOCATE ,POS(0)-1
#qbasic PRINT " ";
#qbasic LOCATE ,POS(0)-1
RETURN
#cbm REM $INCLUDE: 'readline_char.in.bas'
#qbasic-ui REM $INCLUDE: 'readline_char.in.bas'
#qbasic-noui REM $INCLUDE: 'readline_line.in.bas'

View File

@ -1,31 +0,0 @@
REM READLINE(A$) -> R$
READLINE:
EZ=0
PRINT A$;
C$="":R$="":C=0
READCH:
#cbm GET C$
#qbasic C$=INKEY$
IF C$="" THEN GOTO READCH
C=ASC(C$)
REM PRINT C
#qbasic IF ASC(C$)=8 THEN C=20:C$=CHR$(20)
IF C=4 OR C=0 THEN EZ=1:GOTO RL_DONE: REM EOF
IF C=127 OR C=20 THEN GOSUB RL_BACKSPACE
IF C=127 OR C=20 THEN GOTO READCH
IF (C<32 OR C>127) AND C<>13 THEN GOTO READCH
PRINT C$;
IF LEN(R$)<255 AND C$<>CHR$(13) THEN R$=R$+C$
IF LEN(R$)<255 AND C$<>CHR$(13) THEN GOTO READCH
RL_DONE:
RETURN
REM Assumes R$ has input buffer
RL_BACKSPACE:
IF LEN(R$)=0 THEN RETURN
R$=LEFT$(R$,LEN(R$)-1)
#cbm PRINT CHR$(157)+" "+CHR$(157);
#qbasic LOCATE ,POS(0)-1
#qbasic PRINT " ";
#qbasic LOCATE ,POS(0)-1
RETURN

View File

@ -1,6 +0,0 @@
REM READLINE(A$) -> R$
READLINE:
EZ=0
PRINT A$ ;
LINE INPUT ; R$
RETURN

View File

@ -23,6 +23,8 @@ END SUB
REM MAIN program
MAIN:
GOSUB DIM_MEMORY
REPL_LOOP:
A$="user> ":GOSUB READLINE: REM call input parser
IF EZ=1 THEN GOTO QUIT