srecord noeq JGazelleVScriptRunner(SimpleLiveValue<S> lvScript) {
  GazelleVScript parsedScript;
  GazelleVScript.Run run;
  O result;
  SingleComponentPanel scpScriptResult = singleComponentPanel();
  new WeakVar<ImageSurface> isScriptResult;
  new WeakVar<JTextArea> taResult;
  JLabel lblScore = jlabel();

  JComponent scriptAndResultPanel() {
    ret northAndCenter(
      scriptInputField(),
      scpScriptResult = singleComponentPanel()
    );
  }
  
  JComponent scriptInputField() {
    ret withMargin(centerAndEastWithMargin(
      withLabel("Gazelle \"Linear\" Script:", jLiveTextField(lvScript)),
      jPopDownButton_noText(
        "Show Scripting Help", rThread { showText("Gazelle 'Linear Script' Help", GazelleVScript.helpText) })
    ));
  }
  
  void showScriptOutput {
    if (result cast BWImage) {
      showImageResult(result);
    } else if (result cast BufferedImage) {
      showImageResult(result);
    } else if (result cast Animation) {
      showImageResult(result.toImage());
    } else {
      showTextResult(str(result));
    }
  }
  
  void showTextResult(S text) {
    text = shorten(1000, text);
    
    if (isShowing(taResult!)) {
      setTextKeepCaret(taResult!, text);
    } else {
      var ta = jTextArea_noUndo(text);
      scpScriptResult.set(jscroll(ta));
      taResult.set(ta);
    }
  }

  void showImageResult(BufferedImage etc img) {
    if (img == null) ret;
    if (isShowing(isScriptResult!)) {
      isScriptResult->setImage(img);
    } else {
      var is = stdImageSurface();
      is.setImage(img);
      scpScriptResult.set(jscroll_centered(is));
      isScriptResult.set(is);
    }
    
    if (isShowing(lblScore)) {
      S score = "";
      if (sameSize(run.inputImage, img)) {
        double distance = bwDistance(toBWImage(run.inputImage), toBWImage(img));
        score = "Score: " + formatDoubleX(100*(1-distance), 1);
      }
      setText(lblScore, score);
    }
  }
  
  ImageSurface stdImageSurface() {
    ret pixelatedImageSurface().setAutoZoomToDisplay(true).repaintInThread(false);
  }
  
  void parse {
    parsedScript = new GazelleVScript(lvScript!);
  }
  
  void runOn(IBWIntegralImage ii) {
    run = parsedScript.new Run();
    run.setInputImage(ii);
    run.setIntegralImage(ii);
    run.run();
    result = run.result();
  }

  void parseAndRunOn(IBWIntegralImage ii) {
    try {
      parse();
      runOn(ii);
    } catch e {
      result = exceptionToStringShort(e);
      printStackTrace(e);
    }
    showScriptOutput();
  }
}