@echo off
java -cp ../../zbnfjax/zbnf.jar org.vishia.jztxtcmd.JZtxtcmd %0
:: -t:result/testAll.jz.output.txt  REM instead uses <+:create>
if "%NOPAUSE%" == "" pause
exit /B

==JZcmd==

Class FileSystem = org.vishia.util.FileSystem; ##contains some nice static methods.

String const scriptStringEmpty;

##Example for a script level Java instance which is able to found in the given classpath of the JRE:
 Obj scriptObj = new org.vishia.jztxtcmd.JZtxtcmdUserExample(text);

String scriptString = "scriptStringFirst";
String const scriptStringConst = "scriptStringConst";

String scriptStringSet2 = "string2";

set TEST="Env-TEST";


##This routine creates an output file instead the given standard output.
sub createOutputFile() {
  cd &scriptdir;             ##The script dir should be the current one, independent of operation system's current dir.
                            ##set current dir to create the output file locally to the script file:
  mkdir result;              ##create if not exist. 
  //debug;
  //Obj testFile = File:result; ## "result" is the file path.
  if(File:"result".isDirectory()){ <+out>ok<.+n>; }
  //debug;
  <+:create>result/testAll.jzcmd.out.txt<.+>;           ##write all <+>output<.+> to it.
  <+>All output of executing the <&scriptfile> will be written in this file.
  Refer to the scriptfile which statements are used. search for the parts "==subroutine()=="
  <.+n>
}



main(){
  call createOutputFile();
  
  //Note: comment it to execute special tests:
  call testAll();
  //copy special tests
  //call testText(); 

}



sub testAll(){
  call TestScriptVarAccess.test();
  call testBatch();              
  call testErrorlevel();         
  call testOutputTextIndent();   
  call testDebugErrorMsg();      
  call testErrorToOutput();
  call testThrow();
  call TestErrorSub.test();      
  call TestExceptionInException.method(); 
  call testDataAccess();      
  call testMethodInvoke();    
  call testCalctime();        
  call testVariableDatapath();
  call testText();            
  //call testDebug();
  //call TestResult.exec();
  //call TestCmdWindows.test();
  //call testFile();
  //call testStringBuffer();
  //call testNumeric();
  //call testIf();
  //call testJavaNew();
  //call testContainer();
  //call testEnv();
  //call TestChangeDir.exec();
  //call TestThread.exec();
  //call testIpset();
  <+out>done testAll.jz.cmd<.+n> 
  onerror {
    <+>Test faulty: <&error><.+n>
  }
}



class TestScriptVarAccess
{

  sub test() {
    //debug;
    <+out>TestScriptVarAccess ... <.+>
    <+>
    ==testScriptVarAccess()==
    A scriptvariable is copied locally, changes are effective only locally. 
    But if jzcmd.scriptVariables().scriptString is set, it is effective in the script:
    <.+>
    <+:n>Text in a script variable: scriptString = <&scriptString><.+>
    scriptString = "new text";  ##this has only an effect inside this subroutine:
    <+:n>Changed: scriptString = <&scriptString><.+>
    if(scriptString != "new text"){ <+:n>!!!ERROR TestScriptVarAccess 1 !!! <.+> }
    call otherMethod(cmp = "scriptStringFirst");  ##to check the scriptvariable
    jzcmd.scriptVariables().scriptString = "changed on script level";
    call otherMethod(cmp = "changed on script level");  ##to check the scriptvariable
    <+><.+n>    
    <+out>ok<.+n>
    onerror {
      <+>Test faulty: <&error><.+n>
     <+out>ERROR see output file. <.+n>
   }
  }
  
  sub otherMethod(String cmp) { 
    <+:n>In other method: scriptString = <&scriptString><.+>
    if(scriptString != cmp){ <+:n>!!!ERROR TestScriptVarAccess <&cmp>!!!<.+n> }
  }
  
}


sub testBatch(){

  <+out>testBatch ... <.+>
  <+>
  ==testBatch()==
  This example creates an batchfile in this script and executes it then. It works for MS-Windows. 
  The batch 'myBatch.bat' contains a 'dir' cmd which's output is redirected in a newly created text file 'out.txt'.
  After execution this JZcmd subroutine evaluates the 'out.txt'. 
  It searches the line containing 'myBatch' and shows it.
  <.+>
  currdir = <:><&scriptdir>/..<.>;
  <+>
 ::::'''Variantes of currdir''':
 ::::jzcmd.scriptLevel.currdir = <&jzcmd.scriptLevel.currdir>
 ::::jzcmd.currdir() = <&jzcmd.currdir()>
 ::::jzcmdsub.currdir = <&jzcmdsub.currdir>
 ::::jzcmdsub.currdir() = <&jzcmdsub.currdir()>
 ::::jzcmdsub.sCurrdir = <&jzcmdsub.sCurrdir>
  <.+>
   
  Openfile bat = "myBatch.bat";  ##opens in current directory
  <+bat>
  echo off
  dir >out.txt
  call otherbatch.bat
  <.+close>
  <+:n>executes myBatch.bat, creates out.txt in the current directory ...<.+>
  cmd cmd.exe /C myBatch.bat;
  <+:n>evaluates 'out.txt': <.+>
  Stringjar sdir = FileSystem.readFile(File:out.txt);  ##reads the file.
  if(sdir.seek("myBatch").line().found()){
    String line = sdir.getCurrentPart();
    <+><&line><.+n>
  }
  <+out>ok<.+n>
  onerror {
    <+>Test faulty: <&error><.+n>
    <+out>ERROR see output file. <.+n>
  }
}




