1
1
Fork 0
mirror of https://github.com/boxgaming/qbjs.git synced 2024-05-12 08:00:12 +00:00

initial revision

This commit is contained in:
boxgaming 2022-02-16 11:40:03 -06:00
parent 2f099c5d0d
commit 18789a9d9c
16 changed files with 9015 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
tools/webserver.exe
tools/qb2js.exe

48
codemirror/qb-ide.css Normal file
View file

@ -0,0 +1,48 @@
@font-face {
font-family: dosvga;
src: url(lp-dosvga.ttf);
}
.cm-s-lesser-dark {
line-height: 1em;
}
.cm-s-lesser-dark.CodeMirror { background: rgb(0, 0, 39); color: rgb(216, 216, 216); text-shadow: 0 -1px 1px #262626; font-family: dosvga; /*letter-spacing: -1px*/}
.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/
.cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(0, 49, 78, .99); }
.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); }
.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid #fff; }
.cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/
.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/
.cm-s-lesser-dark .CodeMirror-gutters { background: rgb(0, 49, 78); border-right:1px solid #aaa; }
.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; }
.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; }
.cm-s-lesser-dark .CodeMirror-linenumber { color: rgb(175, 175, 175); }
.cm-s-lesser-dark span.cm-header { color: #a0a; }
.cm-s-lesser-dark span.cm-quote { color: #090; }
.cm-s-lesser-dark span.cm-keyword { color: rgb(69, 118, 147); }
.cm-s-lesser-dark span.cm-atom { color: #C2B470; }
.cm-s-lesser-dark span.cm-number { color: rgb(216, 98, 78); }
.cm-s-lesser-dark span.cm-def { color: white; }
.cm-s-lesser-dark span.cm-variable { color:rgb(216, 216, 216); }
.cm-s-lesser-dark span.cm-variable-2 { color: #669199; }
.cm-s-lesser-dark span.cm-variable-3, .cm-s-lesser-dark span.cm-type { color: white; }
.cm-s-lesser-dark span.cm-property { color: #92A75C; }
.cm-s-lesser-dark span.cm-operator { color: #92A75C; }
.cm-s-lesser-dark span.cm-comment { color: rgb(98, 98, 98); }
.cm-s-lesser-dark span.cm-string { color: rgb(255, 167, 0); }
.cm-s-lesser-dark span.cm-string-2 { color: #f50; }
.cm-s-lesser-dark span.cm-meta { color: #738C73; }
.cm-s-lesser-dark span.cm-qualifier { color: #555; }
.cm-s-lesser-dark span.cm-builtin { color: rgb(69, 118, 147); }
.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; }
.cm-s-lesser-dark span.cm-tag { color: #669199; }
.cm-s-lesser-dark span.cm-attribute { color: #81a4d5; }
.cm-s-lesser-dark span.cm-hr { color: #999; }
.cm-s-lesser-dark span.cm-link { color: #7070E6; }
.cm-s-lesser-dark span.cm-error { color: #9d1e15; }
.cm-s-lesser-dark .CodeMirror-activeline-background { background: rgb(0, 49, 78); }
.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }

372
codemirror/qb-lang.js Normal file
View file

@ -0,0 +1,372 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
/*
For extra ASP classic objects, initialize CodeMirror instance with this option:
isASP: true
E.G.:
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
isASP: true
});
*/
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("vbscript", function(conf, parserConf) {
var ERRORCLASS = 'error';
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
}
var singleOperators = new RegExp("^[\\+\\-\\*/&\\\\\\^<>=]");
var doubleOperators = new RegExp("^((<>)|(<=)|(>=))");
var singleDelimiters = new RegExp('^[\\.,]');
var brakets = new RegExp('^[\\(\\)]');
var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*");
var openingKeywords = ['sub','select','while','if','function', 'property', 'with', 'for', 'type'];
var middleKeywords = ['else','elseif','case'];
var endKeywords = ['next','loop','wend'];
var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']);
var commonkeywords = ['dim', 'as', 'redim', 'then', 'until', 'randomize',
'byval','byref','new','property', 'exit', 'in',
'const', 'integer', 'single', 'double', '_unsigned', 'string', '_byte',
'let', 'option explicit', 'call'];
//This list was from: http://msdn.microsoft.com/en-us/library/f8tbc79x(v=vs.84).aspx
var atomWords = ['true', 'false', 'nothing', 'empty', 'null'];
// QB64 Keywords
var builtinFuncsWords = ['_delay', '_fontwidth', '_height', '_limit', '_keyhit', '_newimage', '_pi', '_printstring', '_printwidth', '_rgb',
'_rgb32', '_round', '_title', '_trim', '_width', 'abs', 'asc', 'atn', 'chr\\$', 'cls', 'color', 'cos', 'fix', 'input',
'instr', 'int', 'left\\$', 'lcase\\$', 'len', 'line', 'locate', 'ltrim\\$', 'mid\\$', 'print', 'right\\$', 'rtrim\\$', 'rnd',
'screen', 'sgn', 'sin', 'sleep', 'sqr', 'str\\$', 'tan', 'ubound', 'ucase\\$', 'val', '_screenexists', 'exp', 'timer',
'pset', '_red32', '_green32', '_blue32', '_red', '_green', '_blue', 'circle', '_alpha32', '_keydown', 'swap', '_instrrev',
'command\\$', '_mouseinput', '_mousex', '_mousey', '_mousebutton', 'log', '_atan2', 'inkey\\$'];
//This list was from: http://msdn.microsoft.com/en-us/library/ydz4cfk3(v=vs.84).aspx
var builtinConsts = ['gx_true','vbBlack', 'vbRed', 'vbGreen', 'vbYellow', 'vbBlue', 'vbMagenta', 'vbCyan', 'vbWhite', 'vbBinaryCompare', 'vbTextCompare',
'vbSunday', 'vbMonday', 'vbTuesday', 'vbWednesday', 'vbThursday', 'vbFriday', 'vbSaturday', 'vbUseSystemDayOfWeek', 'vbFirstJan1', 'vbFirstFourDays', 'vbFirstFullWeek',
'vbGeneralDate', 'vbLongDate', 'vbShortDate', 'vbLongTime', 'vbShortTime', 'vbObjectError',
'vbOKOnly', 'vbOKCancel', 'vbAbortRetryIgnore', 'vbYesNoCancel', 'vbYesNo', 'vbRetryCancel', 'vbCritical', 'vbQuestion', 'vbExclamation', 'vbInformation', 'vbDefaultButton1', 'vbDefaultButton2',
'vbDefaultButton3', 'vbDefaultButton4', 'vbApplicationModal', 'vbSystemModal', 'vbOK', 'vbCancel', 'vbAbort', 'vbRetry', 'vbIgnore', 'vbYes', 'vbNo',
'vbCr', 'VbCrLf', 'vbFormFeed', 'vbLf', 'vbNewLine', 'vbNullChar', 'vbNullString', 'vbTab', 'vbVerticalTab', 'vbUseDefault', 'vbTrue', 'vbFalse',
'vbEmpty', 'vbNull', 'vbInteger', 'vbLong', 'vbSingle', 'vbDouble', 'vbCurrency', 'vbDate', 'vbString', 'vbObject', 'vbError', 'vbBoolean', 'vbVariant', 'vbDataObject', 'vbDecimal', 'vbByte', 'vbArray'];
//This list was from: http://msdn.microsoft.com/en-us/library/hkc375ea(v=vs.84).aspx
var builtinObjsWords = ['WScript', 'err', 'debug', 'RegExp', '\\$if', '\\$end if'];
var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'source', 'value', 'count'];
//var knownMethods = ['clear', 'execute', 'raise', 'replace', 'test', 'write', 'writeline', 'close', 'open', 'state', 'eof', 'update', 'addnew', 'end', 'createobject', 'quit', 'gxscenecreate'];
var knownMethods = ['gxongameevent', 'gxmousex', 'gxmousey', 'gxsoundload', 'gxsoundplay', 'gxsoundrepeat', 'gxsoundvolume', 'gxsoundpause', 'gxsoundstop',
'gxsoundmuted', 'gxsoundmuted', 'gxentityanimate', 'gxentityanimatestop', 'gxentityanimatemode', 'gxentityanimatemode',
'gxscreenentitycreate', 'gxentitycreate', 'gxentityvisible', 'gxentitymove', 'gxentitypos', 'gxentityvx', 'gxentityvy',
'gxentityx', 'gxentityy', 'gxentitywidth', 'gxentityheight', 'gxentityframenext', 'gxentityframeset', 'gxentitytype',
'gxentitytype', 'gxentityuid', 'gxfontuid', 'gxentityapplygravity', 'gxentitycollisionoffset',
'gxentitycollisionoffsetleft', 'gxentitycollisionoffsettop', 'gxentitycollisionoffsetright',
'gxentitycollisionoffsetbottom', 'gxfullscreen', 'gxbackgroundadd', 'gxbackgroundy', 'gxbackgroundheight',
'gxbackgroundclear', 'gxsceneembedded', 'gxscenecreate', 'gxscenewindowsize', 'gxscenescale', 'gxsceneresize',
'gxscenedestroy', 'gxcustomdraw', 'gxframerate', 'gxframe', 'gxscenedraw', 'gxscenemove', 'gxscenepos', 'gxscenex',
'gxsceney', 'gxscenewidth', 'gxsceneheight', 'gxscenecolumns', 'gxscenerows', 'gxscenestart', 'gxsceneupdate',
'gxscenefollowentity', 'gxsceneconstrain', 'gxscenestop', 'gxmapcreate', 'gxmapcolumns', 'gxmaprows', 'gxmaplayers',
'gxmaplayervisible', 'gxmaplayeradd', 'gxmaplayerinsert', 'gxmaplayerremove', 'gxmapresize', 'gxmapdraw',
'gxmaptileposat', 'gxmaptile', 'gxmaptile', 'gxmaptiledepth', 'gxmaptileadd', 'gxmaptileremove', 'gxmapversion',
'gxmapsave', 'gxmapload', 'gxmapisometric', 'gxspritedraw', 'gxspritedrawscaled', 'gxtilesetcreate',
'gxtilesetreplaceimage', 'gxtilesetload', 'gxtilesetsave', 'gxtilesetpos', 'gxtilesetwidth', 'gxtilesetheight',
'gxtilesetcolumns', 'gxtilesetrows', 'gxtilesetfilename', 'gxtilesetimage', 'gxtilesetanimationcreate',
'gxtilesetanimationadd', 'gxtilesetanimationremove', 'gxtilesetanimationframes', 'gxtilesetanimationspeed',
'gxfontcreate', 'gxfontwidth', 'gxfontheight', 'gxfontcharspacing', 'gxfontlinespacing',
'gxdrawtext', 'gxdebug', 'gxdebug', 'gxdebugscreenentities', 'gxdebugfont', 'gxdebugtilebordercolor',
'gxdebugentitybordercolor', 'gxdebugentitycollisioncolor', 'gxkeyinput', 'gxkeydown', 'gxdeviceinputdetect',
'gxdeviceinputtest', 'gxdevicename', 'gxdevicetypename', 'gxinputtypename', 'gxkeybuttonname'];
var aspBuiltinObjsWords = ['server', 'response', 'request', 'session', 'application'];
var aspKnownProperties = ['buffer', 'cachecontrol', 'charset', 'contenttype', 'expires', 'expiresabsolute', 'isclientconnected', 'pics', 'status', //response
'clientcertificate', 'cookies', 'form', 'querystring', 'servervariables', 'totalbytes', //request
'contents', 'staticobjects', //application
'codepage', 'lcid', 'sessionid', 'timeout', //session
'scripttimeout']; //server
var aspKnownMethods = ['addheader', 'appendtolog', 'binarywrite', 'end', 'flush', 'redirect', //response
'binaryread', //request
'remove', 'removeall', 'lock', 'unlock', //application
'abandon', //session
'getlasterror', 'htmlencode', 'mappath', 'transfer', 'urlencode']; //server
var knownWords = knownMethods.concat(knownProperties);
builtinObjsWords = builtinObjsWords.concat(builtinConsts);
if (conf.isASP){
builtinObjsWords = builtinObjsWords.concat(aspBuiltinObjsWords);
knownWords = knownWords.concat(aspKnownMethods, aspKnownProperties);
};
var keywords = wordRegexp(commonkeywords);
var atoms = wordRegexp(atomWords);
var builtinFuncs = wordRegexp(builtinFuncsWords);
var builtinObjs = wordRegexp(builtinObjsWords);
var known = wordRegexp(knownWords);
var stringPrefixes = '"';
var opening = wordRegexp(openingKeywords);
var middle = wordRegexp(middleKeywords);
var closing = wordRegexp(endKeywords);
var doubleClosing = wordRegexp(['end']);
var doOpening = wordRegexp(['do']);
var noIndentWords = wordRegexp(['on error resume next', 'exit']);
var comment = wordRegexp(['rem']);
function indent(_stream, state) {
state.currentIndent++;
}
function dedent(_stream, state) {
state.currentIndent--;
}
// tokenizers
function tokenBase(stream, state) {
if (stream.eatSpace()) {
return 'space';
//return null;
}
var ch = stream.peek();
// Handle Comments
if (ch === "'") {
stream.skipToEnd();
return 'comment';
}
if (stream.match(comment)){
stream.skipToEnd();
return 'comment';
}
// Handle Number Literals
if (stream.match(/^((&H)|(&O))?[0-9\.]/i, false) && !stream.match(/^((&H)|(&O))?[0-9\.]+[a-z_]/i, false)) {
var floatLiteral = false;
// Floats
if (stream.match(/^\d*\.\d+/i)) { floatLiteral = true; }
else if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
else if (stream.match(/^\.\d+/)) { floatLiteral = true; }
if (floatLiteral) {
// Float literals may be "imaginary"
stream.eat(/J/i);
return 'number';
}
// Integers
var intLiteral = false;
// Hex
if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; }
// Octal
else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; }
// Decimal
else if (stream.match(/^[1-9]\d*F?/)) {
// Decimal literals may be "imaginary"
stream.eat(/J/i);
// TODO - Can you have imaginary longs?
intLiteral = true;
}
// Zero by itself with no other piece of number.
else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
if (intLiteral) {
// Integer literals may be "long"
stream.eat(/L/i);
return 'number';
}
}
// Handle Strings
if (stream.match(stringPrefixes)) {
state.tokenize = tokenStringFactory(stream.current());
return state.tokenize(stream, state);
}
// Handle operators and Delimiters
if (stream.match(doubleOperators)
|| stream.match(singleOperators)
|| stream.match(wordOperators)) {
return 'operator';
}
if (stream.match(singleDelimiters)) {
return null;
}
if (stream.match(brakets)) {
return "bracket";
}
if (stream.match(noIndentWords)) {
state.doInCurrentLine = true;
return 'keyword';
}
if (stream.match(doOpening)) {
indent(stream,state);
state.doInCurrentLine = true;
return 'keyword';
}
if (stream.match(opening)) {
if (! state.doInCurrentLine)
indent(stream,state);
else
state.doInCurrentLine = false;
return 'keyword';
}
if (stream.match(middle)) {
return 'keyword';
}
if (stream.match(doubleClosing)) {
dedent(stream,state);
dedent(stream,state);
return 'keyword';
}
if (stream.match(closing)) {
if (! state.doInCurrentLine)
dedent(stream,state);
else
state.doInCurrentLine = false;
return 'keyword';
}
if (stream.match(keywords)) {
return 'keyword';
}
if (stream.match(atoms)) {
return 'atom';
}
if (stream.match(known)) {
return 'variable-2';
}
if (stream.match(builtinFuncs)) {
return 'builtin';
}
if (stream.match(builtinObjs)){
return 'variable-2';
}
if (stream.match(identifiers)) {
return 'variable';
}
// Handle non-detected items
stream.next();
return ERRORCLASS;
}
function tokenStringFactory(delimiter) {
var singleline = delimiter.length == 1;
var OUTCLASS = 'string';
return function(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"]/);
if (stream.match(delimiter)) {
state.tokenize = tokenBase;
return OUTCLASS;
} else {
stream.eat(/['"]/);
}
}
if (singleline) {
if (parserConf.singleLineStringErrors) {
return ERRORCLASS;
} else {
state.tokenize = tokenBase;
}
}
return OUTCLASS;
};
}
function tokenLexer(stream, state) {
var style = state.tokenize(stream, state);
var current = stream.current();
// Handle '.' connected identifiers
if (current === '.') {
style = state.tokenize(stream, state);
current = stream.current();
if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) {
if (style === 'builtin' || style === 'keyword') style='variable';
if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2';
return style;
} else {
return ERRORCLASS;
}
}
return style;
}
var external = {
electricChars:"dDpPtTfFeE ",
startState: function() {
return {
tokenize: tokenBase,
lastToken: null,
currentIndent: 0,
nextLineIndent: 0,
doInCurrentLine: false,
ignoreKeyword: false
};
},
token: function(stream, state) {
if (stream.sol()) {
state.currentIndent += state.nextLineIndent;
state.nextLineIndent = 0;
state.doInCurrentLine = 0;
}
var style = tokenLexer(stream, state);
state.lastToken = {style:style, content: stream.current()};
if (style==='space') style=null;
return style;
},
indent: function(state, textAfter) {
var trueText = textAfter.replace(/^\s+|\s+$/g, '') ;
if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1);
if(state.currentIndent < 0) return 0;
return state.currentIndent * conf.indentUnit;
}
};
return external;
});
CodeMirror.defineMIME("text/vbscript", "vbscript");
});

BIN
gx/__gx_font_default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

2567
gx/gx.js Normal file

File diff suppressed because it is too large Load diff

BIN
img/fullscreen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
img/gx-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
img/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

243
index.html Normal file
View file

@ -0,0 +1,243 @@
<html>
<head>
<meta charset="utf-8"/>
<style>
@font-face {
font-family: dosvga;
src: url(lp-dosvga.ttf);
}
body {
background-color: rgb(0, 0, 39);
font-family: dosvga, Arial, Helvetica, sans-serif;
color: #999;
}
a, a:link, a:visited {
text-decoration: none;
color: #ccc;
}
a:hover { color: #fff; }
a:before { content: "< "; }
a:after { content: " >"; }
#code-container {
position: absolute;
left: 10px;
top: 10px;
}
#code {
width: 600px;
margin-bottom: 5px;
border: 1px solid #666;
}
#game-container {
position: absolute;
left: 620px;
top: 10px;
}
#gx-container {
border: 1px solid #666;
text-align: center;
background-color: #000;
}
#gx-canvas {
border: 1px solid #222;
background-color: #000;
}
#output-container {
position: absolute;
color: #ccc;
display: none;
border: 1px solid #666;
overflow: scroll;
height: 150px;
}
#js-code {
font-size: 14px;
font-family: courier;
white-space: pre;
}
#show-js-container {
color: #666;
position: absolute;
}
#warning-container {
white-space: pre;
font-family: dosvga;
color: #999;
padding: 4px;
}
#share-button {
float: right;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/codemirror.min.css"></link>
<link rel="stylesheet" href="codemirror/qb-ide.css"></link>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/codemirror.min.js"></script>
<script type="text/javascript" src="codemirror/qb-lang.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/addon/selection/active-line.js"></script>
<script type="text/javascript" src="util/shorty.min.js"></script>
</head>
<body>
<div id="code-container">
<div id="code"></div>
<a id="run-button" href="javascript:runProgram()">Run Program</a>
<a id="stop-button" href="javascript:stopProgram()">Stop</a>
<a id="share-button" href="javascript:shareProgram()">Share</a>
</div>
<div id="game-container">
<div id="gx-container"></div>
<div id="output-container">
<div id="warning-container"></div>
<div id="js-code"></div>
</div>
<div id="show-js-container"><input type="checkbox" id="show-js" onclick="window.onresize()"/> Show Javascript</div>
</div>
<div id="gx-footer"></div>
</body>
<script language="javascript" src="gx/gx.js"></script>
<script language="javascript" src="qb.js"></script>
<script language="javascript" src="qb2js.js"></script>
<script language="javascript">
// if code has been passed on the query string load it into the editor
var qbcode = "";
var url = location.href;
if (url && url.indexOf("?")) {
var queryString = url.substring(url.indexOf("?")+1);
var nvpairs = queryString.split("&");
for (var i = 0; i < nvpairs.length; i++) {
var nv = nvpairs[i].split("=");
if (nv[0] == "qbcode") {
var zin = new Shorty();
qbcode = zin.inflate(atob(nv[1]));
break;
}
}
}
// initialize the code editor
var editor = CodeMirror(document.querySelector("#code"), {
lineNumbers: true,
tabSize: 4,
indentUnit: 4,
value: qbcode,
module: "vbscript",
theme: "lesser-dark",
height: "auto",
styleActiveLine: true,
extraKeys: {
"Tab": function(cm) {
cm.replaceSelection(" ", "end");
}
}
});
editor.setSize(600, 600);
editor.on("beforeChange", (cm, change) => {
if (change.origin === "paste") {
const newText = change.text.map(line => line.replace(/\t/g, " "));
change.update(null, null, newText);
}
});
var warnCount = 0;
async function runProgram() {
GX.reset();
QB.start();
var qbCode = editor.getValue();
var jsCode = QBCompiler.compile(qbCode);
displayWarnings();
//displayTypes();
var jsDiv = document.getElementById("js-code");
jsDiv.innerHTML = jsCode;
window.onresize();
try {
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
var codeFn = new AsyncFunction(jsCode);
await codeFn();
}
catch (error) {
console.error(error);
}
document.getElementById("gx-container").focus();
}
function stopProgram() {
QB.halt();
GX.sceneStop();
}
function shareProgram() {
var zout = new Shorty();
var wdiv = document.getElementById("warning-container");
var b64 = btoa(zout.deflate(editor.getValue()));
var baseUrl = location.href.split('?')[0];
wdiv.innerHTML = baseUrl + "?qbcode=" + b64;
warnCount = 1;
window.onresize();
}
function displayWarnings() {
var wstr = "";
var w = QBCompiler.getWarnings();
warnCount = w.length;
for (var i=0; i < w.length; i++) {
wstr += w[i].line + ": " + w[i].text + "\n";
}
var wdiv = document.getElementById("warning-container");
wdiv.innerHTML = wstr;
}
function displayTypes() {
var tstr = "";
var t = QBCompiler.getTypes();
for (var i=0; i < t.length; i++) {
tstr += t[i].name
}
var wdiv = document.getElementById("warning-container");
wdiv.innerHTML = tstr;
}
window.onresize = function() {
var f = document.getElementById("gx-container");
var jsDiv = document.getElementById("output-container");
f.style.width = (window.innerWidth - 635) + "px";
jsDiv.style.width = f.style.width;
if (document.getElementById("show-js").checked || warnCount > 0) {
f.style.height = (window.innerHeight - 210) + "px";
jsDiv.style.display = "block";
jsDiv.style.top = (window.innerHeight - 200) + "px";
}
else {
f.style.height = (window.innerHeight - 50) + "px";
jsDiv.style.display = "none";
}
document.getElementById("show-js-container").style.top = (window.innerHeight - 45) + "px";
document.getElementById("show-js-container").style.right = "5px";
document.getElementById("js-code").style.display = document.getElementById("show-js").checked ? "block" : "none";
editor.setSize(600, window.innerHeight - 50);
}
window.onresize();
function checkButtonState() {
var stopButton = document.getElementById("stop-button");
if (GX.sceneActive() || QB.running()) {
stopButton.style.display = "inline";
}
else {
stopButton.style.display = "none";
}
setTimeout(checkButtonState, 100);
}
checkButtonState();
</script>
</html>

