- {env: IMPL=ada, services: [docker]}
- {env: IMPL=awk, services: [docker]}
- {env: IMPL=bash, services: [docker]}
- - {env: IMPL=basic, services: [docker]}
+ - {env: IMPL=basic basic_MODE=cbm, services: [docker]}
+ - {env: IMPL=basic basic_MODE=qbasic, services: [docker]}
- {env: IMPL=c, services: [docker]}
- {env: IMPL=cpp, services: [docker]}
- {env: IMPL=coffee, services: [docker]}
MAL_IMPL = js
+# cbm or qbasic
+basic_MODE = cbm
# clj or cljs (Clojure vs ClojureScript/lumo)
clojure_MODE = clj
# python, js, cpp, or neko
# Implementation specific utility functions
#
+basic_STEP_TO_PROG_cbm = basic/$($(1)).bas
+basic_STEP_TO_PROG_qbasic = basic/$($(1))
+
haxe_STEP_TO_PROG_neko = haxe/$($(1)).n
haxe_STEP_TO_PROG_python = haxe/$($(1)).py
haxe_STEP_TO_PROG_cpp = haxe/cpp/$($(1))
awk_STEP_TO_PROG = awk/$($(1)).awk
bash_STEP_TO_PROG = bash/$($(1)).sh
basic_STEP_TO_PROG = basic/$($(1)).bas
+basic_STEP_TO_PROG = $(basic_STEP_TO_PROG_$(basic_MODE))
c_STEP_TO_PROG = c/$($(1))
d_STEP_TO_PROG = d/$($(1))
chuck_STEP_TO_PROG = chuck/$($(1)).ck
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
+
+
-MODE = cbm
-BASICPP_OPTS = --mode $(MODE)
+basic_MODE = cbm
+BASICPP_OPTS = --mode $(basic_MODE)
+
+QB64 = qb64
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
STEPS1_A = step1_read_print.bas step2_eval.bas $(STEPS3_A)
STEPS0_A = step0_repl.bas $(STEPS1_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
+$(STEPS3_A): env.in.bas
+$(STEPS4_A): core.in.bas
+
+
all: $(STEPS0_A)
step%.bas: step%.in.bas
./basicpp.py $(BASICPP_OPTS) $< > $@
-$(STEPS0_A): readline.in.bas
-$(STEPS1_A): debug.in.bas mem.in.bas types.in.bas reader.in.bas printer.in.bas
-$(STEPS3_A): env.in.bas
-$(STEPS4_A): core.in.bas
-
tests/%.bas: tests/%.in.bas
./basicpp.py $(BASICPP_OPTS) $< > $@
+# QBasic specific compilation rule
+step%: step%.bas
+ $(QB64) -x $(abspath $<) -o $(abspath $@)
# CBM/C64 image rules
.PHONY: clean stats
clean:
- rm -f $(STEPS0_A) *.d64 *.prg
+ rm -f $(STEPS0_A) $(subst .bas,,$(STEPS0_A)) *.d64 *.prg qb64
+ rm -rf ./internal
SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas
-SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP)
+SOURCES = readline.in.bas readline_line.in.bas readline_char.in.bas \
+ types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP)
stats: $(SOURCES)
@wc $^
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,
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
return args
# pull in include files
-def resolve_includes(orig_lines, keep_rems=0):
+def resolve_includes(orig_lines, args):
included = {}
- lines = []
- for line in orig_lines:
- m = re.match(r"^ *REM \$INCLUDE: '([^'\n]*)' *$", line)
- if m and m.group(1) not in included:
- f = m.group(1)
- if f not in included:
+ lines = orig_lines[:]
+ position = 0
+ while position < len(lines):
+ line = lines[position]
+ m = re.match(r"^(?:#([^ ]*) )? *REM \$INCLUDE: '([^'\n]*)' *$", line)
+ if m:
+ mode = m.group(1)
+ f = m.group(2)
+ if mode and mode != args.mode and mode != args.full_mode:
+ position += 1
+ elif f not in included:
ilines = [l.rstrip() for l in open(f).readlines()]
- if keep_rems: lines.append("REM vvv BEGIN '%s' vvv" % f)
- lines.extend(ilines)
- if keep_rems: lines.append("REM ^^^ END '%s' ^^^" % f)
+ if args.keep_rems: lines.append("REM vvv BEGIN '%s' vvv" % f)
+ lines[position:position+1] = ilines
+ if args.keep_rems: lines.append("REM ^^^ END '%s' ^^^" % f)
else:
debug("Ignoring already included file: %s" % f)
else:
- lines.append(line)
+ position += 1
return lines
-def resolve_mode(orig_lines, mode):
+def resolve_mode(orig_lines, args):
lines = []
for line in orig_lines:
m = re.match(r"^ *#([^ \n]*) *([^\n]*)$", line)
if m:
- if m.group(1) == mode:
+ 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 text.split("\n")
-def finalize(lines, args, mode):
+def finalize(lines, args):
labels_lines = {}
lines_labels = {}
call_index = {}
label = sub+"_"+str(call_index[sub])
# Replace the CALL with stack based GOTO
- if mode == "cbm":
+ if args.mode == "cbm":
lines.append("%s %sQ=%s:GOSUBPUSH_Q:GOTO%s" % (
lnum, prefix, call_index[sub], sub))
else:
index = call_index[cur_sub]
ret_labels = [cur_sub+"_"+str(i) for i in range(1, index+1)]
- if mode == "cbm":
+ if args.mode == "cbm":
line = "%s GOSUBPOP_Q:ONQGOTO%s" % (lnum, ",".join(ret_labels))
else:
line = "%s X=X-1:ON X%%(X+1) GOTO %s" % (lnum, ",".join(ret_labels))
stext = text
text = re.sub(r"(THEN *)%s\b" % a, r"\g<1>%s" % b, stext)
#text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext)
- if mode == "cbm":
+ if args.mode == "cbm":
text = re.sub(r"ON *([^:\n]*) *GOTO *([^:\n]*)\b%s\b" % a, r"ON\g<1>GOTO\g<2>%s" % b, text)
text = re.sub(r"ON *([^:\n]*) *GOSUB *([^:\n]*)\b%s\b" % a, r"ON\g<1>GOSUB\g<2>%s" % b, text)
else:
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
debug("Original lines: %s" % len(lines))
# pull in include files
- lines = resolve_includes(lines, keep_rems=args.keep_rems)
+ lines = resolve_includes(lines, args)
debug("Lines after includes: %s" % len(lines))
- lines = resolve_mode(lines, mode=args.mode)
+ lines = resolve_mode(lines, args)
debug("Lines after resolving mode specific lines: %s" % len(lines))
# drop blank lines
lines = misc_fixups(lines)
# number lines, drop/keep labels, combine lines
- lines = finalize(lines, args, mode=args.mode)
+ lines = finalize(lines, args)
debug("Lines after finalizing: %s" % len(lines))
print("\n".join(lines))
DO_SLURP_LOOP:
C$=""
RJ=1:GOSUB READ_FILE_CHAR
- IF ASC(C$)=10 THEN R$=R$+CHR$(13)
+ #cbm IF ASC(C$)=10 THEN R$=R$+CHR$(13)
+ #qbasic IF ASC(C$)=10 THEN R$=R$+CHR$(10)
IF (ASC(C$)<>10) AND (C$<>"") THEN R$=R$+C$
IF EZ>0 THEN GOTO DO_SLURP_DONE
GOTO DO_SLURP_LOOP
PR_STRING_READABLY:
S1$="\":S2$="\\":GOSUB REPLACE: REM escape backslash "
S1$=CHR$(34):S2$="\"+CHR$(34):GOSUB REPLACE: REM escape quotes "
- S1$=CHR$(13):S2$="\n":GOSUB REPLACE: REM escape newlines
+ #cbm S1$=CHR$(13):S2$="\n":GOSUB REPLACE: REM escape newlines
+ #qbasic S1$=CHR$(10):S2$="\n":GOSUB REPLACE: REM escape newlines
R$=CHR$(34)+R$+CHR$(34)
RETURN
PR_SYMBOL:
IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_RETURN
R$=MID$(T$,2,LEN(T$)-2)
S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes
- S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines
+ #cbm S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines
+ #qbasic S1$=CHR$(92)+"n":S2$=CHR$(10):GOSUB REPLACE: REM unescape newlines
S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes
REM intern string value
B$=R$:T=4:GOSUB STRING
-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
+
+#cbm REM $INCLUDE: 'readline_char.in.bas'
+#qbasic-ui REM $INCLUDE: 'readline_char.in.bas'
+#qbasic-noui REM $INCLUDE: 'readline_line.in.bas'
--- /dev/null
+REM READLINE(A$) -> R$
+READLINE:
+ EZ=0
+ PRINT A$ ;
+ LINE INPUT ; R$
+ RETURN
#!/bin/bash
cd $(dirname $0)
(echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > .args.mal
-exec cbmbasic ${STEP:-stepA_mal}.bas "${@}"
+case ${basic_MODE:-cbm} in
+ cbm) exec cbmbasic ${STEP:-stepA_mal}.bas "${@}" ;;
+ qbasic) exec ./${STEP:-stepA_mal} "${@}" ;;
+ *) echo "Invalid basic_MODE: ${basic_MODE}"; exit 2 ;;
+esac
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
REM if the error is an object, then print and free it
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
- PRINT:GOSUB PR_MEMORY_SUMMARY_SMALL
REM GOSUB PR_MEMORY_MAP
REM P1=0:P2=ZI:GOSUB PR_MEMORY
REM P1=D:GOSUB PR_OBJECT
REM P1=ZK:GOSUB PR_OBJECT
- END
+ #cbm END
+ #qbasic SYSTEM
PRINT_ERROR:
REM if the error is an object, then print and free it