sub testErrorlevel()
{
  <+out>testErrorLevel ... <.+>
  <+>                                                     
  ==testErrorlevel()==
  <.+>
  List testErrorlevels;
  testErrorlevels += "1";
  testErrorlevels += "2";
  testErrorlevels += "3";
  testErrorlevels += "5";

  for(level: testErrorlevels){
    ##to procude an errorlevel from a cmd invocation create a batch routine:
    Openfile bat = "error.bat";
    <+bat>exit <&level><.+close>
    cmd cmd.exe /C error.bat;
    throw on errorlevel 5;
    <+>testErrorlevel(): <&jzcmdsub.cmdErrorlevel> < 5 <.+n> 
    if errorlevel 2 { 
      <+>testErrorlevel(): <&jzcmdsub.cmdErrorlevel> >= 2 <.+n> 
    }
    ##
    onerror cmd {
      <+>testErrorlevel(): exeception handling <.+n>
    }
  }
  <+out>ok<.+n>
  onerror {
    <+>Test faulty: <&error><.+n>
    <+out>ERROR see output file. <.+n>
  }
  
}



sub testOutputTextIndent()
{
  <+out>testOutputTextIdent ... <.+>
  <+>
  ==testOutputTextIndent()==
  <.+>
  <+><: >  ## without emtpy line
  First line left aligned
 Second line left aligned
    Third line left aligned
    forth line 2 spaces, <: >
         continue of forth line, one space
  fifth line left aligned.
  <.+>      
  <+out>ok<.+n>
  onerror {
    <+>Test faulty: <&error><.+n>
    <+out>ERROR see output file. <.+n>
  }
}


sub testCausesError(){
  <+>Variable not exists: <&faultyVariable><.+n>
}


sub testDebugErrorMsg(){
  <+out>testDebugErrorMsg ... <.+>
  <+>
  ==testDebugErrorMsg()==
  <.+>
  call testCausesError();
  <+>This statment is skipped on error<.+n>
  onerror{ 
    <+out>ok<.+n>
    <+>onerror block executed for this test: <&error><.+n> 
  }
  
}




##It shows how to handle errors while generating text
sub testErrorToOutput(){
  <+out>testErrorToOutput ... <.+>
  <+>
 :::==testErrorToOutput()==
  <.+>
  errortoOutput;       ##with this statement an error text will be written into the output text with <?? ... error ??>
  <+>
 ::: In this text expression an unknown variable will be accessed. 
 ::: Because 'errorToOutput' was set, the error hint will be produced in this output text: 
 ::: <&unknown>
  <.+n>
  errortoOutput off;   ##with this statement an exception is thrown on the unknown variable, executes the onerror-block
    then.
  <+>
 ::: In this text expression an unknown variable will be accessed too. 
 ::: But because 'errorToOutput off' was set, the execution is aborted and the 'onerror' block is entered: 
 ::: <&unknown>
  <.+n>
  onerror{
    <+>Error: <&error>
      The error with stacktrace: <&jzcmdsub.excStacktraceinfo()>
    <.+n>
  }  
  <+out>ok<.+n>  ##all onerror handlings are expected!
}





sub testThrow() {
  <+out>testThrow ... <.+>
  <+>
 :::==testThrow()==
 :::A throw statement will be execute.
  <.+>
  <+>test throw...<.+n>
  throw "this is the throw text";
  <+>further statement skipped!<.+n>
  onerror{ 
    <+>onerror block: error: <&error> 
 :::Error with stacktrace: <&jzcmdsub.excStacktraceinfo()>
    <.+>
  }  
  <+out>ok<.+n>  ##all onerror handlings are expected!
}


class TestErrorSub 
{
  sub causeError(){
    String xx = nonexistVariable;  //causes error, not caught here.
  }
  
  
  sub test(){
    <+out>testErrorSub ... <.+>
    <+>
    ==TestErrorSub.test()==
    <.+>
    call TestErrorSub.causeError();
    <+out>ok<.+n>;
    onerror {
      <+>TestErrorSub - onerror reached: <&error> <.+n>;
    }
    <+out>ok<.+n>  ##all onerror handlings are expected!
  }

}



class TestExceptionInException
{

  sub methodinner(){
    String xx = nonexistVariable;  //causes error, not caught here.
    onerror{
      <+>TestExceptionInException with errorneous: <&unknownVariable><.+n>
      onerror{
        <+>An inner onerror catches the error in an error handling: <&error><.+n>
      }
    }

    String x2 = nonexistVariable;  //causes error, not caught here.
    onerror{
      <+>Second error is not catched: <&unknownVariable><.+n>
    }
    
  }
  
  
  sub method()
  { <+out>testExceptionInException ... <.+>
    <+>
    ==TestExceptionInException==
    <.+>
    call methodinner();
    onerror{
      <+>An onerror block in an outer routine should be correct: <&error><.+n>
    }
    <+out>ok<.+n>  ##all onerror handlings are expected!
  }  

}





