]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Get the basic parsing pieces and command dispatching in place in the JS SQLTester.
authorstephan <stephan@noemail.net>
Tue, 29 Aug 2023 13:28:36 +0000 (13:28 +0000)
committerstephan <stephan@noemail.net>
Tue, 29 Aug 2023 13:28:36 +0000 (13:28 +0000)
FossilOrigin-Name: 8fcc2a553c1e26734902bbdee0c38183ee22b7b5c75f07405529bb79db34145a

ext/wasm/SQLTester/SQLTester.mjs
ext/wasm/SQLTester/SQLTester.run.mjs
manifest
manifest.uuid

index c295bbd84962a7b5c50d751f9ee9bd7f0ee89910..3af83e9e71d1132107d43e26db080845f825a206 100644 (file)
@@ -54,7 +54,7 @@ const ResultRowMode = newObj({
 
 class SQLTesterException extends globalThis.Error {
   constructor(...args){
-    super(args.join(' '));
+    super(args.join(''));
   }
   isFatal() { return false; }
 }
@@ -74,8 +74,8 @@ class DbException extends SQLTesterException {
 }
 
 class TestScriptFailed extends SQLTesterException {
-  constructor(...args){
-    super(...args);
+  constructor(testScript, ...args){
+    super(testScript.getPutputPrefix(),': ',...args);
   }
   isFatal() { return true; }
 }
@@ -130,26 +130,39 @@ class Outer {
   #verbosity = 0;
   #logger = console.log.bind(console);
 
+  constructor(){
+  }
+
   out(...args){
-    this.#lnBuf.append(...args);
+    if(!this.#lnBuf.length && this.getOutputPrefix ){
+      this.#lnBuf.push(this.getOutputPrefix());
+    }
+    this.#lnBuf.push(...args);
     return this;
   }
   outln(...args){
-    this.#lnBuf.append(...args,'\n');
-    this.logger(this.#lnBuf.join(''));
+    if(!this.#lnBuf.length && this.getOutputPrefix ){
+      this.#lnBuf.push(this.getOutputPrefix());
+    }
+    this.#lnBuf.push(...args,'\n');
+    this.#logger(this.#lnBuf.join(''));
     this.#lnBuf.length = 0;
     return this;
   }
 
-  #verboseN(lvl, argv){
+  setOutputPrefix( func ){
+    this.getOutputPrefix = func;
+    return this;
+  }
+
+  verboseN(lvl, argv){
     if( this.#verbosity>=lvl ){
-      const pre = this.getOutputPrefix ? this.getOutputPrefix() : '';
-      this.outln('VERBOSE ',lvl,' ',pre,': ',...argv);
+      this.outln('VERBOSE ',lvl,': ',...argv);
     }
   }
-  verbose1(...args){ return this.#verboseN(1,args); }
-  verbose2(...args){ return this.#verboseN(2,args); }
-  verbose3(...args){ return this.#verboseN(3,args); }
+  verbose1(...args){ return this.verboseN(1,args); }
+  verbose2(...args){ return this.verboseN(2,args); }
+  verbose3(...args){ return this.verboseN(3,args); }
 
   verbosity(){
     let rc;
@@ -165,11 +178,10 @@ class Outer {
 }/*Outer*/
 
 class SQLTester {
-  SQLTester(){}
 
+  #outer = new Outer().setOutputPrefix( ()=>'SQLTester: ' );
   #aFiles = [];
   #inputBuffer = [];
-  #outputBuffer = [];
   #resultBuffer = [];
   #nullView = "nil";
   #metrics = newObj({
@@ -184,14 +196,42 @@ class SQLTester {
     initialDbName: "test.db",
   });
 
+  constructor(){
+  }
+
+  appendInput(line, addNL){
+    this.#inputBuffer.push(line);
+    if( addNL ) this.#inputBuffer.push('\n');
+  }
+  appendResult(line, addNL){
+    this.#resultBuffer.push(line);
+    if( addNL ) this.#resultBuffer.push('\n');
+  }
+
+  clearInputBuffer(){
+    this.#inputBuffer.length = 0;
+    return this.#inputBuffer;
+  }
+  clearResultBuffer(){
+    this.#resultBuffer.length = 0;
+    return this.#resultBuffer;
+  }
+
+  getInputText(){ return this.#inputBuffer.join(''); }
+  getResultText(){ return this.#resultBuffer.join(''); }
+
+  verbosity(...args){ return this.#outer.verbosity(...args); }
+
 }/*SQLTester*/
 
 class Command {
-  Command(){
+  constructor(){
   }
+
   process(sqlTester,testScript,argv){
     SQLTesterException.toss("process() must be overridden");
   }
+
   argcCheck(testScript,argv,min,max){
     const argc = argv.length-1;
     if(argc<min || (max>=0 && argc>max)){
@@ -203,13 +243,22 @@ class Command {
         testScript.toss(argv[0]," requires at least ",min," arguments.");
       }
     }
+  }
+}
 
+class TestCase extends Command {
+
+  process(tester, script, argv){
+    this.argcCheck(script, argv,1);
+    script.testCaseName(argv[1]);
+    tester.clearResultBuffer();
+    tester.clearInputBuffer();
   }
 }
 
 class Cursor {
   src;
-  buffer = [];
+  sb = [];
   pos = 0;
   //! Current line number. Starts at 0 for internal reasons and will
   // line up with 1-based reality once parsing starts.
@@ -223,68 +272,241 @@ class Cursor {
   //! Peeked-to line number.
   peekedLineNo = 0;
 
+  constructor(){
+  }
+
   //! Restore parsing state to the start of the stream.
   rewind(){
-    this.buffer.length = 0;
-    this.pos = this.lineNo = this.putbackPos =
-      this.putbackLineNo = this.peekedPos = this.peekedLineNo = 0;
+    this.sb.length = this.pos = this.lineNo
+      = this.putbackPos = this.putbackLineNo
+      = this.peekedPos = this.peekedLineNo = 0;
   }
 }
 
+const Rx = newObj({
+  requiredProperties: / REQUIRED_PROPERTIES:[ \t]*(\S.*)\s*$/,
+  scriptModuleName: / SCRIPT_MODULE_NAME:[ \t]*(\S+)\s*$/,
+  mixedModuleName: / ((MIXED_)?MODULE_NAME):[ \t]*(\S+)\s*$/,
+  command: /^--(([a-z-]+)( .*)?)$/
+});
+
 class TestScript {
   #cursor = new Cursor();
-  #verbosity = 0;
   #moduleName = null;
   #filename = null;
   #testCaseName = null;
-  #outer = new Outer();
-  #verboseN(lvl, argv){
-    if( this.#verbosity>=lvl ){
-      this.outln('VERBOSE ',lvl,': ',...argv);
+  #outer = new Outer().setOutputPrefix( ()=>this.getOutputPrefix() );
+
+  constructor(...args){
+    let content, filename;
+    if( 2 == args.length ){
+      filename = args[0];
+      content = args[1];
+    }else{
+      content = args[0];
     }
+    this.#filename = filename;
+    this.#cursor.src = content;
+    this.#outer.outputPrefix = ()=>this.getOutputPrefix();
   }
 
-  verbose1(...args){ return this.#verboseN(1,args); }
-  verbose2(...args){ return this.#verboseN(2,args); }
-  verbose3(...args){ return this.#verboseN(3,args); }
+  testCaseName(){
+    return (0==arguments.length)
+      ? this.#testCaseName : (this.#testCaseName = arguments[0]);
+  }
 
-  TestScript(content){
-    this.cursor.src = content;
-    this.outer.outputPrefix = ()=>this.getOutputPrefix();
+  getOutputPrefix() {
+    let rc =  "["+(this.#moduleName || this.#filename)+"]";
+    if( this.#testCaseName ) rc += "["+this.#testCaseName+"]";
+    return rc + " line "+ this.#cursor.lineNo +" ";
   }
 
-  verbosity(){
-    let rc;
-    if(arguments.length){
-      rc = this.#verbosity;
-      this.#verbosity = arguments[0];
-    }else{
-      rc = this.#verbosity;
+  reset(){
+    this.#testCaseName = null;
+    this.#cursor.rewind();
+    return this;
+  }
+
+  toss(...args){
+    throw new TestScriptFailed(this,...args);
+  }
+
+  #checkForDirective(tester,line){
+    //todo
+  }
+
+  #getCommandArgv(line){
+    const m = Rx.command.exec(line);
+    return m ? m[1].trim().split(/\s+/) : null;
+  }
+
+  run(tester){
+    this.reset();
+    this.#outer.verbosity(tester.verbosity());
+    let line, directive, argv = [];
+    while( null != (line = this.getLine()) ){
+      this.verbose3("input line: ",line);
+      this.#checkForDirective(tester, line);
+      argv = this.#getCommandArgv(line);
+      if( argv ){
+        this.#processCommand(tester, argv);
+        continue;
+      }
+      tester.appendInput(line,true);
+    }
+    return true;
+  }
+
+  #processCommand(tester, argv){
+    this.verbose1("running command: ",argv[0], " ", Util.argvToString(argv));
+    if(this.#outer.verbosity()>1){
+      const input = tester.getInputText();
+      if( !!input ) this.verbose3("Input buffer = ",input);
+    }
+    CommandDispatcher.dispatch(tester, this, argv);
+  }
+
+  getLine(){
+    const cur = this.#cursor;
+    if( cur.pos==cur.src.byteLength ){
+      return null/*EOF*/;
+    }
+    cur.putbackPos = cur.pos;
+    cur.putbackLineNo = cur.lineNo;
+    cur.sb.length = 0;
+    let b = 0, prevB = 0, i = cur.pos;
+    let doBreak = false;
+    let nChar = 0 /* number of bytes in the aChar char */;
+    const end = cur.src.byteLength;
+    for(; i < end && !doBreak; ++i){
+      b = cur.src[i];
+      switch( b ){
+        case 13/*CR*/: continue;
+        case 10/*NL*/:
+          ++cur.lineNo;
+          if(cur.sb.length>0) doBreak = true;
+          // Else it's an empty string
+          break;
+        default:{
+          /* Multi-byte chars need to be gathered up and appended at
+             one time so that we can get them as string objects. */
+          nChar = 1;
+          switch( b & 0xF0 ){
+            case 0xC0: nChar = 2; break;
+            case 0xE0: nChar = 3; break;
+            case 0xF0: nChar = 4; break;
+            default:
+              if( b > 127 ) this.toss("Invalid character (#"+b+").");
+              break;
+          }
+          if( 1==nChar ){
+            cur.sb.push(String.fromCharCode(b));
+          }else{
+            const aChar = [] /* multi-byte char buffer */;
+            for(let x = 0; (x < nChar) && (i+x < end); ++x) aChar[x] = cur.src[i+x];
+            cur.sb.push(
+              Util.utf8Decode( new Uint8Array(aChar) )
+            );
+            i += nChar-1;
+          }
+          break;
+        }
+      }
+    }
+    cur.pos = i;
+    const rv = cur.sb.join('');
+    if( i==cur.src.byteLength && 0==rv.length ){
+      return null /* EOF */;
     }
+    return rv;
+  }/*getLine()*/
+
+  /**
+     Fetches the next line then resets the cursor to its pre-call
+     state. consumePeeked() can be used to consume this peeked line
+     without having to re-parse it.
+  */
+  peekLine(){
+    const cur = this.#cursor;
+    const oldPos = cur.pos;
+    const oldPB = cur.putbackPos;
+    const oldPBL = cur.putbackLineNo;
+    const oldLine = cur.lineNo;
+    const rc = this.getLine();
+    cur.peekedPos = cur.pos;
+    cur.peekedLineNo = cur.lineNo;
+    cur.pos = oldPos;
+    cur.lineNo = oldLine;
+    cur.putbackPos = oldPB;
+    cur.putbackLineNo = oldPBL;
     return rc;
   }
 
-  getOutputPrefix() {
-    const rc =  "["+(this.moduleName || this.filename)+"]";
-    if( this.testCaseName ) rc += "["+this.testCaseName+"]";
-    return rc + " line "+ this.cur.lineNo;
+
+  /**
+     Only valid after calling peekLine() and before calling getLine().
+     This places the cursor to the position it would have been at had
+     the peekLine() had been fetched with getLine().
+  */
+  consumePeeked(){
+    const cur = this.#cursor;
+    cur.pos = cur.peekedPos;
+    cur.lineNo = cur.peekedLineNo;
   }
 
-  toss(...args){
-    Util.toss(this.getOutputPrefix()+":",TestScriptFailed,...args)
+  /**
+     Restores the cursor to the position it had before the previous
+     call to getLine().
+  */
+  putbackLine(){
+    const cur = this.#cursor;
+    cur.pos = cur.putbackPos;
+    cur.lineNo = cur.putbackLineNo;
   }
 
+  verbose1(...args){ return this.#outer.verboseN(1,args); }
+  verbose2(...args){ return this.#outer.verboseN(2,args); }
+  verbose3(...args){ return this.#outer.verboseN(3,args); }
+  verbosity(...args){ return this.#outer.verbosity(...args); }
+
 }/*TestScript*/;
 
+class CommandDispatcher {
+  static map = newObj();
+
+  static getCommandByName(name){
+    let rv = CommandDispatcher.map[name];
+    if( rv ) return rv;
+    switch(name){
+        //todo: map name to Command instance
+      case "testcase": rv = new TestCase(); break;
+    }
+    if( rv ){
+      CommandDispatcher.map[name] = rv;
+    }
+    return rv;
+  }
+
+  static dispatch(tester, testScript, argv){
+    const cmd = CommandDispatcher.getCommandByName(argv[0]);
+    if( !cmd ){
+      toss(UnknownCommand,argv[0],' ',testScript.getOutputPrefix());
+    }
+    cmd.process(tester, testScript, argv);
+  }
+}/*CommandDispatcher*/
 
 const namespace = newObj({
-  SQLTester: new SQLTester(),
+  Command,
   DbException,
   IncompatibleDirective,
+  Outer,
+  SQLTester,
   SQLTesterException,
+  TestScript,
   TestScriptFailed,
-  UnknownCommand
+  UnknownCommand,
+  Util
 });
 
-
 export {namespace as default};
index 0a0f8903b7ee0776e8c6c9d7462137aa05b4945e..4efd068e308605a49cd1bc34e5d751222e90b754 100644 (file)
@@ -1,8 +1,26 @@
 import {default as ns} from './SQLTester.mjs';
 
-const log = (...args)=>{
+const log = function f(...args){
   console.log('SQLTester.run:',...args);
+  return f;
 };
 
+console.log("Loaded",ns);
+const out = function f(...args){ return f.outer.out(...args) };
+out.outer = new ns.Outer();
+out.outer.getOutputPrefix = ()=>'SQLTester.run: ';
+const outln = (...args)=>{ return out.outer.outln(...args) };
 
-log("SQLTester is ostensibly ready.");
+log("ns =",ns);
+out("Hi there. ").outln("SQLTester is ostensibly ready.");
+
+let ts = new ns.TestScript('/foo.test', ns.Util.utf8Encode(`
+# comment line
+select 1;
+--testcase 0.0
+#--result 1
+`));
+
+const sqt = new ns.SQLTester();
+sqt.verbosity(3);
+ts.run(sqt);
index 9e9e27578e04f12b0e589725f052b47fa8b7c960..8e208a8ae17e775da79d7e4cb7ef9e2398d22845 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Init\sbits\sof\sa\sport\sof\sJava's\sSQLTester\sto\sJS.\sFar\sfrom\scomplete.
-D 2023-08-29T11:22:45.711
+C Get\sthe\sbasic\sparsing\spieces\sand\scommand\sdispatching\sin\splace\sin\sthe\sJS\sSQLTester.
+D 2023-08-29T13:28:36.476
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -548,8 +548,8 @@ F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34ce
 F ext/wasm/GNUmakefile 0e362f3fc04eab6628cbe4f1e35f4ab4a200881f6b5f753b27fb45eabeddd9d2
 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576
 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193
-F ext/wasm/SQLTester/SQLTester.mjs a3d6ed049e578b2d0965273016f56293584823760df8ff850868c1f8aea17533
-F ext/wasm/SQLTester/SQLTester.run.mjs 156e174b98e7dd78bf6d1438d32e60d7a647f8b51d8246a3054e5fdfe6042478
+F ext/wasm/SQLTester/SQLTester.mjs 2ea7d09f0c33e509aa3c4ca974be5705f59ddcd2173d4ff2721d7448c65be8bd
+F ext/wasm/SQLTester/SQLTester.run.mjs 478f4d90951591decaa7e1e3fa1729f6ed0043ae4cb48b0a92056b9707d44185
 F ext/wasm/SQLTester/index.html 88d87e3ccbc33e7ab3773a8e48c1172e876951c4be31d1307c3700671262cddf
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b
@@ -2111,11 +2111,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 6c83e31fa96f65b61377c0c801cc32b3c8ca27a0c8442f860364bec258c003cb
-R 94685fe29bdbe21cb1fdfbf40f131ffe
-T *branch * js-tester
-T *sym-js-tester *
-T -sym-trunk * Cancelled\sby\sbranch.
+P 60eec5ceda80c64870713df8e9aeabeef933c007f2010792225a07d5ef36baef
+R 0bdc268c3b78f235b9f50e80fcfe1a69
 U stephan
-Z 952fd010d6a4d240388ad80f3eddac99
+Z 00dd200744de92467554b7f23df0cf79
 # Remove this line to create a well-formed Fossil manifest.
index d92a1595edbd70f5b4de95990869c3048020b408..212a3786b4d7f54ba31f70b82ade4750874997c4 100644 (file)
@@ -1 +1 @@
-60eec5ceda80c64870713df8e9aeabeef933c007f2010792225a07d5ef36baef
\ No newline at end of file
+8fcc2a553c1e26734902bbdee0c38183ee22b7b5c75f07405529bb79db34145a
\ No newline at end of file