BIN
lp-dosvga.ttf Normal file

Binary file not shown.

922
qb.js Normal file
View file

@ -0,0 +1,922 @@
var QB = new function() {
// QB constants
this.COLUMN_ADVANCE = Symbol("COLUMN_ADVANCE");
this.PREVENT_NEWLINE = Symbol("PREVENT_NEWLINE");
var _fgColor = null;
var _bgColor = null;
var _lastX = 0;
var _lastY = 0;
//var _fntDefault = null;
var _locX = 0;
var _locY = 0;
var _lastKey = null;
var _keyBuffer = [];
var _inputMode = false;
var _haltedFlag = false;
var _runningFlag = false;
// Array handling methods
// ----------------------------------------------------
this.initArray = function(dimensions, obj) {
var a = {};
if (dimensions && dimensions.length > 0) {
a._dimensions = dimensions;
}
else {
// default to single dimension to support Dim myArray() syntax
// for convenient hashtable declaration
a._dimensions = [0];
}
a._newObj = { value: obj };
return a;
};
this.resizeArray = function(a, dimensions, obj, preserve) {
if (!preserve) {
var props = Object.getOwnPropertyNames(a);
for (var i = 0; i < props.length; i++) {
if (props[i] != "_newObj") {
delete a[props[i]];
}
}
}
a._dimensions = dimensions;
};
this.arrayValue = function(a, indexes) {
var value = a;
for (var i=0; i < indexes.length; i++) {
if (value[indexes[i]] == undefined) {
if (i == indexes.length-1) {
value[indexes[i]] = JSON.parse(JSON.stringify(a._newObj));
}
else {
value[indexes[i]] = {};
}
}
value = value[indexes[i]];
}
// added to treat regular objects and qb hashtable/arrays the same
if (value.value == undefined) {
value = { value: value };
}
return value;
};
this.import = async function(url) {
await fetch(url).then(response => response.text()).then((response) => {
var f = new Function(response);
f();
});
};
/*
this.byRef = function(v) {
if (v != undefined && v.value == undefined) {
return { value: v };
}
return v;
};
this.byValue = function(v) {
if (v !== undefined && v.value != undefined) {
return v.value;
}
return v;
};
this.$var = function(varname) {
return window[varname];
};
this.$func = function(methodName) {
var func = window["sub_" + methodName];
if (func == undefined) {
func = window["func_" + methodName]
}
return func;
}
*/
// Process control methods
// -------------------------------------------
this.halt = function() {
_haltedFlag = true;
_runningFlag = false;
};
this.halted = function() {
return _haltedFlag;
};
this.end = function() {
_runningFlag = false;
};
this.start = function() {
_runningFlag = true;
_haltedFlag = false;
}
this.running = function() {
return _runningFlag;
};
// Extended QB64 Keywords
// --------------------------------------------
this.func__Alpha = function(rgb, imageHandle) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).a * 255;
};
this.func__Alpha32 = function(rgb) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).a * 255;
};
this.func__Atan2 = function(y, x) {
return Math.atan2(y, x);
};
this.func__Blue = function(rgb, imageHandle) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).b;
};
this.func__Blue32 = function(rgb) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).b;
};
this.sub__Delay = async function(seconds) {
await GX.sleep(seconds*1000);
};
this.func__FontHeight = function(fnt) {
//return GX.fontHeight(_fntDefault) + GX.fontLineSpacing(_fntDefault);
return 16;
};
this.func__FontWidth = function(fnt) {
//return GX.fontWidth(_fntDefault);
return 8;
};
this.func__Green = function(rgb, imageHandle) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).g;
};
this.func__Green32 = function(rgb) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).g;
};
this.func__Height = function(img) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return GX.sceneHeight();
};
this.func__InStrRev = function(arg1, arg2, arg3) {
var startIndex = +Infinity;
var strSource = "";
var strSearch = "";
if (arg3 != undefined) {
startIndex = arg1-1;
strSource = String(arg2);
strSearch = String(arg3);
}
else {
strSource = String(arg1);
strSearch = String(arg2);
}
return strSource.lastIndexOf(strSearch, startIndex)+1;
};
this.func__KeyDown = function(keyCode) {
// TODO: actual implementation (maybe)
// this is here just to allow converted programs to compile
return GX.keyDown(keyCode) ? -1 : 0;
};
this.func__KeyHit = function() {
// TODO: actual implementation (maybe)
// this is here just to support rendering loops that are using _KeyHit as the exit criteria
return 0;
};
this.sub__Limit = async function(fps) {
// TODO: limit based on frame rate
// need to incorporate time elapsed from last loop invocation
await GX.sleep(1000/fps);
};
this.func__MouseInput = function() {
return GX._mouseInput();
};
this.func__MouseX = function() {
return GX.mouseX();
};
this.func__MouseY = function() {
return GX.mouseY();
};
this.func__MouseButton = function(button) {
return GX.mouseButton(button);
};
this.func__NewImage = function(iwidth, iheight) {
return {
width: iwidth,
height: iheight
};
};
this.sub__PrintString = function(x, y, s) {
// TODO: check the background opacity mode
// Draw the text background
var ctx = GX.ctx();
ctx.beginPath();
ctx.fillStyle = _bgColor.rgba();
ctx.fillRect(x, y, QB.func__FontWidth(), QB.func__FontHeight());
//GX.drawText(_fntDefault, x, y, s);
ctx.font = "16px dosvga";
ctx.fillStyle = _fgColor.rgba();
ctx.fillText(s, x, y+QB.func__FontHeight()-6);
};
this.func__PrintWidth = function(s) {
if (!s) { return 0; }
return String(s).length * QB.func__FontWidth();
};
this.func__Pi = function(m) {
if (m == undefined) {
m = 1;
}
return Math.PI * m;
}
function _rgb(r, g, b) {
return {
r: r,
g: g,
b: b,
a: 1,
rgb: function() { return "rgb(" + this.r + "," + this.g + "," + this.b + ")"; },
rgba: function() { return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")"; }
}
}
this.func__Red = function(rgb, imageHandle) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).r;
};
this.func__Red32 = function(rgb) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return _color(rgb).r;
};
this.func__RGB = function(r, g, b) {
return this.func__RGB32(r, g, b);
};
this.func__RGB32 = function(r, g, b, a) {
if (a == undefined) {
a = 255;
}
if (b == undefined && g != undefined) {
a = g;
g = r;
b = r;
}
else if (b == undefined) {
g = r;
b = r;
}
a = a / 255;
return {
r: r,
g: g,
b: b,
a: a,
rgb: function() { return "rgb(" + this.r + "," + this.g + "," + this.b + ")"; },
rgba: function() { return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")"; }
}
}
this.func__Round = function(value) {
return Math.round(value);
};
this.func__ScreenExists = function() {
return true;
}
this.sub__Title = function(title) {
document.title = title;
};
this.func__Trim = function(value) {
return value.trim();
};
this.func__Width = function(img) {
// TODO: implement corresponding logic when an image handle is supplied (maybe)
return GX.sceneWidth();
};
// QB45 Keywords
// --------------------------------------------
this.func_Asc = function(value, pos) {
if (pos == undefined) {
pos = 0;
}
else { pos--; }
return String(value).charCodeAt(pos);
}
this.func_Abs = function(value) {
return Math.abs(value);
};
this.func_Chr = function(charCode) {
return String.fromCharCode(charCode);
};
this.sub_Cls = function() {
// TODO: parameter variants
var ctx = GX.ctx();
ctx.beginPath();
ctx.fillStyle = _bgColor.rgba();
ctx.fillRect(0, 0, QB.func__Width() , QB.func__Height());
};
function _color(c) {
if (c != undefined && c.r != undefined) {
return c;
}
return QB.func__RGB(0,0,0);
}
this.sub_Color = function(fg, bg) {
if (fg != undefined) {
_fgColor = _color(fg);
}
if (bg != undefined) {
_bgColor = _color(bg);
}
};
this.func_Command = function() {
return "";
};
this.func_Cos = function(value) {
return Math.cos(value);
};
this.func_Exp = function(value) {
return Math.exp(value);
};
this.func_Fix = function(value) {
if (value >=0) {
return Math.floor(value);
}
else {
return Math.floor(Math.abs(value)) * -1;
}
};
function _textColumns() {
return Math.floor(QB.func__Width() / QB.func__FontWidth());
}
function _textRows() {
return Math.floor(QB.func__Height() / QB.func__FontHeight());
}
this.sub_Input = async function(values, preventNewline, addQuestionPrompt, prompt) {
_lastKey = null;
var str = "";
_inputMode = true;
if (prompt != undefined) {
QB.sub_Print([prompt, QB.PREVENT_NEWLINE]);
}
if (prompt == undefined || addQuestionPrompt) {
QB.sub_Print(["? ", QB.PREVENT_NEWLINE]);
}
if (!preventNewline && _locY > _textRows()-1) {
await _printScroll();
_locY = _textRows()-1;
}
//QB.sub__PrintString(_locX * QB.func__FontWidth(), _locY * QB.func__FontHeight(), "? ");
//_locX += 2;
while (_lastKey != "Enter") {
if (_lastKey == "Backspace" && str.length > 0) {
_locX--;
var ctx = GX.ctx();
ctx.beginPath();
ctx.fillStyle = _bgColor.rgba();
ctx.fillRect(_locX * QB.func__FontWidth(), _locY * QB.func__FontHeight(), QB.func__FontWidth() , QB.func__FontHeight());
str = str.substring(0, str.length-1);
}
else if (_lastKey && _lastKey.length < 2) {
QB.sub__PrintString(_locX * QB.func__FontWidth(), _locY * QB.func__FontHeight(), _lastKey);
_locX++;
str += _lastKey;
}
_lastKey = null;
await GX.sleep(10);
}
if (!preventNewline) {
_locY++;
_locX = 0;
}
if (values.length < 2) {
values[0] = str;
}
else {
var vparts = str.split(",");
for (var i=0; i < values.length; i++) {
values[i] = vparts[i] ? vparts[i] : "";
}
}
_inputMode = false;
}
this.func_InKey = function() {
if (_keyBuffer.length < 1) {
return "";
}
return _keyBuffer.shift();
// var temp = _lastKey;
// _lastKey = "";
// return temp;
}
this.func_InStr = function(arg1, arg2, arg3) {
var startIndex = 0;
var strSource = "";
var strSearch = "";
if (arg3 != undefined) {
startIndex = arg1-1;
strSource = String(arg2);
strSearch = String(arg3);
}
else {
strSource = String(arg1);
strSearch = String(arg2);
}
return strSource.indexOf(strSearch, startIndex)+1;
};
this.func_Int = function(value) {
return Math.floor(value);
};
this.func_LCase = function(value) {
return String(value).toLowerCase();
};
this.func_Left = function(value, n) {
return String(value).substring(0, n);
};
this.func_Len = function(value) {
return String(value).length;
};
this.func_Log = function(value) {
return Math.log(value);
};
this.sub_Circle = function(step, x, y, radius, color, startAngle, endAngle, aspect) {
// TODO: implement aspect parameter
if (color == undefined) {
color = _fgColor;
}
else {
color = _color(color);
}
if (startAngle == undefined) { startAngle = 0; }
if (endAngle == undefined) { endAngle = 2 * Math.PI; }
if (step) {
x = _lastX + x;
y = _lastY + y;
}
_lastX = x;
_lastY = y;
var ctx = GX.ctx();
ctx.strokeStyle = color.rgba();
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.stroke();
};
this.sub_Line = function(sstep, sx, sy, estep, ex, ey, color, style, pattern) {
if (color == undefined) {
if (style == "BF") {
color = _bgColor;
}
else {
color = _fgColor;
}
}
else {
color = _color(color);
}
if (sstep) {
sx = _lastX + sx;
sy = _lastY + sy;
}
if (sx == undefined) {
sx = _lastX;
sy = _lastY;
}
_lastX = sx;
_lastY = sy;
if (estep) {
ex = _lastX + ex;
ey = _lastY + ey;
}
_lastX = ex;
_lastY = ey;
var ctx = GX.ctx();
if (style == "B") {
ctx.strokeStyle = color.rgba();
ctx.beginPath();
ctx.strokeRect(sx, sy, ex-sx, ey-sy)
}
else if (style == "BF") {
ctx.fillStyle = color.rgba();
ctx.beginPath();
ctx.fillRect(sx, sy, ex-sx, ey-sy)
}
else {
ctx.strokeStyle = color.rgba();
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.stroke();
}
};
this.sub_LineInput = async function(values, preventNewline, addQuestionPrompt, prompt) {
await QB.sub_Input(values, preventNewline, addQuestionPrompt, prompt);
}
this.sub_Locate = function(row, col) {
// TODO: implement cursor positioning/display
if (row && row > 0 && row <= _textRows()) {
_locY = row-1;
}
if (col && col > 0 && col <= _textColumns()) {
_locX = col-1;
}
};
this.func_LTrim = function(value) {
return String(value).trimStart();
}
this.func_Mid = function(value, n, len) {
if (len == undefined) {
return String(value).substring(n-1);
}
else {
return String(value).substring(n-1, n+len-1);
}
};
this.sub_Print = async function(args) {
// Print called with no arguments
if (args == undefined || args == null || args.length < 1) {
args = [""];
}
var ctx = GX.ctx();
var preventNewline = (args[args.length-1] == QB.PREVENT_NEWLINE || args[args.length-1] == QB.COLUMN_ADVANCE);
for (var ai = 0; ai < args.length; ai++) {
if (args[ai] == QB.PREVENT_NEWLINE) {
// ignore as we will just concatenate the next arg
}
else if (args[ai] == QB.COLUMN_ADVANCE) {
// TODO: advance to the next column offset
_locX += 14 - _locX % 13;
}
else {
var str = args[ai];
var lines = String(str).split("\n");
for (var i=0; i < lines.length; i++) {
var x = _locX * QB.func__FontWidth();
var y = -1;
// scroll the screen
if (_locY < _textRows()-1) {
y = _locY * QB.func__FontHeight();
//_locY = _locY + 1;
}
else {
//await _printScroll();
y = (_locY) * QB.func__FontHeight();
}
// TODO: check the background opacity mode
// Draw the text background
ctx.beginPath();
ctx.fillStyle = _bgColor.rgba();
ctx.fillRect(x, y, QB.func__FontWidth() * lines[i].length, QB.func__FontHeight());
//GX.drawText(_fntDefault, x, y, lines[i]);
ctx.font = "16px dosvga";
ctx.fillStyle = _fgColor.rgba();
ctx.fillText(lines[i], x, y+QB.func__FontHeight()-6);
_locX += lines[i].length;
if (i < lines.length-1) {
if (_locY < _textRows()-1) {
_locY = _locY + 1;
_locX = 0;
}
else {
await _printScroll();
}
}
}
}
}
if (!preventNewline) {
_locX = 0;
if (_locY < _textRows()-1) {
_locY = _locY + 1;
}
else {
await _printScroll();
}
}
};
/*
this.sub_Print = async function(str) {
if (str == undefined || str == null) {
str = "";
}
var ctx = GX.ctx();
var lines = String(str).split("\n");
for (var i=0; i < lines.length; i++) {
var x = _locX * QB.func__FontWidth();
var y = -1;
// scroll the screen
if (_locY < _textRows()) {
y = _locY * QB.func__FontHeight();
_locY = _locY + 1;
}
else {
await _printScroll();
y = (_locY-1) * QB.func__FontHeight();
}
// TODO: check the background opacity mode
// Draw the text background
ctx.beginPath();
ctx.fillStyle = _bgColor.rgba();
ctx.fillRect(x, y, QB.func__FontWidth() * lines[0].length, QB.func__FontHeight());
GX.drawText(_fntDefault, x, y, lines[i]);
}
_locX = 0;
};
*/
async function _printScroll() {
var img = new Image();
img.src = GX.canvas().toDataURL("image/png");
while (!img.complete) {
await GX.sleep(10);
}
var ctx = GX.ctx();
ctx.beginPath();
ctx.fillStyle = _bgColor.rgba();
ctx.fillRect(0, 0, QB.func__Width(), QB.func__Height());
ctx.drawImage(img, 0, -QB.func__FontHeight());
}
this.sub_PSet = function(sstep, x, y, color) {
if (color == undefined) {
color = _fgColor;
}
else {
color = _color(color);
}
if (sstep) {
x = _lastX + x;
y = _lastY + y;
}
_lastX = x;
_lastY = y;
var ctx = GX.ctx();
ctx.fillStyle = color.rgba();
ctx.beginPath();
ctx.fillRect(x, y, 1, 1);
};
this.func_Right = function(value, n) {
if (value == undefined) {
return "";
}
var s = String(value);
return s.substring(s.length-n, s.length);
};
this.func_RTrim = function(value) {
return String(value).trimEnd();
}
this.func_Rnd = function(n) {
// TODO: implement modifier parameter
return Math.random();
}
this.sub_Screen = async function(mode) {
if (mode == 0) {
GX.sceneCreate(640, 400);
//GX.fontLineSpacing(_fntDefault, 2);
}
else if (mode < 2 || mode == 7 || mode == 13) {
GX.sceneCreate(320, 200);
//GX.fontLineSpacing(_fntDefault, 0);
}
else if (mode == 8) {
GX.sceneCreate(640, 200);
//GX.fontLineSpacing(_fntDefault, 0);
}
else if (mode == 9 || mode == 10) {
GX.sceneCreate(640, 350);
//GX.fontLineSpacing(_fntDefault, 0);
}
else if (mode == 11 || mode == 12) {
GX.sceneCreate(640, 480);
//GX.fontLineSpacing(_fntDefault, 0);
}
else if (mode.width != undefined) {
GX.sceneCreate(mode.width, mode.height);
//GX.fontLineSpacing(_fntDefault, 2);
}
// initialize the graphics
_fgColor = this.func__RGB(255, 255, 255);
_bgColor = this.func__RGB(0, 0, 0);
_lastX = 0;
_lastY = 0;
_locX = 0;
_locY = 0;
_lastKey = null;
_inputMode = false;
};
this.func_Sgn = function(value) {
if (value > 0) { return 1; }
else if (value < 0) { return -1; }
else { return 0; }
};
this.func_Sin = function(value) {
return Math.sin(value);
};
this.sub_Sleep = async function(seconds) {
var elapsed = 0;
var totalWait = Infinity;
if (seconds != undefined) {
totalWait = seconds*1000;
}
_lastKey = null;
while (!_lastKey && elapsed < totalWait) {
await GX.sleep(100);
elapsed += 100;
}
};
this.func_Sqr = function(value) {
return Math.sqrt(value);
};
this.func_Str = function(value) {
return String(value);
};
this.sub_Swap = function(values) {
var temp = values[1];
values[1] = values[0];
values[0] = temp;
};
this.func_Tan = function(value) {
return Math.tan(value);
};
this.func_Timer = function(accuracy) {
// TODO: implement optional accuracy
var midnight = new Date();
midnight.setHours(0, 0, 0, 0);
return ((new Date()).getTime() - midnight.getTime()) / 1000;
};
this.func_Atn = function(value) {
return Math.atan(value);
};
this.func_UBound = function(a, dimension) {
if (dimension == undefined) {
dimension = 1;
}
return a._dimensions[dimension-1];
//return a.length-1;
};
this.func_UCase = function(value) {
return String(value).toUpperCase();
};
this.func_Val = function(value) {
return Number(value);
};
// QBJS-only methods
// ---------------------------------------------------------------------------------
this.func_Fetch = async function(url) {
var response = await fetch(url);
var responseText = await(response.text());
return {
ok: response.ok,
status: response.status,
statusText: response.statusText,
text: responseText
};
};
this.func_FromJSON = function(s) {
return JSON.parse(s);
};
this.func_ToJSON = function(a) {
return JSON.stringify(a);
}
function _init() {
// initialize the fonts
/*if (!_fntDefault) {
_fntDefault = GX.fontCreate("./qb/font.png", 8, 14,
"`1234567890-=~!@#$%^&*()_+\n" +
"qwertyuiop[]\\QWERTYUIOP{}|\n" +
"asdfghjkl;'ASDFGHJKL:\"\n" +
"zxcvbnm,./ZXCVBNM<>?");
}*/
addEventListener("keydown", function(event) {
if (_inputMode) {
event.preventDefault();
}
_lastKey = event.key;
});
addEventListener("keyup", function(event) {
_keyBuffer.push(event.key);
});
};
_init();
}