##checks the access to a String in a Java user class:
sub testDataAccess(){
  <+out>testDataAccess ... <.+>
  <+:n>==testDataAccess()==<.+>
  <+:n>Text in a user Java instance: scriptObj.simpleTestString = <&scriptObj.simpeTestString><.+>
  scriptObj.simpeTestString = "new text";  ##sets a String in the users class.
  <+:n>Changed scriptObj.simpeTestString = "new text"; result = <&scriptObj.simpeTestString><.+>
  if(scriptObj.simpeTestString != "new text"){ <+:n>!!!ERROR testDataAccess !!! <.+> }
  //create a new variable inside a container in a Java user class.
  String scriptObj.testVariables.inUserData = "test in users data";   
  <+><.+n>
  <+out>ok<.+n>
  onerror {
    <+>Access ERROR in script: <&error><.+>
    <+out>ERROR see output file. <.+n>
  }
}



##searches the correct method by arguments.
sub testMethodInvoke(){
  <+out>testMethodInvoke ... <.+>
  <+>
  ==testMethodInvoke()==
  <.+>
  Obj obj = java new org.vishia.zcmd.JZcmdUserExample(text);
  //obj.methodTest(5,8);
  //obj.methodTest(5*Math.PI,8+5);

  ##Converts to an absolute file with currdir
  obj.methodTest(File: "myFile");  
  obj.methodTest(File: path/myFile.ext);  

  
  ## invokes methodTest(char)
  obj.methodTest("string argument");
  //obj.methodTest('a');   ##TODO 'a' as Character does not accept as char argument.
  //obj.methodTest('b'-1); ##TODO check and fix it.
  //obj.methodTest(65+32);
  //obj.methodTest(0x61);
  obj.methodTest("a");
  
  ## invokes methodTest(String)
  obj.methodTest(<:>content of <&obj><.>);
  <+out>ok<.+n>
  onerror {
    <+>Access ERROR in script: <&error><.+>
    <+out>ERROR see output file. <.+n>
  }

}



sub testCalctime(){

  <+out>testCalcTime ... <.+>
  <+>
    ==testCalctime()==
  <.+>
  <+>Start...<.+n>
  Num starttime = System.currentTimeMillis();
  Num i = 1;
  do{
    scriptObj.testCalctimeMethodInvocation(i); 
    i = i + 1;
  } while(i < 1000);
  Num endtime = System.currentTimeMillis();
  Num timediff = endtime - starttime;
  <+>Time = <&timediff> ms. <.+n>
  <+out>ok<.+n>
  onerror {
    <+>Access ERROR in script: <&error><.+>
    <+out>ERROR see output file. <.+n>
  }
}






sub testVariableDatapath()
{
  <+out>testVariableDatapath ... <.+>
  <+>
    ==testVariableDatapath()==
  <.+>
  Map map;
  String map.varA = "valA";  ## example for data tree
  String map.varB = "valB";
  String map.varC = "valC";

  String select = "B";       ## any selection input
  
  String path = <:>map.var<&select><.>;  ##build the path with text expression
  String result1 = &(path);              ##access with variable datapath 
  <+><&result1><.+n>

  String datafieldname = <:>var<&select><.>;  ##build a field name for selection
  String result2 = &map.&(datafieldname);     ##access with variable field name.
  <+><&result2><.+n>
  <+out>ok<.+n>
  onerror {
    <+>Access ERROR in script: <&error><.+>
    <+out>ERROR see output file. <.+n>
  }
  

}



sub testText()
{
  <+out>testText ... <.+>
    <+><: >
    ==testText()==
  1. line without indentation. It starts before indentation point in the script.
    2. line without indentation. It starts exactly on indentation point.
      3. line has 2 spaces indentation. It starts after the indentation point.
::::4. line without indentation with :::: in script.      
::::::::5. line without indentation with more :::::: in script.      
::  6. line with 2 spaces indentation because it starts after indentation chars.      
::::::::  7. line with 2 spaces indentation. It starts after the indentation chars though there are longer as
  indentation point in the script.
++++++: 8. line starts with a colon as first char after ++++ indentation.
  =====9. line without indentation
  =+10. line starts with a '+' because first char after another indentation char though before indentation point.
  <.+>
  String text1 = <:>text1 scriptdir = <:scriptdir>
                 may have a second line, same indentation rules like <+>
                   3. line with 2 spaces indentation.
               ====4. line no indentation    
                 <.>;
  
  String text2 = "constant text";
  <+><: >
  assigment text1 = <&text1>
  text2 = <&text2>;
  <.+>
  
  <+out>ok<.+n>
  onerror {
    <+>Access ERROR in script: <&error><.+>
    <+out>ERROR see output file. <.+n>
  }

}