2134
qb2js.js Normal file

File diff suppressed because it is too large Load diff

2365
tools/qb2js.bas Normal file

File diff suppressed because it is too large Load diff

360
tools/webserver.bas Normal file
View file

@ -0,0 +1,360 @@
' HTTP 1.1 Compliant Web Server
' Author: luke
' Source: https://www.qb64.org/forum/index.php?topic=2052.0
' This program is made available for you to use, modify and distribute it as you wish,
' all under the condition you do not claim original authorship.
'$ExeIcon:'./../gx/resource/gx.ico'
$Console:Only
Option _Explicit
DefLng A-Z
Const MAX_CONNECTIONS = 8
Dim PORT As Integer: PORT = 8080
If _CommandCount > 0 Then
PORT = Val(Command$(1))
End If
Const FALSE = 0
Const TRUE = -1
Dim Shared CRLF As String
CRLF = Chr$(13) + Chr$(10)
Const HTTP_10 = 1
Const HTTP_11 = 11
Const HTTP_GET = 1
Const HTTP_HEAD = 2
Const HTTP_POST = 3
Type connection_t
handle As Long
read_buf As String
http_version As Integer
method As Integer
request_uri As String
End Type
Type http_error_t
code As Integer
message As String
connection As Integer
End Type
Type file_error_t
failed As Integer
code As Integer
End Type
Dim i
Dim num_active_connections
Dim server_handle
Dim Shared Connections(1 To MAX_CONNECTIONS) As connection_t
Dim Shared Http_error_info As http_error_t
Dim Shared File_error_info As file_error_t
On Error GoTo error_handler
server_handle = _OpenHost("TCP/IP:" + LTrim$(Str$(PORT)))
Print "Listening on port:" + Str$(PORT)
Do
If num_active_connections < MAX_CONNECTIONS Then
Dim new_connection
new_connection = _OpenConnection(server_handle)
If new_connection Then
num_active_connections = num_active_connections + 1
For i = 1 To MAX_CONNECTIONS
If Connections(i).handle = 0 Then
Dim empty_connection As connection_t
Connections(i) = empty_connection
Connections(i).handle = new_connection
num_active_connections = num_active_connections - 1
Exit For
End If
Next i
End If
End If
For i = 1 To MAX_CONNECTIONS
If Connections(i).handle Then
Dim buf$
Get #Connections(i).handle, , buf$
If buf$ <> "" Then
Connections(i).read_buf = Connections(i).read_buf + buf$
process_request i
http_error_complete:
End If
End If
Next i
_Limit 240
Loop
error_handler:
If Err = 100 Then 'HTTP error
Print "HTTP error"; Http_error_info.code; Http_error_info.message; " for connection"; Http_error_info.connection
Resume http_error_complete
End If
Print "error"; Err; "on line"; _ErrorLine
End
file_error_handler:
File_error_info.failed = TRUE
File_error_info.code = Err
Resume Next
Sub http_send_status (c, code, message As String)
Dim s$
s$ = "HTTP/1.1" + Str$(code) + " " + message + CRLF
Put #Connections(c).handle, , s$
End Sub
Sub http_send_header (c, header As String, value As String)
Dim s$
s$ = header + ": " + value + CRLF
Put #Connections(c).handle, , s$
End Sub
Sub http_end_headers (c)
Put #Connections(c).handle, , CRLF
End Sub
Sub http_send_body (c, body As String)
Put #Connections(c).handle, , body
End Sub
Sub http_do_get (c)
Dim filepath As String, filedata As String
Dim fh
filepath = get_requested_filesystem_path(c)
Print filepath
If Not _FileExists(filepath) Then http_error 404, "Not Found", c
On Error GoTo file_error_handler
fh = FreeFile
File_error_info.failed = FALSE
Open filepath For Binary As #fh
On Error GoTo error_handler
If File_error_info.failed Then http_error 403, "Permission Denied", c
'Doing this all in one go isn't healthy for a number of reasons (memory usage, starving other clients)
'It should be done in chunks in the main loop
filedata = Space$(LOF(fh))
Get #fh, , filedata
Close #fh
http_send_status c, 200, "OK"
http_send_header c, "Content-Length", LTrim$(Str$(Len(filedata)))
http_send_header c, "Access-Control-Allow-Origin", "true"
http_send_header c, "Connection", "close"
http_end_headers c
http_send_body c, filedata
close_connection c
End Sub
Sub http_do_head (c)
Print "http_do_head"
Dim s$
s$ = "HTTP/1.1 200 OK" + CRLF + CRLF
Put #Connections(c).handle, , s$
End Sub
Sub http_do_post (c)
Print "POST"
Print Connections(c).request_uri
Dim path As String
path = Right$(Connections(c).request_uri, Len(Connections(c).request_uri) - 1)
Dim idx As Integer
idx = _InStrRev(path, "/")
path = Left$(path, idx)
Dim basFile As String
basFile = path + "game.bas"
Dim jsFile As String
jsFile = path + "game.js"
If _FileExists(basFile) Then Kill basFile
Dim fh
fh = FreeFile
Open basFile For Binary As #fh
Put #fh, , Connections(c).read_buf
Close #fh
Shell "qb2js " + basFile + " > " + jsFile
close_connection c
End Sub
Sub close_connection (c)
Close #Connections(c).handle
Connections(c).handle = 0
End Sub
Function get_requested_filesystem_path$ (c)
'7230 5.3 also 3986 for URI
'Origin form only for now
Dim raw_path As String
raw_path = Connections(c).request_uri
If Left$(raw_path, 1) <> "/" Then http_error 400, "Malformed URI", c
Dim hash, questionmark, path_len
hash = InStr(raw_path, "#") 'Clients shouldn't be sending fragments, but we will gracefully ignore them
questionmark = InStr(raw_path, "?")
path_len = Len(raw_path)
If hash > 0 Then path_len = hash - 1
'If questionmark > 0 And questionmark < hash Then path_len = questionmark - 1
If questionmark > 0 Then path_len = questionmark - 1
' Query strings are ignored for now
'Dim cwd As String
'cwd = _CWD$
'$If WIN Then
' 'raw_path = GXSTR_Replace(raw_path, "/", "\")
' cwd = GXSTR_Replace(cwd, "\", "/")
'$End If
'Print "--> " + Left$(raw_path, path_len)
get_requested_filesystem_path = _STARTDIR$ + cannonicalise_path(percent_decode(Left$(raw_path, path_len)))
End Function
Function percent_decode$ (raw_string As String)
Dim final_string As String, hexchars As String
Dim i, c
For i = 1 To Len(raw_string)
c = Asc(raw_string, i)
If c = 37 Then '%
hexchars = Mid$(raw_string, i + 1, 2)
If Len(hexchars) = 2 And InStr("0123456789abcdefABCDEF", Left$(hexchars, 1)) > 0 And InStr("0123456789abcdefABCDEF", Right$(hexchars, 1)) > 0 Then
final_string = final_string + Chr$(Val("&H" + hexchars))
Else
'String ends in something like "%1", or is invalid hex characters
final_string = final_string + "%" + hexchars
End If
i = i + Len(hexchars)
Else
final_string = final_string + Chr$(c)
End If
Next i
percent_decode = final_string
End Function
Function cannonicalise_path$ (raw_path As String)
Dim path As String
ReDim segments(1 To 1) As String
Dim i, uplevels
split raw_path, "/", segments()
For i = UBound(segments) To 1 Step -1
If segments(i) = "." Or segments(i) = "" Then
_Continue
ElseIf segments(i) = ".." Then
uplevels = uplevels + 1
Else
If uplevels = 0 Then
path = "/" + segments(i) + path
Else
uplevels = uplevels - 1
End If
End If
Next i
If path = "" Then path = "/"
'Note: if uplevels > 0 at this point, the path attempted to go above the root
'This is usually a client trying to be naughty
cannonicalise_path = path
End Function
'https://www.qb64.org/forum/index.php?topic=1607.0
Sub split (SplitMeString As String, delim As String, loadMeArray() As String)
Dim curpos As Long, arrpos As Long, LD As Long, dpos As Long 'fix use the Lbound the array already has
curpos = 1: arrpos = LBound(loadMeArray): LD = Len(delim)
dpos = InStr(curpos, SplitMeString, delim)
Do Until dpos = 0
loadMeArray(arrpos) = Mid$(SplitMeString, curpos, dpos - curpos)
arrpos = arrpos + 1
If arrpos > UBound(loadMeArray) Then ReDim _Preserve loadMeArray(LBound(loadMeArray) To UBound(loadMeArray) + 1000) As String
curpos = dpos + LD
dpos = InStr(curpos, SplitMeString, delim)
Loop
loadMeArray(arrpos) = Mid$(SplitMeString, curpos)
ReDim _Preserve loadMeArray(LBound(loadMeArray) To arrpos) As String 'get the ubound correct
End Sub
Sub process_request (c)
Dim eol
Dim l As String
Do
eol = InStr(Connections(c).read_buf, CRLF)
If eol = 0 Then Exit Sub
l = Left$(Connections(c).read_buf, eol - 1)
Connections(c).read_buf = Mid$(Connections(c).read_buf, eol + 2)
If Connections(c).http_version = 0 Then 'First line not yet read
process_start_line c, l
Else
If l = "" Then
'headers complete; act upon request now
Select Case Connections(c).method
Case HTTP_GET
http_do_get c
Case HTTP_POST
http_do_post c
Case HTTP_HEAD
http_do_head c
End Select
Exit Sub
Else
process_header c, l
End If
End If
Loop
End Sub
Sub process_start_line (c, l As String)
'7230 3.1.1
'METHOD uri HTTP/x.y
Dim sp1, sp2
sp1 = InStr(l, " ")
If sp1 = 0 Then http_error 400, "Bad Request", c
'7231 4.3
Select Case Left$(l, sp1 - 1)
Case "GET"
Connections(c).method = HTTP_GET
Case "HEAD"
Connections(c).method = HTTP_HEAD
Case "POST"
Connections(c).method = HTTP_POST
Case Else
http_error 501, "Not Implemented", c
End Select
sp2 = InStr(sp1 + 1, l, " ")
If sp2 = 0 Or sp2 - sp1 = 1 Then http_error 400, "Bad Request", c
Connections(c).request_uri = Mid$(l, sp1 + 1, sp2 - sp1 - 1)
'7230 2.6
If Mid$(l, sp2 + 1, 5) <> "HTTP/" Then
http_error 400, "Bad Request", c
End If
Select Case Mid$(l, sp2 + 6)
Case "1.0"
Connections(c).http_version = HTTP_10
Case "1.1"
Connections(c).http_version = HTTP_11
Case Else
http_error 505, "HTTP Version Not Supported", c
End Select
End Sub
Sub process_header (c, l As String)
'All headers ignored for now
End Sub
Sub http_error (code, message As String, connection)
http_send_status connection, code, message
http_send_header connection, "Content-Length", "0"
http_send_header connection, "Connection", "close"
http_end_headers connection
close_connection connection
Http_error_info.code = code
Http_error_info.message = message
Http_error_info.connection = connection
Error 100
End Sub

2
util/shorty.min.js vendored Normal file
View file

@ -0,0 +1,2 @@
/* shorty.js - by enki - https://enkimute.github.io */
!function(t,i,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define(t,e):i[t]=e()}("Shorty",this,function(){function t(t){this.tokensize=t||10,this.reset(!0)}return t.prototype.reset=function(t){t===!0&&(this.nodes=[{up:0,weight:0}],this.nyt=0,this.nodecount=0),this.data="",this.curpos=0,this.bitCount=7,this.bitChar=0},t.prototype.findNode=function(t){for(var i=this.nodes.length-1;i>0;i--)if("undefined"!=typeof this.nodes[i].symbol&&this.nodes[i].symbol==t)return i;return 0},t.prototype.addNode=function(t){return this.nodecount>=2046?0:(this.nodes[++this.nodecount]={up:this.nyt,symbol:t,weight:1},this.nodes[++this.nodecount]={up:this.nyt,weight:0},this.nodes[this.nyt].weight+=1,this.nyt=this.nodecount,this.nodes[this.nodecount-2].up!=this.nodecount-2&&this.balanceNode(this.nodes[this.nodecount-2].up),this.nodecount-2)},t.prototype.swapNode=function(t,i){var e=this.nodes[t].symbol,s=this.nodes[i].symbol,o=this.nodes[t].weight;this.nodes[t].symbol=s,this.nodes[i].symbol=e,this.nodes[t].weight=this.nodes[i].weight,this.nodes[i].weight=o;for(var h=this.nodes.length-1;h>0;h--)this.nodes[h].up==t?this.nodes[h].up=i:this.nodes[h].up==i&&(this.nodes[h].up=t)},t.prototype.balanceNode=function(t){for(;;){for(var i=t,e=this.nodes[t].weight;i>1&&this.nodes[i-1].weight==e;)i--;if(i!=t&&i!=this.nodes[t].up&&(this.swapNode(i,t),t=i),this.nodes[t].weight++,this.nodes[t].up==t)return;t=this.nodes[t].up}},t.prototype.emitNode=function(t){for(var i=[];0!=t;)i.unshift(t%2),t=this.nodes[t].up;for(var e=0;e<i.length;e++)this.emitBit(i[e])},t.prototype.emitNyt=function(t){this.emitNode(this.nyt);var i=t.length-1;this.tokensize>8&&this.emitBit(8&i),this.tokensize>4&&this.emitBit(4&i),this.tokensize>2&&this.emitBit(2&i),this.tokensize>1&&this.emitBit(1&i);for(var e=0;e<t.length;e++)this.emitByte(t.charCodeAt(e));return this.nyt},t.prototype.readNode=function(){if(0==this.nyt){for(var t=(this.tokensize>8?8*this.readBit():0)+(this.tokensize>4?4*this.readBit():0)+(this.tokensize>2?2*this.readBit():0)+(this.tokensize>1?this.readBit():0)+1,i="";t--;)i+=this.readByte();return i}for(var e=0;;){var s=this.readBit();if(void 0==this.nodes[e].symbol)for(var o=0;;o++)if(this.nodes[o].up==e&&o!=e&&o%2==s){e=o;break}if(void 0!=this.nodes[e].symbol||0==this.nodes[e].weight){if(this.nodes[e].weight)return this.nodes[e].symbol;for(var t=(this.tokensize>8?8*this.readBit():0)+(this.tokensize>4?4*this.readBit():0)+(this.tokensize>2?2*this.readBit():0)+(this.tokensize>1?this.readBit():0)+1,i="";t--;)i+=this.readByte();return i}}},t.prototype.emitBit=function(t){t&&(this.bitChar+=1<<this.bitCount),--this.bitCount<0&&(this.data+=String.fromCharCode(this.bitChar),this.bitCount=7,this.bitChar=0)},t.prototype.emitByte=function(t){for(var i=7;i>=0;i--)this.emitBit(t>>i&1)},t.prototype.readBit=function(){if(this.curpos==8*this.data.length)throw"done";var t=this.data.charCodeAt(this.curpos>>3)>>(7-this.curpos&7)&1;return this.curpos++,t},t.prototype.readByte=function(){res=0;for(var t=0;8>t;t++)res+=(128>>t)*this.readBit();return String.fromCharCode(res)},t.prototype.deflate=function(t){var i,e,s,o=t.length;for(this.reset(),e=0;o>e;e++){if(i=t[e],this.tokensize>1)if(/[a-zA-Z]/.test(i))for(;o>e+1&&i.length<this.tokensize&&/[a-zA-Z]/.test(t[e+1]);)i+=t[++e];else if(/[=\[\],\.:\"'\{\}]/.test(i))for(;o>e+1&&i.length<this.tokensize&&/[=\[\],\.:\"'\{\}]/.test(t[e+1]);)e++,i+=t[e];s=this.findNode(i),s?(this.emitNode(s),this.balanceNode(s)):(this.emitNyt(i),s=this.addNode(i))}if(7!=this.bitCount){var h=this.data.length;this.emitNode(this.nyt),h==this.data.length&&this.emitByte(0)}return this.data},t.prototype.inflate=function(t){this.reset(),this.data=t;var i="";try{for(var e=0;e>=0;e++){var s=this.readNode();i+=s;var o=this.findNode(s);o?this.balanceNode(o):this.addNode(s)}}catch(h){}return i},t});