lexer.apl: added a comment about token types

⍝ Token types:
⍝   'W' whitespace
⍝   'L' newline
⍝   'D' diamond (⋄)
⍝   'N' number
⍝   'S' string
⍝   '()[]{}:;←' self (toke type is token value)
⍝   'J' JavaScript literal («»)
⍝   'X' symbol
tds←⊃( ⍝ token definitions
  ('W' '^([ \t]+|[⍝\#].*)+')
  ('L' '^[\n\r]+')
  ('D' '^[◇⋄]')
  ('N' '^(¯?(0x[0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞)([Jj]¯?(0x[0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞))?)')
  ('S' '^((''[^'']*'')+|("[^"]*"))+')
  ('.' '^[\(\)\[\]\{\}:;←]')
  ('J' '^«[^»]*»')
  ('X' '^(⎕?[A-Za-z_][0-9A-Za-z_]*|⍺⍺|⍵⍵|∇∇|[^¯''":«»])')
)

tpairs←{             ⍝ returns a vector of (type,value) pairs
  0=⍴⍵:⊂'E' ''       ⍝ empty input: only an "end of file" token
  m←tds[;1]⎕RE¨⊂⍵    ⍝ try to match the regexes against ⍵
  i←(×≢¨m)⍳1         ⍝ index of the first regex that matched
  t←tds[i;0]         ⍝ token type
  v←↑(↑m[i])[1]      ⍝ token value
  t←(t='.')⌷t(↑v)    ⍝ '.' means type should be the same as value
  (⊂t v),∇(≢v)↓⍵     ⍝ chop off current token and recur
}

tokenize←{
  t←⊃tpairs⍵         ⍝ col 0: token type, col 1: token value
  e←+\≢¨t[;1]        ⍝ compute end offsets
  t←(t,(¯1↓0,e)),e   ⍝ cols 2 and 3: start and end offset
  t←(t[;0]≠'W')⌿t    ⍝ remove whitespace and comments

  ⍝ remove newlines that are inside () or [] but not inside {}
  s←'{' ⍝ stack
  t←({
    ⍵∊'([{':1⊣s←s,⍵
    ⍵∊')]}':1⊣s←¯1↓s
    ⍵='L':'{'=↑¯1↑s
    1
  }¨t[;0])⌿t
}


Issue #31: fix primes demo so that it doesn't rely on 0-residue.

#!/usr/bin/env apl
⍝ Sieve of Eratosthenes
⎕ ← (1=+⌿0=A∘.∣A)/A←2↓⍳100

#!/usr/bin/env apl
⍝ Sieve of Eratosthenes
⎕ ← (2=+⌿0=A∘.∣A)/A←⍳100


Fixed JS.ChainedSelect

Fixed JS.ChainedSelect

:class ChainedSelect : #._html.select
⍝ Description:: Implements a hierarchical select across multiple levels
⍝ Constructor:: [options [[selected] [[disabled] [prompt]]]]
⍝ options   - 3 column matrix of displayed[;1] and returned[;2] values and their level/depth [;3]
⍝ selected  - Boolean or integer array indicating pre-selected option (consciously NOT supporting multiple selects)
⍝ disabled  - Boolean or integer array indicating disabled options(s)
⍝ prompt    - first item to display (has no value) (default '[Select]')
⍝
⍝ Public Fields::
⍝ Options   - 3 column matrix of displayed[;1] and returned[;2] values and their level/depth [;3]
⍝ Selected  - Boolean or integer array indicating pre-selected options
⍝ Disabled  - Boolean or integer array indicating disabled options(s)
⍝ Prompt      - first item to display (has no value) (default '[Select]')
⍝

    :field public Options←0 3⍴⊂''     ⍝ vector or matrix [;1] display, [;2] value [;3]=depth
    :field public Selected←⍬          ⍝ either Boolean integer vector indicating
    :field public Prompt←'[Select]'   ⍝ character vector "Prompt" choice - the first choice on the list and does not have a value
    :field public Disabled←⍬          ⍝ either Boolean integer vector indicating
    :field public Horizontal←1        ⍝ Orientation (Hz=0 implies one physical row per "sub-select" - within the same virtual row)
    :field public Labels←⍬            ⍝ Labels[n] is the label for the select on level n (useful if Horizontal=0)

    ∇ make
      :Access public
      :Implements constructor
    ∇

    ∇ make1 args;attr
      :Access public
      :Implements constructor
      args←eis args
      :If (|≡args)∊0 1 2
      :AndIf ~0∊⍴⊃args
          args←,⊂args
      :EndIf
      (Options Selected Disabled Prompt)←args defaultArgs Options Selected Disabled Prompt
    ∇

    parents←{0∊⍴⍵:⍬ ⋄ (⍴⍵),∇({+/∨\⍵<1↑⍵}⌽⍵)↑⍵}

    ∇ r←Render;opts;sel;dsp;rc;rtab
      :Access public
      Use'JQuery'
      Use'⍎/PlugIns/Other/chained/jquery.chained.min.js'
      Use'⍕/PlugIns/Other/chained/ChainedSelect.css'
      SetUse

    ⍝ nested/chained selects!

    ⍝ the control with the id given for this control will be a hidden input!
    ⍝ downside: visual attributes set on the control (in MS) will have no effect!
      value←''  ⍝ consider "Selected" for sensible value. if multiple, build a comma/blank-separated list?
      SetInputName
      id∆←id
      r←((id)('name="',id,'" value="',(1↓∊(⍕¨Selected),¨','),'"')'type=hidden'Add _.input).Render
      →(0∊2↑⍴Options)/0
      Options[;3]←{(∪⍵)[⍋∪⍵]⍳⍵}Options[;3] ⍝ make sure levels are consistently numbered
      Options[;2]←(↓(⍳1↑⍴Options),[1.5]⊂Options){
          (i o)←⍺                   ⍝ index and complete list of options
          0∊⍴o:⍬
          (0<1↑⍴,⍵)∧i=o[;2]⍳⊂⍵:⍵    ⍝ if current row has an id and has the first match, we are fine
          'opt',(⍕i),'_',⍵          ⍝ otherwise prefix with row no...
      }¨Options[;2]
      maxdepth←1↑⌈/Options[;3]
      Options←AddClasses Options maxdepth
      js←((ScriptFollows #.Strings.subst'%maxdepth%'(⍕maxdepth))#.Strings.subst'%prefix%'id∆),'$(function() {'
            ⍝ function changedChainedSelect(event)
            ⍝ {
            ⍝   if($(event.target).is(":visible"))
            ⍝   {
            ⍝     var lvl = $(event.target).data("lvl");
            ⍝     var dat = $(event.target).find('option:selected').data();
            ⍝     var ToHide = Array();
            ⍝     if (dat !== undefined)
            ⍝     { if (dat.hasOwnProperty("hidelevels"))
            ⍝       {
            ⍝         var ToHide = (dat.hidelevels).toString().split(",");
            ⍝       }
            ⍝       if (dat.hasOwnProperty("finalitem"))
            ⍝       {
            ⍝         var newval = $(event.target).find('option:selected').val();
            ⍝         $("#%prefix%").val(newval);
            ⍝         $("#%prefix%").trigger("change");
            ⍝       }
            ⍝     }
            ⍝
            ⍝     for (var i=lvl+1; i<=%maxdepth%; i++) {
            ⍝       var daId = '%prefix%_' + (i);
            ⍝       if (-1==ToHide.indexOf(i.toString())) {
            ⍝         $(".cs" + daId).show();
            ⍝       } else {
            ⍝         $(".cs" + daId).hide();
            ⍝       }
            ⍝     }
            ⍝   }
            ⍝ }
      SelectedOnAllLevels←∊(⊂Options[;3]){parents ⍵↑⍺}¨Selected
      rtab←(2,⍴uo←∪Options[;3])⍴⊂''
      :For level :In uo
          z←Options[;3]=level
          opts←z⌿Options
          sl←({⍵/⍳⍴⍵}z)∊SelectedOnAllLevels
          di←({⍵/⍳⍴⍵}z)∊Disabled
          slid←id∆,'_',⍕level
          dsp←(((0=+/sl)∧level>1)/' style="display:none"'),'>'  ⍝ set display-property and close tag
          :If 0<⍴lvlb←level{2>≡⍵:'' ⋄ ⍺>⍴⍵:'' ⋄ ⍺⊃⍵}Labels
              rtab[1;uo⍳level]←⊂'<label class="cs',slid,'" for="',slid,'"',dsp,lvlb,''
          :EndIf
          rc←'<select class="cs',slid,'" id="',slid,'" name="',slid,'" data-lvl="',(⍕level),'"'
          rc,←dsp
          rc,←BuildOptions opts sl di
          rc,←'</select>'
          rtab[2;uo⍳level]←⊂rc
          js,←'$("#',slid,'").on("change" , changedChainedSelect);'
          :If level>1
              js,←'$("#',slid,'").chained("#',id∆,'_',(⍕level-1),'"); '
          :EndIf
      :EndFor
      js,←'});'
      rtab←(⌽1,1<⍴∪∊rtab[1;])⌿rtab
      :If ~Horizontal ⋄ rtab←⍉rtab ⋄ :EndIf
      r,←'
',(∊(⊂'
'),((⊂''),¨rtab,¨⊂'	'),⊂'
'),'
'
      r,←(Add _.Script js).Render
      r,←RenderHandlers
    ∇

    ∇ opts←{id}AddClasses(opts maxdepth);z;j
      opts←6↑[2]opts   ⍝ adds 4th&5th&6th col to Options-Matrix with classnames (for chainedselect: composed of ids from parent-selections [;4] and indicator if lower levels coming [;5] andif this is "final(ly an) item" [;6]
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      :For z :In {⍵/⍳⍴⍵}opts[;3]=⌊/opts[;3]
          j←((z<⍳1↑⍴opts)∧opts[;3]≤opts[z;3])⍳1  ⍝ first entry of a same (or higher level) marks end of lower-level entries
          opts[z;4]←⊂id
          op5←''
          :If z=j-1   ⍝ no more items below
          :AndIf opts[z;3]<maxdepth  ⍝ and not at lowest level...
              op5←¯1↓∊(⍕¨opts[z;3]↓⍳maxdepth),¨','  ⍝ remember that we need to hide these levels...!
          :EndIf
          opts[z;5]←⊂op5
          opts[z;6]←(z=j-1)∨opts[z;3]=maxdepth   ⍝ nothing below or last level anyway...
          opts[z↓⍳j-1;]←(2⊃opts[z;])AddClasses opts[z↓⍳j-1;]maxdepth
      :EndFor
    ∇

    ∇ r←BuildOptions(opts sel dis);v
      r←''
      :If ~0∊⍴opts
          opts←eis opts
          :If 1=⍴⍴opts
              opts←opts,⍪opts
          :EndIf
      :EndIf

      v←⍳⍬⍴⍴opts
      (sel dis)←v∘∊∘{∧/⍵∊0 1:⍵/⍳⍴⍵ ⋄ ⍵}∘,¨sel dis

      :If ~0∊⍴Prompt
          r,←('disabled="disabled" selected="selected"'New #._html.option Prompt).Render
      :EndIf
      r,←FormatOptions(opts sel dis)
    ∇

    ∇ r←FormatOptions(opts sel dis);o;s;d
      :Access Public Shared
      r←''
      :If 1=⍴⍴opts ⋄ opts←opts,⍪opts ⋄ :EndIf
      :For o s d :InEach (↓opts)sel dis
          r,←'<option',({⍵ ine' value="',(HtmlSafeText ⍵),'"'}2⊃o),(s/' selected="selected"'),(d/' disabled="disabled"'),(' class="'{0=1↑⍴,⍵:'' ⋄ ⍺,⍵,'"'}4⊃o)
          r,←(6⊃o)/' data-finalitem="1"'
          r,←(' data-hidelevels="'{0=1↑⍴,⍵:'' ⋄ ⍺,⍵,'"'}⍕5⊃o),'>',(#.HtmlUtils.HtmlSafeText 1⊃o),'</option>'
      :EndFor
    ∇

⍝    ∇ r←{selector}ReplaceOptions args;sel;opts;dis
⍝ Not (yet) implemented...do we need that?
⍝     Replaces select elements options - used by callback functions
⍝ Ex: r←Execute ReplaceOptions ('New Option 1' 'New Option 2') 1
⍝     arg = options [[selected] [disabled]]
⍝      :Access public
⍝      :If 0=⎕NC'selector' ⋄ selector←'#',id ⋄ :EndIf
⍝      args←eis args
⍝      :If (|≡args)∊0 1 2
⍝      :AndIf ~0∊⍴⊃args
⍝          args←,⊂args
⍝      :EndIf
⍝      (opts sel dis)←args defaultArgs Options ⍬ ⍬
⍝      r←selector #.JQ.Replace BuildOptions(opts sel dis)
⍝    ∇
:endclass

:class ChainedSelect : #._html.select
⍝ Description:: Implements a hierarchical select across multiple levels
⍝ Constructor:: [options [[selected] [[disabled] [prompt]]]]
⍝ options   - 3 column matrix of displayed[;1] and returned[;2] values and their level/depth [;3]
⍝ selected  - Boolean or integer array indicating pre-selected option (consciously NOT supporting multiple selects)
⍝ disabled  - Boolean or integer array indicating disabled options(s)
⍝ prompt    - first item to display (has no value) (default '[Select]')
⍝
⍝ Public Fields::
⍝ Options   - 3 column matrix of displayed[;1] and returned[;2] values and their level/depth [;3]
⍝ Selected  - Boolean or integer array indicating pre-selected options
⍝ Disabled  - Boolean or integer array indicating disabled options(s)
⍝ Prompt      - first item to display (has no value) (default '[Select]')
⍝

    :field public Options←0 3⍴⊂''     ⍝ vector or matrix [;1] display, [;2] value [;3]=depth
    :field public Selected←⍬          ⍝ either Boolean integer vector indicating
    :field public Prompt←'[Select]'   ⍝ character vector "Prompt" choice - the first choice on the list and does not have a value
    :field public Disabled←⍬          ⍝ either Boolean integer vector indicating
    :field public Horizontal←1        ⍝ Orientation (Hz=0 implies one physical row per "sub-select" - within the same virtual row)
    :field public Labels←⍬            ⍝ Labels[n] is the label for the select on level n (useful if Horizontal=0)

    ∇ make
      :Access public
      :Implements constructor
    ∇

    ∇ make1 args;attr
      :Access public
      :Implements constructor
      args←eis args
      :If (|≡args)∊0 1 2
      :AndIf ~0∊⍴⊃args
          args←,⊂args
      :EndIf
      (Options Selected Disabled Prompt)←args defaultArgs Options Selected Disabled Prompt
    ∇

    parents←{0∊⍴⍵:⍬ ⋄ (⍴⍵),∇({+/∨\⍵<1↑⍵}⌽⍵)↑⍵}

    ∇ r←Render;opts;sel;dsp;rc;rtab
      :Access public
      Use'JQuery'
      Use'⍎/PlugIns/Other/chained/jquery.chained.min.js'
      Use'⍕/PlugIns/Other/chained/ChainedSelect.css'
      SetUse

    ⍝ nested/chained selects!

    ⍝ the control with the id given for this control will be a hidden input!
    ⍝ downside: visual attributes set on the control (in MS) will have no effect!
      value←''  ⍝ consider "Selected" for sensible value. if multiple, build a comma/blank-separated list?
      SetInputName
      id∆←id
      r←((id)('name="',id,'" value="',(1↓∊(⍕¨Selected),¨','),'"')'type=hidden'Add _.input).Render
      →(0∊2↑⍴Options)/0
      Options[;3]←{(∪⍵)[⍋∪⍵]⍳⍵}Options[;3] ⍝ make sure levels are consistently numbered
      Options[;2]←(↓(⍳1↑⍴Options),[1.5]⊂Options){
          (i o)←⍺                   ⍝ index and complete list of options
          0∊⍴o:⍬
          (0<1↑⍴,⍵)∧i=o[;2]⍳⊂⍵:⍵    ⍝ if current row has an id and has the first match, we are fine
          'opt',(⍕i),'_',⍵          ⍝ otherwise prefix with row no...
      }¨Options[;2]
      maxdepth←1↑⌈/Options[;3]
      Options←AddClasses Options maxdepth
      js←((ScriptFollows #.Strings.subst'%maxdepth%'(⍕maxdepth))#.Strings.subst'%prefix%'id∆),'$(function() {'
            ⍝ function changedChainedSelect(event)
            ⍝ {
            ⍝   if($(event.target).is(":visible"))
            ⍝   {
            ⍝     var lvl = $(event.target).data("lvl");
            ⍝     var dat = $(event.target).find('option:selected').data();
            ⍝     var ToHide = Array();
            ⍝     if (dat !== undefined)
            ⍝     { if (dat.hasOwnProperty("hidelevels"))
            ⍝       {
            ⍝         var ToHide = (dat.hidelevels).toString().split(",");
            ⍝       }
            ⍝       if (dat.hasOwnProperty("finalitem"))
            ⍝       {
            ⍝         var newval = $(event.target).find('option:selected').val();
            ⍝         $("#%prefix%").val(newval);
            ⍝         $("#%prefix%").trigger("change");
            ⍝       }
            ⍝     }
            ⍝
            ⍝     for (var i=lvl+1; i<=%maxdepth%; i++) {
            ⍝       var daId = '%prefix%_' + (i);
            ⍝       if (-1==ToHide.indexOf(i.toString())) {
            ⍝         $(".cs" + daId).show();
            ⍝       } else {
            ⍝         $(".cs" + daId).hide();
            ⍝       }
            ⍝     }
            ⍝   }
            ⍝ }
      SelectedOnAllLevels←∊(⊂Options[;3]){parents ⍵↑⍺}¨Selected
      rtab←(2,⍴uo←∪Options[;3])⍴⊂''
      :For level :In uo
          z←Options[;3]=level
          opts←z⌿Options
          sl←({⍵/⍳⍴⍵}z)∊SelectedOnAllLevels
          di←({⍵/⍳⍴⍵}z)∊Disabled
          slid←id∆,'_',⍕level
          dsp←(((0=+/sl)∧level>1)/' style="display:none"'),'>'  ⍝ set display-property and close tag
          :If 0<⍴lvlb←level{2>≡⍵:'' ⋄ ⍺>⍴⍵:'' ⋄ ⍺⊃⍵}Labels
              rtab[1;uo⍳level]←⊂'<label class="cs',slid,'" for="',slid,'"',dsp,lvlb,''
          :EndIf
          rc←'<select class="cs',slid,'" id="',slid,'" name="',slid,'" data-lvl="',(⍕level),'"'
          rc,←dsp
          rc,←BuildOptions opts sl di
          rc,←'</select>'
          rtab[2;uo⍳level]←⊂rc
          js,←'$("#',slid,'").on("change" , changedChainedSelect);'
          :If level>1
              js,←'$("#',slid,'").chained("#',id∆,'_',(⍕level-1),'"); '
          :EndIf
      :EndFor
      js,←'});'
      rtab←(⌽1,1<⍴∪∊rtab[1;])⌿rtab
      :If ~Horizontal ⋄ rtab←⍉rtab ⋄ :EndIf
      r,←'
',(∊(⊂'
'),(⊂''),¨(rtab,¨⊂'	'),⊂'
'),'
'
      r,←(Add _.Script js).Render
      r,←RenderHandlers
    ∇

    ∇ opts←{id}AddClasses(opts maxdepth);z;j
      opts←6↑[2]opts   ⍝ adds 4th&5th&6th col to Options-Matrix with classnames (for chainedselect: composed of ids from parent-selections [;4] and indicator if lower levels coming [;5] andif this is "final(ly an) item" [;6]
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      :For z :In {⍵/⍳⍴⍵}opts[;3]=⌊/opts[;3]
          j←((z<⍳1↑⍴opts)∧opts[;3]≤opts[z;3])⍳1  ⍝ first entry of a same (or higher level) marks end of lower-level entries
          opts[z;4]←⊂id
          op5←''
          :If z=j-1   ⍝ no more items below
          :AndIf opts[z;3]<maxdepth  ⍝ and not at lowest level...
              op5←¯1↓∊(⍕¨opts[z;3]↓⍳maxdepth),¨','  ⍝ remember that we need to hide these levels...!
          :EndIf
          opts[z;5]←⊂op5
          opts[z;6]←(z=j-1)∨opts[z;3]=maxdepth   ⍝ nothing below or last level anyway...
          opts[z↓⍳j-1;]←(2⊃opts[z;])AddClasses opts[z↓⍳j-1;]maxdepth
      :EndFor
    ∇

    ∇ r←BuildOptions(opts sel dis);v
      r←''
      :If ~0∊⍴opts
          opts←eis opts
          :If 1=⍴⍴opts
              opts←opts,⍪opts
          :EndIf
      :EndIf

      v←⍳⍬⍴⍴opts
      (sel dis)←v∘∊∘{∧/⍵∊0 1:⍵/⍳⍴⍵ ⋄ ⍵}∘,¨sel dis

      :If ~0∊⍴Prompt
          r,←('disabled="disabled" selected="selected"'New #._html.option Prompt).Render
      :EndIf
      r,←FormatOptions(opts sel dis)
    ∇

    ∇ r←FormatOptions(opts sel dis);o;s;d
      :Access Public Shared
      r←''
      :If 1=⍴⍴opts ⋄ opts←opts,⍪opts ⋄ :EndIf
      :For o s d :InEach (↓opts)sel dis
          r,←'<option',({⍵ ine' value="',(HtmlSafeText ⍵),'"'}2⊃o),(s/' selected="selected"'),(d/' disabled="disabled"'),(' class="'{0=1↑⍴,⍵:'' ⋄ ⍺,⍵,'"'}4⊃o)
          r,←(6⊃o)/' data-finalitem="1"'
          r,←(' data-hidelevels="'{0=1↑⍴,⍵:'' ⋄ ⍺,⍵,'"'}⍕5⊃o),'>',(1⊃o),'</option>'
      :EndFor
    ∇

⍝    ∇ r←{selector}ReplaceOptions args;sel;opts;dis
⍝ Not (yet) implemented...do we need that?
⍝     Replaces select elements options - used by callback functions
⍝ Ex: r←Execute ReplaceOptions ('New Option 1' 'New Option 2') 1
⍝     arg = options [[selected] [disabled]]
⍝      :Access public
⍝      :If 0=⎕NC'selector' ⋄ selector←'#',id ⋄ :EndIf
⍝      args←eis args
⍝      :If (|≡args)∊0 1 2
⍝      :AndIf ~0∊⍴⊃args
⍝          args←,⊂args
⍝      :EndIf
⍝      (opts sel dis)←args defaultArgs Options ⍬ ⍬
⍝      r←selector #.JQ.Replace BuildOptions(opts sel dis)
⍝    ∇
:endclass


Add file reading and bitwise operations to TAIL-prelude

$dot ← {
  WA ← (1↓⍴⍵),⍴⍺
  KA ← (⊃⍴⍴⍺)-1
  VA ← ⍳ ⊃ ⍴WA
  ZA ← (KA⌽¯1↓VA),¯1↑VA
  TA ← ZA⍉WA⍴⍺    ⍝ Replicate, transpose
  WB ← (¯1↓⍴⍺),⍴⍵
  KB ← ⊃ ⍴⍴⍺
  VB ← ⍳ ⊃ ⍴WB
  ZB0 ← (-KB) ↓ KB ⌽ ⍳(⊃⍴VB)
  ZB ← (¯1↓(⍳ KB)),ZB0,KB
  TB ← ZB⍉WB⍴⍵    ⍝ Replicate, transpose
  ⍺⍺ / TA ⍵⍵ TB   ⍝ Compute the result
}

$out ← {
  A ← ⍺
  B ← ⍵
  T ← (⊃(⍴⍴A))⌽⍳⊃⍴(⍴B),⍴A
  x ← T ⍉ ((⍴B),(⍴A)) ⍴ A
  y ← ((⍴A),(⍴B)) ⍴ B
  x ⍺⍺ y
}

$slashbar ← {
  ⍉ ⍺⍺ / ⍉ ⍵
}

$log ← {
  ⍝ Dyadic logarithm
  (⍟⍵)÷⍟⍺
}

ReadCSVDouble ← ⎕ReadDoubleVecFile
ReadCSVInt ← ⎕ReadIntVecFile
xor ← ⎕INT32XOR
and ← ⎕INT32AND
sll ← ⎕INT32SHL
srl ← ⎕INT32SHR
testBit ← { 0≠⍵ and 1 sll (⍺-1) }


Pushing index.dyalog WIP for Brian to debug...

:Class index : MiPageTemplate

    ∇ Compose;left;mid;right;sp;vp
      :Access public

      Use'ejTreeView' 'ejTab' ⍝ May get added by callbacks

      div←{⍵ New _html.div}                                             ⍝ Make a div
      textspan←{'.textspan'New _html.span ⍵}                            ⍝ And a span
      horz←{⍺←⊣ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1}  ⍝ Horizontal StackPanel

      Add _.StyleSheet'/Styles/homepage.css'

      (left mid right)←div¨'leftBar' 'midBar' 'rightBar'

      sp←'mainSP'horz left mid right
      sp.Items[1 3].style←⊂'width: 150px; max-height: 300px;'
      sp.Items[2].style←⊂'margin: 5px;'
      sp.style←'height: 250px;'

      vp←Add _.StackPanel sp(div'divSampleTab')
      vp.style←'width:100%'

      PopulateLeft left
      PopulateMid mid
      PopulateRight right
    ∇

    ∇ PopulateLeft thediv;class;items;name;names;ref;tv;vp
        ⍝ Populate the Left Bar

      items←1 2 2 2,⍪'Apps' 'one' 'two' 'three'

      :For (name ref) :In ('Base HTML'_html)('Wrapped HTML'_HTML)('Dyalog Controls'_DC)('JQueryUI'_JQ)('SyncFusion'_SF)
          names←{({#.HtmlElement=⊃⊃⌽⎕CLASS ⍵}¨⍵)/⍵}ref.(⍎¨⎕NL ¯9.4)
          class←2↓⍕ref
          items⍪←(1,(≢names)/2),⍪(⊂name),(3+⍴class)↓¨⍕¨names
      :EndFor

      thediv.Add textspan'Samples'
      tv←thediv.Add _.ejTreeView(Samples←items)
      tv.style←'max-height: 300px'
      tv.On'nodeSelect' 'onSelectSample'('node' 'eval' 'argument.id')
    ∇

    ∇ PopulateRight thediv;pages;ul
        ⍝ Populate the Right Bar

      thediv.Add textspan'Documentation'
      pages←1 2⍴'SyncFusion APIs' 'http://helpjs.syncfusion.com/js/api/ejaccordion'
      pages⍪←'SyncFusion Demos' 'http://js.syncfusion.com/demos/web/'
      pages⍪←'JQueryUI' 'https://jqueryui.com/'
      pages⍪←2 2⍴'HTML' 'http://www.w3schools.com/html/' 'CSS' 'http://www.w3schools.com/css/'
      pages[;2]←↓pages[;,2],⊂'target=_blank"'
      ul←thediv.Add _.Ul pages
      ul.style←'font-size: 10px;'

      thediv.Add _.br

      thediv.Add textspan'Resources'
      pages←1 2⍴'MiServer Forum' 'http://www.dyalog.com/forum/viewforum.php?f=34'
      pages⍪←'Blog' 'http://www.dyalog.com/blog/category/miserver/'
      pages⍪←'GitHub Repo' 'https://github.com/Dyalog/MiServer'
      pages⍪←'dyalog.com' 'http://www.dyalog.com'
      pages[;2]←↓pages[;,2],⊂'target=_blank"'
      ul←thediv.Add _.Ul pages
      ul.style←'font-size: 10px;'
    ∇

    ∇ PopulateMid mid;btns;size

      mid.Add¨3⍴_.br
      btns←LinkButton'Download MiServer' '/styles/images/download-zone.png' '/download...'
      btns,←LinkButton'Read More' '/styles/images/support.png' '/readmore...'
      mid.Add horz btns
    ∇

    ∇ r←LinkButton(label image link);a;d;size

      d←'.linkbutton'New _.div
      size←'height: 32px; width: 32px;'
      'src' 'style'(d.Add _.img).Set image size
      d.Add textspan label
      r←New _.A d link
    ∇

    ∇ r←onSelectSample;node;section;sp;tab;text
      :Access Public
        ⍝ When a sample is selected, call this
      node←⊃2⊃⎕VFI{((+\⍵='_')⍳2)↓⍵}⊃_PageData.node
      section←⊃Samples[1+node-(⌽node↑Samples[;1])⍳1;2]

      text←{New _.p ⍵}¨('<pre>:Class ',node,' ...</pre>')'<body>'
      text←'Tab1' 'Tab2'
      tab←New _SF.ejTab(('Source' 'HTML')text)
      tab.IsURL←0 0
      sp←New horz tab(div'divRendered')
      r←'#divSampleTab'Replace sp
    ∇

:EndClass

:Class index : MiPageTemplate

    ∇ Compose;left;mid;right;sp;vp
      :Access public

      div←{⍵ New _html.div}                                             ⍝ Make a div
      textspan←{'.textspan'New _html.span ⍵}                            ⍝ And a span
      horz←{⍺←⊣ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1}  ⍝ Horizontal StackPanel

      Add _.StyleSheet'/Styles/homepage.css'

      (left mid right)←div¨'leftBar' 'midBar' 'rightBar'

      sp←'mainSP'horz left mid right
      sp.Items[1 3].style←⊂'width: 150px; max-height: 300px;'
      sp.Items[2].style←⊂'margin: 5px;'
      sp.style←'height: 250px;'

      vp←Add _.StackPanel sp(div'divSampleTab')
      vp.style←'width:100%'

      PopulateLeft left
      PopulateMid mid
      PopulateRight right
    ∇

    ∇ PopulateLeft thediv;class;items;name;names;ref;tv;vp
        ⍝ Populate the Left Bar

      items←1 2 2 2,⍪'Apps' 'one' 'two' 'three'
      :For (name ref) :In ('Base HTML'_html)('Wrapped HTML'_HTML)('Dyalog Controls'_DC)('JQueryUI'_JQ)('SyncFusion'_SF)
          names←{({#.HtmlElement=⊃⊃⌽⎕CLASS ⍵}¨⍵)/⍵}ref.(⍎¨⎕NL ¯9.4)
          class←2↓⍕ref
          items⍪←(1,(≢names)/2),⍪(⊂name),(3+⍴class)↓¨⍕¨names
      :EndFor
      thediv.Add textspan'Samples'
      tv←thediv.Add _.ejTreeView(Samples←items)
      tv.style←'max-height: 300px'
      tv.On'nodeSelect' 'onSelectSample'('node' 'eval' 'argument.id')
    ∇

    ∇ PopulateRight thediv;pages;ul
        ⍝ Populate the Right Bar

      thediv.Add textspan'Documentation'
      pages←1 2⍴'SyncFusion APIs' 'http://helpjs.syncfusion.com/js/api/ejaccordion'
      pages⍪←'SyncFusion Demos' 'http://js.syncfusion.com/demos/web/'
      pages⍪←'JQueryUI' 'https://jqueryui.com/'
      pages⍪←2 2⍴'HTML' 'http://www.w3schools.com/html/' 'CSS' 'http://www.w3schools.com/css/'
      pages[;2]←↓pages[;,2],⊂'target=_blank"'
      ul←thediv.Add _.Ul pages
      ul.style←'font-size: 10px;'

      thediv.Add _.br

      thediv.Add textspan'Resources'
      pages←1 2⍴'MiServer Forum' 'http://www.dyalog.com/forum/viewforum.php?f=34'
      pages⍪←'Blog' 'http://www.dyalog.com/blog/category/miserver/'
      pages⍪←'GitHub Repo' 'https://github.com/Dyalog/MiServer'
      pages⍪←'dyalog.com' 'http://www.dyalog.com'
      pages[;2]←↓pages[;,2],⊂'target=_blank"'
      ul←thediv.Add _.Ul pages
      ul.style←'font-size: 10px;'
    ∇

    ∇ PopulateMid mid;btns;size

      mid.Add¨3⍴_.br
      btns←LinkButton'Download MiServer' '/styles/images/download-zone.png' '/download...'
      btns,←LinkButton'Read More' '/styles/images/support.png' '/readmore...'
      mid.Add horz btns
    ∇

    ∇ r←LinkButton(label image link);a;d;size

      d←'.linkbutton'New _.div
      size←'height: 32px; width: 32px;'
      'src' 'style'(d.Add _.img).Set image size
      d.Add textspan label
      r←New _.A d link
    ∇

    ∇ r←onSelectSample;node;section
      :Access Public
        ⍝ When a sample is selected, call this
      node←⊃2⊃⎕VFI{((+\⍵='_')⍳2)↓⍵}⊃_PageData.node
      section←⊃Samples[1+node-(⌽node↑Samples[;1])⍳1;2]

      r←'#divSampleTab'Replace'Selected node: ',(⊃Samples[node;2]),' in section ',section
    ∇

:EndClass


Lists of search results/controls/samples now have buttons instead of highlighting.

:Class index : MiPageSample

    :SECTION GLOBALS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    :Field TYPES←'Simple' 'Advanced'⍝ Types of samples (if not App)
    :Field GROUPS      ⍝ Names of groups of elements
    :Field REFS        ⍝ ... their refs
    :Field APPS        ⍝ List of all apps
    :Field SAMPLES     ⍝ List of all samples (well, groups and controls really)
    :Field INFOLONG    ⍝ Long desc of each control
    :Field INFOSHORT   ⍝ Short (1-3 words) desc of each control
    :Field NAMESLONG   ⍝ Elements that have long info
    :Field NAMESSHORT  ⍝ Elements that have short info
    :Field SEARCH←''   ⍝ Last-search cache
    :Field CONTROLS←'' ⍝ All control names
    :Field APPDESCS    ⍝ Sample Apps' Description::
    :Field APPCTRLS    ⍝ Sample Apps' Control:: (i.e. the title)
    :Field CORE        ⍝ Core subset of controls

    :ENDSECTION

    :SECTION UTILITIES ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    Q←'"'∘,,∘'"'  ⍝ Surround with quotes, a.k.a. APL dragon

    D←{(×≢⍵)/' – ',⍵} ⍝ Prepend a dash if non-empty

    P←{1⌽(×≢#.HTMLInput.dtlb ⍵)/') (',⍵} ⍝ Surround with parens if non-empty

    Words←{(1↓¨(' '∘=⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces

    SpaceToDir←{'Examples/',1↓⍵} ⍝ Convert Space name to Folder name

    Frame←{'.framed' ('src=',Q⍵,'?NoWrapper=1') New _.iframe}

    NewWinA←{('target' '_blank')('href=',Q⊃⌽⍵) New _.a (⊃⍵)} ⍝ New link that opens in a new window/tab

    NewDiv←{⍵ New _.div} ⍝ Make a div

    Horz←{⍺←⊢ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1} ⍝ Horizontal StackPanel

    Type←{P(1⍳⍨∨/¨TYPES⍷¨⊂⍺)⊃(TYPES,¨⊂' sample for ',⍵),⊂'Sample app'} ⍝ Simple/Advanced/App?

    Dread←{0::,⊂'[file read failed]' ⋄ #.UnicodeFile.ReadNestedText #.Boot.AppRoot,⍵,'.dyalog'} ⍝ Read dyalog file

    Dlist←{0::⍬ ⋄ ¯7↓¨6⊃#.Files.DirX #.Boot.AppRoot,⍵,'/*.dyalog'} ⍝ List dyalog files

    NodeID←{⍺←⊢ ⋄ ('#node',⊃⍵)∘,¨⍕¨⍺+⍳⍴⊃⌽⍵} ⍝ Generate node ids (⍵='L' items) optional (⍺=offset)

    Split←{{⍵↑⍨+/∨\' '≠⌽⍵}¨('_'⎕R' ')⍵((⍳↑⊣){⍺⍵}(⍳↓⊣))'_'} ⍝ Split at first _ and fix _s

    In←{∨/¨⊃⍷¨/{⎕SE.Dyalog.Utils.lcase¨eis ⍵}¨⍺ ⍵} ⍝ Case-insensitive find

    Names←{1↓¨⍵↑¨⍨¯1+⍵⍳¨':'} ⍝ Extract section names from set of several 'name:: description'

    AddShortInfo←{⍵,¨P¨(4+⍴¨⍵)↓¨(INFOSHORT,⊂'')[NAMESSHORT⍳⍵]} ⍝ ⍵ (ShortDesc)

    AddLongInfo←{(New¨_.strong,¨⍵),¨D¨{⍵,'[no info available]'/⍨0∊⍴⍵}¨(4+⍴¨⍵)↓¨(INFOLONG,⊂'')[NAMESLONG⍳⍵]} ⍝ ⍵ - LongDesc

      Section←{ ⍝ extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)'
          opts←('Mode' 'M')('DotAll' 1)
          res←(regex ⎕S'\1\2'⍠opts)⍵
          (1↓⊃res)~'⍝'
      }

      JSsafe←{ ⍝ Make text JS safe by escaping problematic chars
          fr←'''' '"' '\\' '\n' '\r',⎕UCS 9 8 12               ⍝ ' " \ NL CR TB BS FF
          to←'\\''' '\\"' '\\\\' '\\n' '\\r' '\\t' '\\b' '\\f' ⍝ problematic in regex too :-)
          Q(fr ⎕R to⍠'Mode' 'D')⍵ ⍝ Replace problematic chars and sourround with quotes
      }

    ∇ js←APLtoJS vtv;act;data
      :Access public shared
     ⍝ Convert vectors of pseudo JS (generated by Replace, Execute, et al.) to proper JS

      js←''
      :For act data :In eis vtv
          data←JSsafe⊃⌽eis data ⍝ the last or only one
          :Select ⊃eis act      ⍝ the first or only one
          :Case 'assign'
              js,←(⊃⌽act),' = ',data,';' ⍝ variable = "data"
          :Case 'execute'
              js,←'eval(',data,');' ⍝ eval("data")
          :Case 'replace'
              js,←'$("',(⊃⌽act),'").html(',data,');' ⍝ $("#myid").html("data")
          :Else
              js,←'$("',(⊃⌽act),'").',(⊃act),'(',data,');' ⍝ $("#myid").append("data")
          :EndSelect
      :EndFor
    ∇

    :ENDSECTION

    :SECTION MAIN ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ Compose;left;mid;right;sp;style
      :Access public

     ⍝ Initialize globals
      SAMPLES←0 3⍴⍬
      GROUPS←'Dyalog Controls' 'Base HTML' 'SyncFusion' 'JQueryUI'
      REFS←_DC _html _SF _JQ
      INFOSHORT←'⍝',¨Dread'Examples/Data/infoShort'
      INFOLONG←'⍝',¨Dread'Examples/Data/infoLong'
      NAMESSHORT←Names INFOSHORT
      NAMESLONG←Names INFOLONG

      '#title'Add _.title'MS3: About' ⍝ After the : will be updated

      Add _.StyleSheet'/Styles/homepage.css'
      style←''
      style,←'#leftBar {background-color:inherit;margin-right:6px;} '
      ⍝style,←'li:hover {background-color:highlight;font-wight:normal;}'
      style,←'.menu {padding-left,padding-right:0;} '
      style,←'.cat {font-size:12pt;cursor:pointer;border:1px solid transparent;padding:0 4px 2px 4px;} '
      style,←'.cat:hover {background:linear-gradient(to bottom,#ffbb60 0%,#f37603 100%);border:1px solid #f9cb59;font-weight:bold;} '
      style,←'.menuitem {margin-bottom,margin-left:0px;padding-left:2px;cursor:pointer;} '
      style,←'.menuitem:hover {background-color:highlight;} '
      style,←'.framed {width:730px;max-height:600px;min-height:400px;border:2px inset;overflow-y:auto;background-color:white;} '
      style,←'.listitem {margin:0px;padding:4px;cursor:pointer;box-sizing:border-box;} '
      style,←'.listitem:before {content:"▶";padding: 1px 3px 3px 4px;'
      style,←'    background: ThreeDFace;'⍝#4479BA;'
      style,←'    color: ButtonText;'
      style,←'    border-radius: 4px;'
      style,←'    border: solid 1px ThreeDLightShadow;'⍝#20538D;'
      style,←'    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);'
      style,←'    transition-duration: 0.2s;'
      style,←'    margin-right: 4px;'
      style,←'}'
      style,←'.listitem:active:before {'
      style,←'    box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);'
      style,←'    background: ThreeDShadow;'⍝#2E5481;'
      style,←'    border: solid 1px ThreeDDarkShadow;'⍝#203E5F;'
      style,←'}'
      style,←'.listitem:hover:before {'
      style,←'    background: ThreeDHighlight;'⍝#356094;'
      style,←'    border: solid 1px ThreeDFace;'⍝#2A4E77;'
      style,←'    text-decoration: none;'
      style,←'}'
      style,←'.noitems {margin:0px;padding:4px;cursor:not-allowed;} '
      style,←'.samplesource {overflow-x:auto;width:730px;background-color:#e5e5cc;border:2px inset;} '
      Add _.style style

      (left mid)←NewDiv¨'#leftBar' '#midBar' ⍝ Create panes
      sp←Add'mainSP'Horz left mid

      sp.Items[1].style←⊂'width: 200px; max-height: 450px;'
      sp.Items[2].style←⊂'margin: 5px;'
      sp.style←'height: 450px;background-color:inherit;'

      PopulateLeft left
      PopulateMid mid
    ∇

    ∇ PopulateLeft thediv;class;depths;group;items;ref;samples;vp;search;style;menu;i;fs;ac;text;stuff;treeall;treecore;names;tree

      stuff←''
      tree←0 3⍴0 '' ''

         ⍝ SEARCH FIELD ⍝⍝⍝
      stuff,←New _.EditField'searchfield'⍝).On'change' 'OnSearch'('str' 'val')
      (stuff,←'#searchbutton' '.menu'New _.button'Search').On'click' 'OnSearch'('str' '#searchfield' 'val')

      thediv.Add Horz stuff
      thediv.Add _.hr

      ⍝ SAMPLE APPS ⍝⍝⍝
      APPS←(Dlist'Examples/Apps')~⊂'index'
      APPDESCS←(⊂'Description')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      APPCTRLS←(⊂'Control')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      ('.cat'thediv.Add _.span'Sample Apps').On'click' 'OnAppHeader'
      APPS{tree⍪←1 ⍵('nodeA',⍺)}¨APPCTRLS
      tree←'#treeA'thediv.Add _.ejTreeView tree
      tree.On'nodeSelect' 'OnApp'('node' 'eval' 'argument.id')

      thediv.Add _.hr

      ('#node01All' '.cat'thediv.Add _.span'Controls').On'click' 'OnTree'
      CORE←⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/core -noname'
      treecore←1 3⍴1 'Core' 'node0_Core'
      treeall←1 3⍴1 'All' 'node00All'

      :For group class :InEach GROUPS(3↓¨⍕¨REFS) ⍝ Remove leading #._
          names←⎕THIS.⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/tree',class,' -noname'
          CONTROLS,←⊂names[;2]/⍨(⊢=⌈/)names[;1]
          ⍎class,'←names'

          :If ∨/names[;2]∊CORE
              treecore⍪←2(group,P'_',class)('node__',class)
              treecore⍪←3,0 1↓names⌿⍨names[;2]∊CORE
          :EndIf

          treeall⍪←2(group,P'_',class)('node_',class)
          treeall⍪←⍎class

      :EndFor

      tree←'#treeC' 'style="max-height:260px"'thediv.Add _.ejTreeView,⊂treecore⍪treeall
      tree.On'nodeSelect' 'OnTree'('node' 'eval' 'argument.id')
    ∇

    ∇ PopulateMid mid;url;code;frame;mya

     ⍝ Read framed pages
      url←'Examples/Apps/About'
      code←Dread url
     ⍝ Create and fill placeholder for title header
      mya←('#SampleTitle'mid.Add _.h2).Add NewWinA('Control'Section code)url
     ⍝ Create and fill placeholder for description line
      '#SampleDesc'mid.Add _.p('Description'Section code)
     ⍝ Create and fill placeholder for embedded page
      frame←mid.Add NewDiv'#SampleFrame'
      frame.Add Frame url
      '#SampleSource' '.samplesource'mid.Add _.div⍝,⊂'x-small;border:none'#.HTMLInput.APLToHTMLColour code ⍝ No source on page load
    ∇

    :ENDSECTION

    :SECTION CALLBACKS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ r←OnTree;node;descs;out;items;spacectrl;control;spacedir;files;file;i;url;code;ctrlsec;desc;item;title;currctrls;core;names;class;group;cat
      :Access Public
      node←GetNode
      :If '0'∊node ⍝ 'All'
          core←'_'=2⊃node
          currctrls←(CORE∘∩⍣core)⊃,/CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←(2↓node)(2↓node,' Controls')((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃node ⍝ Class or core subset thereof
          core←'_'=2⊃node
          node↓⍨←core ⍝ Remove additional _ from '__html' if core-only
          i←REFS⍳⍎node
          currctrls←(CORE∘∩⍣core)i⊃CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←node((i⊃GROUPS),P node)((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃⌽node ⍝ Category
          (class cat)←Split node
          names←⍎class
          i←(names[;3]/⍨(⊢=⌊/)names[;1])⍳⊂'node',node
          currctrls←1↓i⊃names[;2]⊂⍨(⊢=⌊/)names[;1]
          group←GROUPS⊃⍨REFS⍳⍎'_',class
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',class)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←('_',class)(group,(P'_',class),' ',cat)(' controls',⍨⍕⍴currctrls)out''
      :Else ⍝ Control
          spacectrl←'_',('_'⎕R'.')node
          (class control)←Split node
          spacedir←'Examples/',class
          files←Dlist spacedir
          out←NewDiv'.framed'
          CURRFILES←''
          i←0
          :For file :In files
              url←spacedir,'/',file
              code←Dread url
              ctrlsec←Words'Control'Section code
              :If (⊂spacectrl)∊ctrlsec ⍝ Extract space-separated words
                  i+←1
                  CURRFILES,←⊂url
                  desc←⊂'Description'Section code
                  desc←∊desc,file Type⊃ctrlsec
                  item←('#nodeS',⍕i)'.listitem'out.Add _.p desc
                  item.On'click' 'OnSample'
              :EndIf
          :EndFor
          :If ~×i
              '.noitems'out.Add _.p,⊂'[no samples using ',spacectrl,']'
          :EndIf
          title←1↓control Section INFOSHORT
          title←'Samples using ',spacectrl,P title
          desc←control Section INFOLONG
          r←spacectrl title desc out''
      :EndIf
      r←GenJS r
    ∇

    ∇ r←OnAppHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      descs←APPCTRLS,¨' – '∘,¨APPDESCS
      out←NewDiv'.framed'
      items←APPS{('nodeB',⍺)out.Add _.p ⍵}¨descs
      items.Set⊂'.listitem'
      items.On⊂'click' 'OnApp'
      r←GenJS'Apps' 'Small applications to showcase MiServer 3.0 functionality'(' apps',⍨≢APPS)out''
    ∇

    ∇ r←OnCtrlHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      r←GenJS'Controls' 'Something about controls' 'Controls' 'list of all controls with long descs' ''
    ∇

    ∇ r←OnApp
      :Access Public
     ⍝ Gets called upon selection in Sample Apps tree
      r←GenJS Update'Examples/Apps/',1↓GetNode
    ∇

    ∇ r←OnSample
      :Access Public
      r←GenJS Update CURRFILES⊃⍨⍎5↓_what ⍝ id="nodeS23"
    ∇

    ∇ r←OnSearch;time;str;terms;out;i;dir;files;file;url;code;ctrlsec;desc;item
      :Access Public
      time←0.001×3⊃⎕AI ⍝ prepare search statistics
      :If ×≢str←Get'str'  ⍝ get search string
      ⍝⍝⍝ Controls
          terms←INFOSHORT/⍨str In INFOSHORT
          terms,←terms~⍨INFOLONG/⍨str In INFOLONG
          terms←'.',¨∪Names terms
      ⍝⍝⍝ Samples
          out←NewDiv'.framed'
          CURRFILES←''
          i←0
          :For dir :In 'Apps' 'html' 'HTMLplus' 'DC' 'JQ' 'SF'
              files←Dlist'Examples/',dir
              :For file :In files~⊂'index'
                  url←'Examples/',dir,'/',file
                  code←Dread url
                  ctrlsec←'Control'Section code
                  desc←⊂'Description'Section code
                  :If ∨/str In desc
                  :OrIf ∨/∊(terms,⊂str)In¨⊂ctrlsec ⍝ always case sensitive
                      i+←1
                      CURRFILES,←⊂url
                      desc←∊desc,file Type⊃Words ctrlsec
                      item←('#nodeS',⍕i)'.listitem'out.Add _.p desc
                      item.On'click' 'OnSample'
                  :EndIf
              :EndFor
          :EndFor
          :If ~×i
              '.noitems'out.Add _.p,⊂'[no results for ',str,']'
          :EndIf
          desc←New _.span'Showing results for '
          desc.Add _.em str
          desc.Add(×≢terms)/D'including relevant element','s'/⍨1≠≢terms
          desc.Add _.em(⍕1↓¨terms)
          SEARCH←(Q str)''desc out''
      :EndIf
      time-⍨←0.001×3⊃⎕AI
      (2⊃SEARCH)←(⍕≢CURRFILES),' results (',time,' seconds)'
      r←GenJS SEARCH

    ∇

    ∇ (page title desc iframe code)←Update url;ctrlsec
     ⍝ Create new placeholder values
      page←'Sample'
      iframe←Frame url
      code←Dread url
      ctrlsec←'Control'Section code
      desc←'Description'Section code
      title←NewWinA ctrlsec url

    ∇

    ∇ r←GenJS(page title desc iframe code)
     ⍝ Generate JavaScript for filling placeholders
      r←'#title'Replace'MS3: ',page
      r,←'#SampleTitle'Replace title
      r,←'#SampleDesc'Replace desc
      r,←'#SampleFrame'Replace iframe
      r,←'#SampleSource'Replace(×≢code)/'x-small;border:none'#.HTMLInput.APLToHTMLColour code
    ∇

    ∇ node←GetNode
      node←4↓(1+'tree'≡4↑_what)⊃_what(Get'node')
    ∇
    :ENDSECTION

:EndClass

:Class index : MiPageSample

    :SECTION GLOBALS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    :Field TYPES←'Simple' 'Advanced'⍝ Types of samples (if not App)
    :Field GROUPS      ⍝ Names of groups of elements
    :Field REFS        ⍝ ... their refs
    :Field APPS        ⍝ List of all apps
    :Field SAMPLES     ⍝ List of all samples (well, groups and controls really)
    :Field INFOLONG    ⍝ Long desc of each control
    :Field INFOSHORT   ⍝ Short (1-3 words) desc of each control
    :Field NAMESLONG   ⍝ Elements that have long info
    :Field NAMESSHORT  ⍝ Elements that have short info
    :Field SEARCH←''   ⍝ Last-search cache
    :Field CONTROLS←'' ⍝ All control names
    :Field APPDESCS    ⍝ Sample Apps' Description::
    :Field APPCTRLS    ⍝ Sample Apps' Control:: (i.e. the title)
    :Field CORE        ⍝ Core subset of controls

    :ENDSECTION

    :SECTION UTILITIES ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    Q←'"'∘,,∘'"'  ⍝ Surround with quotes, a.k.a. APL dragon

    D←{(×≢⍵)/' – ',⍵} ⍝ Prepend a dash if non-empty

    P←{1⌽(×≢#.HTMLInput.dtlb ⍵)/') (',⍵} ⍝ Surround with parens if non-empty

    Words←{(1↓¨(' '∘=⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces

    SpaceToDir←{'Examples/',1↓⍵} ⍝ Convert Space name to Folder name

    Frame←{'.framed' ('src=',Q⍵,'?NoWrapper=1') New _.iframe}

    NewWinA←{('target' '_blank')('href=',Q⊃⌽⍵) New _.a (⊃⍵)} ⍝ New link that opens in a new window/tab

    NewDiv←{⍵ New _.div} ⍝ Make a div

    Horz←{⍺←⊢ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1} ⍝ Horizontal StackPanel

    Type←{P(1⍳⍨∨/¨TYPES⍷¨⊂⍺)⊃(TYPES,¨⊂' sample for ',⍵),⊂'Sample app'} ⍝ Simple/Advanced/App?

    Dread←{0::,⊂'[file read failed]' ⋄ #.UnicodeFile.ReadNestedText #.Boot.AppRoot,⍵,'.dyalog'} ⍝ Read dyalog file

    Dlist←{0::⍬ ⋄ ¯7↓¨6⊃#.Files.DirX #.Boot.AppRoot,⍵,'/*.dyalog'} ⍝ List dyalog files

    NodeID←{⍺←⊢ ⋄ ('#node',⊃⍵)∘,¨⍕¨⍺+⍳⍴⊃⌽⍵} ⍝ Generate node ids (⍵='L' items) optional (⍺=offset)

    Split←{{⍵↑⍨+/∨\' '≠⌽⍵}¨('_'⎕R' ')⍵((⍳↑⊣){⍺⍵}(⍳↓⊣))'_'} ⍝ Split at first _ and fix _s

    In←{∨/¨⊃⍷¨/{⎕SE.Dyalog.Utils.lcase¨eis ⍵}¨⍺ ⍵} ⍝ Case-insensitive find

    Names←{1↓¨⍵↑¨⍨¯1+⍵⍳¨':'} ⍝ Extract section names from set of several 'name:: description'

    AddShortInfo←{⍵,¨P¨(4+⍴¨⍵)↓¨(INFOSHORT,⊂'')[NAMESSHORT⍳⍵]} ⍝ ⍵ (ShortDesc)

    AddLongInfo←{(New¨_.strong,¨⍵),¨D¨{⍵,'[no info available]'/⍨0∊⍴⍵}¨(4+⍴¨⍵)↓¨(INFOLONG,⊂'')[NAMESLONG⍳⍵]} ⍝ ⍵ - LongDesc

      Section←{ ⍝ extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)'
          opts←('Mode' 'M')('DotAll' 1)
          res←(regex ⎕S'\1\2'⍠opts)⍵
          (1↓⊃res)~'⍝'
      }

      JSsafe←{ ⍝ Make text JS safe by escaping problematic chars
          fr←'''' '"' '\\' '\n' '\r',⎕UCS 9 8 12               ⍝ ' " \ NL CR TB BS FF
          to←'\\''' '\\"' '\\\\' '\\n' '\\r' '\\t' '\\b' '\\f' ⍝ problematic in regex too :-)
          Q(fr ⎕R to⍠'Mode' 'D')⍵ ⍝ Replace problematic chars and sourround with quotes
      }

    ∇ js←APLtoJS vtv;act;data
      :Access public shared
     ⍝ Convert vectors of pseudo JS (generated by Replace, Execute, et al.) to proper JS

      js←''
      :For act data :In eis vtv
          data←JSsafe⊃⌽eis data ⍝ the last or only one
          :Select ⊃eis act      ⍝ the first or only one
          :Case 'assign'
              js,←(⊃⌽act),' = ',data,';' ⍝ variable = "data"
          :Case 'execute'
              js,←'eval(',data,');' ⍝ eval("data")
          :Case 'replace'
              js,←'$("',(⊃⌽act),'").html(',data,');' ⍝ $("#myid").html("data")
          :Else
              js,←'$("',(⊃⌽act),'").',(⊃act),'(',data,');' ⍝ $("#myid").append("data")
          :EndSelect
      :EndFor
    ∇

    :ENDSECTION

    :SECTION MAIN ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ Compose;left;mid;right;sp;style
      :Access public

     ⍝ Initialize globals
      SAMPLES←0 3⍴⍬
      GROUPS←'Dyalog Controls' 'Base HTML' 'SyncFusion' 'JQueryUI'
      REFS←_DC _html _SF _JQ
      INFOSHORT←'⍝',¨Dread'Examples/Data/infoShort'
      INFOLONG←'⍝',¨Dread'Examples/Data/infoLong'
      NAMESSHORT←Names INFOSHORT
      NAMESLONG←Names INFOLONG

      '#title'Add _.title'MS3: About' ⍝ After the : will be updated

      Add _.StyleSheet'/Styles/homepage.css'
      style←''
      style,←'#leftBar {background-color:inherit;margin-right:6px;} '
      ⍝style,←'li:hover {background-color:highlight;font-wight:normal;}'
      style,←'.menu {padding-left,padding-right:0;} '
      style,←'.cat {font-size:12pt;cursor:pointer;border:1px solid transparent;padding:0 4px 2px 4px;} '
      style,←'.cat:hover {background:linear-gradient(to bottom,#ffbb60 0%,#f37603 100%);border:1px solid #f9cb59;font-weight:bold;} '
      style,←'.menuitem {margin-bottom,margin-left:0px;padding-left:2px;cursor:pointer;} '
      style,←'.menuitem:hover {background-color:highlight;} '
      style,←'.framed {width:730px;max-height:600px;min-height:400px;border:2px inset;overflow-y:auto;background-color:white;} '
      style,←'.listitem {margin:0px;padding:4px;cursor:pointer;} '
      style,←'.listitem:hover {background-color:highlight;} '
      style,←'.noitems {margin:0px;padding:4px;cursor:not-allowed;} '
      style,←'.samplesource {overflow-x:auto;width:730px;background-color:#e5e5cc;border:2px inset;} '
      Add _.style style

      (left mid)←NewDiv¨'#leftBar' '#midBar' ⍝ Create panes
      sp←Add'mainSP'Horz left mid

      sp.Items[1].style←⊂'width: 200px; max-height: 450px;'
      sp.Items[2].style←⊂'margin: 5px;'
      sp.style←'height: 450px;background-color:inherit;'

      PopulateLeft left
      PopulateMid mid
    ∇

    ∇ PopulateLeft thediv;class;depths;group;items;ref;samples;vp;search;style;menu;i;fs;ac;text;stuff;treeall;treecore;names;tree

      stuff←''
      tree←0 3⍴0 '' ''

         ⍝ SEARCH FIELD ⍝⍝⍝
      (stuff,←New _.EditField'str').On'change' 'OnSearch'('str' 'val')
      (stuff,←'#case' '.menu'New _.button'Search').On'click' 'OnSearch'

      thediv.Add Horz stuff
      thediv.Add _.hr

      ⍝ SAMPLE APPS ⍝⍝⍝
      APPS←(Dlist'Examples/Apps')~⊂'index'
      APPDESCS←(⊂'Description')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      APPCTRLS←(⊂'Control')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      ('.cat'thediv.Add _.span'Sample Apps').On'click' 'OnAppHeader'
      APPS{tree⍪←1 ⍵('nodeA',⍺)}¨APPCTRLS
      tree←'#treeA'thediv.Add _.ejTreeView tree
      tree.On'nodeSelect' 'OnApp'('node' 'eval' 'argument.id')

      thediv.Add _.hr

      ('#node01All' '.cat'thediv.Add _.span'Controls').On'click' 'OnTree'
      CORE←⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/core -noname'
      treecore←1 3⍴1 'Core' 'node0_Core'
      treeall←1 3⍴1 'All' 'node00All'

      :For group class :InEach GROUPS(3↓¨⍕¨REFS) ⍝ Remove leading #._
          names←⎕THIS.⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/tree',class,' -noname'
          CONTROLS,←⊂names[;2]/⍨(⊢=⌈/)names[;1]
          ⍎class,'←names'

          :If ∨/names[;2]∊CORE
              treecore⍪←2(group,P'_',class)('node__',class)
              treecore⍪←3,0 1↓names⌿⍨names[;2]∊CORE
          :EndIf

          treeall⍪←2(group,P'_',class)('node_',class)
          treeall⍪←⍎class

      :EndFor

      tree←'#treeC' 'style="max-height:260px"'thediv.Add _.ejTreeView,⊂treecore⍪treeall
      tree.On'nodeSelect' 'OnTree'('node' 'eval' 'argument.id')
    ∇

    ∇ PopulateMid mid;url;code;frame;mya

     ⍝ Read framed pages
      url←'Examples/Apps/About'
      code←Dread url
     ⍝ Create and fill placeholder for title header
      mya←('#SampleTitle'mid.Add _.h2).Add NewWinA('Control'Section code)url
     ⍝ Create and fill placeholder for description line
      '#SampleDesc'mid.Add _.p('Description'Section code)
     ⍝ Create and fill placeholder for embedded page
      frame←mid.Add NewDiv'#SampleFrame'
      frame.Add Frame url
      '#SampleSource' '.samplesource'mid.Add _.div⍝,⊂'x-small;border:none'#.HTMLInput.APLToHTMLColour code ⍝ No source on page load
    ∇

    :ENDSECTION

    :SECTION CALLBACKS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ r←OnTree;node;descs;out;items;spacectrl;control;spacedir;files;file;i;url;code;ctrlsec;desc;item;title;currctrls;core;names;class;group;cat
      :Access Public
      node←GetNode
      :If '0'∊node ⍝ 'All'
          core←'_'=2⊃node
          currctrls←(CORE∘∩⍣core)⊃,/CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←(2↓node)(2↓node,' Controls')((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃node ⍝ Class or core subset thereof
          core←'_'=2⊃node
          node↓⍨←core ⍝ Remove additional _ from '__html' if core-only
          i←REFS⍳⍎node
          currctrls←(CORE∘∩⍣core)i⊃CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←node((i⊃GROUPS),P node)((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃⌽node ⍝ Category
          (class cat)←Split node
          names←⍎class
          i←(names[;3]/⍨(⊢=⌊/)names[;1])⍳⊂'node',node
          currctrls←1↓i⊃names[;2]⊂⍨(⊢=⌊/)names[;1]
          group←GROUPS⊃⍨REFS⍳⍎'_',class
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',class)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←('_',class)(group,(P'_',class),' ',cat)(' controls',⍨⍕⍴currctrls)out''
      :Else ⍝ Control
          spacectrl←'_',('_'⎕R'.')node
          (class control)←Split node
          spacedir←'Examples/',class
          files←Dlist spacedir
          out←NewDiv'.framed'
          CURRFILES←''
          i←0
          :For file :In files
              url←spacedir,'/',file
              code←Dread url
              ctrlsec←Words'Control'Section code
              :If (⊂spacectrl)∊ctrlsec ⍝ Extract space-separated words
                  i+←1
                  CURRFILES,←⊂url
                  desc←⊂'Description'Section code
                  desc←∊desc,file Type⊃ctrlsec
                  item←('#nodeS',⍕i)'.listitem'out.Add _.p desc
                  item.On'click' 'OnSample'
              :EndIf
          :EndFor
          :If ~×i
              '.noitems'out.Add _.p,⊂'[no samples using ',spacectrl,']'
          :EndIf
          title←1↓control Section INFOSHORT
          title←'Samples using ',spacectrl,P title
          desc←control Section INFOLONG
          r←spacectrl title desc out''
      :EndIf
      r←GenJS r
    ∇

    ∇ r←OnAppHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      descs←APPCTRLS,¨' – '∘,¨APPDESCS
      out←NewDiv'.framed'
      items←APPS{('nodeB',⍺)out.Add _.p ⍵}¨descs
      items.Set⊂'.listitem'
      items.On⊂'click' 'OnApp'
      r←GenJS'Apps' 'Small applications to showcase MiServer 3.0 functionality'(' apps',⍨≢APPS)out''
    ∇

    ∇ r←OnCtrlHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      r←GenJS'Controls' 'Something about controls' 'Controls' 'list of all controls with long descs' ''
    ∇

    ∇ r←OnApp
      :Access Public
     ⍝ Gets called upon selection in Sample Apps tree
      r←GenJS Update'Examples/Apps/',1↓GetNode
    ∇

    ∇ r←OnSample
      :Access Public
      r←GenJS Update CURRFILES⊃⍨⍎5↓_what ⍝ id="nodeS23"
    ∇

    ∇ r←OnSearch;time;str;terms;out;i;dir;files;file;url;code;ctrlsec;desc;item
      :Access Public
      time←0.001×3⊃⎕AI ⍝ prepare search statistics
      :If ×≢str←Get'str'  ⍝ get search string
      ⍝⍝⍝ Controls
          terms←INFOSHORT/⍨str In INFOSHORT
          terms,←terms~⍨INFOLONG/⍨str In INFOLONG
          terms←'.',¨∪Names terms
      ⍝⍝⍝ Samples
          out←NewDiv'.framed'
          CURRFILES←''
          i←0
          :For dir :In 'Apps' 'html' 'HTMLplus' 'DC' 'JQ' 'SF'
              files←Dlist'Examples/',dir
              :For file :In files~⊂'index'
                  url←'Examples/',dir,'/',file
                  code←Dread url
                  ctrlsec←'Control'Section code
                  desc←⊂'Description'Section code
                  :If ∨/str In desc
                  :OrIf ∨/∊(terms,⊂str)In¨⊂ctrlsec ⍝ always case sensitive
                      i+←1
                      CURRFILES,←⊂url
                      desc←∊desc,file Type⊃Words ctrlsec
                      item←('#nodeS',⍕i)'.listitem'out.Add _.p desc
                      item.On'click' 'OnSample'
                  :EndIf
              :EndFor
          :EndFor
          :If ~×i
              '.noitems'out.Add _.p,⊂'[no results for ',str,']'
          :EndIf
          desc←New _.span'Showing results for '
          desc.Add _.em str
          desc.Add(×≢terms)/D'including relevant element','s'/⍨1≠≢terms
          desc.Add _.em(⍕1↓¨terms)
          SEARCH←(Q str)''desc out''
      :EndIf
      time-⍨←0.001×3⊃⎕AI
      (2⊃SEARCH)←(⍕≢CURRFILES),' results (',time,' seconds)'
      r←GenJS SEARCH

    ∇

    ∇ (page title desc iframe code)←Update url;ctrlsec
     ⍝ Create new placeholder values
      page←'Sample'
      iframe←Frame url
      code←Dread url
      ctrlsec←'Control'Section code
      desc←'Description'Section code
      title←NewWinA ctrlsec url

    ∇

    ∇ r←GenJS(page title desc iframe code)
     ⍝ Generate JavaScript for filling placeholders
      r←'#title'Replace'MS3: ',page
      r,←'#SampleTitle'Replace title
      r,←'#SampleDesc'Replace desc
      r,←'#SampleFrame'Replace iframe
      r,←'#SampleSource'Replace(×≢code)/'x-small;border:none'#.HTMLInput.APLToHTMLColour code
    ∇

    ∇ node←GetNode
      node←4↓(1+'tree'≡4↑_what)⊃_what(Get'node')
    ∇
    :ENDSECTION

:EndClass


Use rm -f to delete files on Unix

Use rm -f to delete files on Unix

It's never appropriate to try to prompt the user for confirmation.

:Namespace Files

⍝ Provides cover functions for many file operations
⍝ Eventually all will be implemented for both Windows and *nix

    (⎕IO ⎕ML)←1

    ∇ r←data Append name
     ⍝ Append data to existing file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND ⍵}Nopen name
    ∇

    ∇ r←text AppendText name;tn
     ⍝ Append text to existing file (must be single byte text)
      tn←Nopen name
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ (tn name)←{ext}CreateTemp folder;cnt
     ⍝ Create a temp file
     ⍝ folder - folder to create temp file in
     ⍝ ext - file extension
      :If 0=⎕NC'ext' ⋄ ext←'tmp' ⋄ :EndIf
      :If 0∊⍴ext ⋄ ext←'tmp' ⋄ :EndIf
      cnt←tn←0
      :Repeat
          name←folder,('/\'∊⍨¯1↑folder)↓'/',⎕A[?8⍴⍴⎕A],'.',ext
          :Trap 22 ⋄ tn←name ⎕NCREATE 0 ⋄ :EndTrap
          cnt+←1
      :Until (cnt>20)∨tn≠0
      'Unable to create temporary file'⎕SIGNAL tn↓22
    ∇

    ∇ {protect}Copy FmTo;CopyFileX;GetLastError ⍝ Copy file Fm -> To
      :Select APLVersion
      :Case '*nix'
          ∘
      :Case 'Win'
          :If 0=⎕NC'protect'     ⍝ Copy fails if <protect> and 'To' exists.
              protect←0          ⍝ Default unprotected copy.
          :EndIf
          'CopyFileX'⎕NA'I kernel32.C32∣CopyFile* <0T <0T I'
          :If 0=CopyFileX FmTo,protect
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'CopyFile error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Delete name;DeleteFileX;GetLastError;FindFirstFile;FindNextFile;FindClose;handle;rslt;ok;next;⎕IO;path
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          _SH'rm -f ',unixfix name
      :Case 'Win'
          'DeleteFileX'⎕NA'I kernel32.C32∣DeleteFile* <0T'
          ⎕NA'I4 kernel32.C32|GetLastError'
          :If ∨/'*?'∊name ⍝ wildcards?
              path←{(⌽∨\⌽⍵∊'\/')/⍵}name
              _FindDefine
              handle rslt←_FindFirstFile name
              :If 0=handle
                  :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
              :EndIf
              :If '.'≠⊃6⊃rslt
                  {}DeleteFileX⊂path,6⊃rslt
              :EndIf
              :While 1=0⊃ok next←_FindNextFile handle
                  :If '.'≠⊃6⊃next
                      {}DeleteFileX⊂path,6⊃next
                  :EndIf
              :EndWhile
              :If 0 18∨.≠ok next
                  ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
              :EndIf
              {}FindClose handle
          :Else
              :If 0=DeleteFileX⊂name
                  11 ⎕SIGNAL⍨'DeleteFile error:',⍕GetLastError
              :EndIf
          :EndIf
      :EndSelect
    ∇

    ∇ r←DirExists path
      r←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :Trap 11
              r←0<⍴_SH'ls -adl ',unixfix path
          :EndTrap
      :Case 'Win'
          r←{1≠⊃⍴⍵:0 ⋄ ⍵[1;4]}'.'List path
      :EndSelect
    ∇

    ∇ r←Exists name
     ⍝ Does file exist?
      r←1
      :Trap 22
          :Trap 19 ⍝ file access error means file exists
              ⎕NUNTIE(unixfix name)⎕NTIE 0
          :EndTrap
      :Else
          r←0
      :EndTrap
    ∇

    ∇ r←GetCurrentDirectory;GCD;GetLastError
     ⍝ Get Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          r←⊃_SH'pwd'
      :Case 'Win'
          'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
          :If 0≠1⊃r←GCD 256 256
              r←2⊃r
          :Else
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Chars←GetText name;nid;signature;nums;sz;b
     ⍝ Read ANSI or Unicode character file
      sz←⎕NSIZE nid←(unixfix name)⎕NTIE 0
      signature←⎕NREAD nid 83 3 0
      :If signature≡¯17 ¯69 ¯65 ⍝ UTF-8?
          nums←⎕NREAD nid 83 sz
          Chars←'UTF-8'⎕UCS 256|nums ⍝ Signed ints
      :ElseIf ∨/b←(2↑signature)∧.=2 2⍴¯1 ¯2 ¯2 ⍝ Unicode (UTF-16)
          Chars←{,⌽(2,⍨2÷⍨⍴⍵)⍴⍵}⍣(⊣/b)⎕NREAD nid 83 sz 2
          Chars←'UTF-16'⎕UCS(2*16)|163 ⎕DR Chars
      :Else ⍝ ANSI or UTF-8
          Chars←{11::⎕UCS ⍵ ⋄ 'UTF-8'⎕UCS ⍵}256|⎕NREAD nid 83 sz 0
      :EndIf
      ⎕NUNTIE nid
    ∇

    ∇ vtv←GetVTV name
     ⍝ Read ANSI or Unicode character file as vector of text vectors
      vtv←{1↓¨(v=n)⊂v←(n←⎕UCS 10),⍵}(GetText name)~⎕UCS 13
    ∇

    ∇ r←LikelyURL w
      →0↓⍨r←(⎕DR w)∊80 82
      r←{(0∊⍴⍵)<∧/⍵∊'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%-._~:/?#[]@!$&''()*+,;='}w ⍝ identify likely URIs
    ∇

    ∇ r←{pattern}List path;z;rslt;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError;isFile;filter;isFolder
    ⍝ path and pattern are related.
    ⍝ If there is no pattern (or pattern is empty)
    ⍝   If path ends with '/' or '\' then return information for the contents of the folder, otherwise, return information about the folder (or file) itself
    ⍝ If a non-empty pattern exists, it is used as a filter on the contents of path (path is treated as a folder name)
    ⍝ Examples:
    ⍝   List '/dir/foo'  ⍝ returns information about /dir/foo
    ⍝   List '/dir/foo/' ⍝ returns information about the contents of /dir/foo/
    ⍝   '*.dyalog' List '/dir/foo'  ⍝ returns information about the .dyalog files in /dir/foo/
    ⍝   List '/dir/foo/*.dyalog'    ⍝ also returns information about the .dyalog files in /dir/foo/

    ⍝ Information returned is:
    ⍝ [;0] Name [;1] Length [;2] LastAccessTime [;3] IsDirectory
      ⎕IO←0
      :If 0=⎕NC'pattern' ⋄ pattern←'' ⋄ :EndIf
      isFolder←'/\'∊⍨¯1↑path
      filter←''
      :If isFile←~0∊⍴pattern
          filter←isFolder↓'/',pattern
      :EndIf

      r←0 4⍴'' 0 0 0

      :Select APLVersion
      :Case '*nix'
          →(0∊⍴rslt←1 _SH'ls -al',isFolder↓'d --time-style=full-iso ',unixfix path,filter)⍴0
          rslt←↑rslt
          rslt←' ',('total '≡6⍴rslt)↓[0]rslt
          r←((1↑⍴rslt),4)⍴0
          z←∧⌿' '=rslt ⍝ entirely blank columns
          z←z∧10>+\z    ⍝ Do not split file names
          rslt←z⊂rslt
          r[;3]←'d'=(0⊃rslt)[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI,4⊃rslt ⍝ Size
          z←,(5⊃rslt),6⊃rslt ⋄ ((z∊'-:')/z)←' ' ⋄ z←((1↑⍴r),6)⍴1⊃⎕VFI z
          r[;2]←↓⌊z,1000×1|z[;5]                ⍝ Add msec to Timestamp
          r[;0]←{(⌽~∨\⌽⍵='/')/⍵}¨{(-+/∧\' '=⌽⍵)↓¨↓⍵}0 1↓8⊃rslt    ⍝ Name

      :Case 'Mac'
          →(0∊⍴rslt←1 _SH'stat -lt "%F %T %z" ',unixfix path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter)⍴0
          r←((1↑⍴rslt),4)⍴0
          rslt←↑{⎕ML←3 ⋄ ⍵⊂⍨~{⍵∧9>+\⍵}' '=⍵}¨rslt
          r[;3]←'d'=0⊃¨rslt[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃¨⎕VFI¨rslt[;4]       ⍝ Size
          z←↑∊¨↓{w←⍵ ⋄ ((w∊'-:')/w)←' ' ⋄ 1⊃⎕VFI w}¨rslt[;5 6] ⍝
          r[;2]←↓z,0                            ⍝ 0 msec for MacOS to Timestamp
          r[;0]←rslt[;8]                        ⍝ Name

      :Case 'Win'
      ⍝ See DirX for explanations of results of _FindNextFile etc
          _FindDefine
          handle rslt←_FindFirstFile path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter
          :If 0=handle
              :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          →(0∊⍴rslt←↓[0]↑rslt)⍴0
          r←((1↑⍴0⊃rslt),4)⍴0
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt    ⍝ Get attributes into bits
          r[;3]←(0⊃rslt)[;27]              ⍝ IsDirectory?
          r[;1]←0(2*32)⊥⍉↑4⊃rslt           ⍝ combine size elements
          r[;2]←_Filetime_to_TS¨3⊃rslt     ⍝ As ⎕TS vector
          r[;0]←6⊃rslt                     ⍝ Name
      :EndSelect
      r←r[⍋↑r[;0];]
    ∇

    ∇ MkDir path;CreateDirectory;GetLastError;err
      ⍝ Create a folder
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :If ~DirExists path
              1 _SH'mkdir ',unixfix path
              ('mkdir error on ',path)⎕SIGNAL 11/⍨~DirExists path
          :EndIf
      :Case 'Win'
          ⎕NA'I kernel32.C32∣CreateDirectory* <0T I4' ⍝ Try for best function
          →(0≠CreateDirectory path 0)⍴0 ⍝ 0 means "default security attributes"
          ⎕NA'I4 kernel32.C32|GetLastError'
          err ⎕SIGNAL⍨'CreateDirectory error:',⍕err←GetLastError
      :EndSelect
    ∇

    ∇ Move filenames;MoveFileX;MoveFileExA;GetLastError;err
    ⍝ Move (from to) - move/rename file
      :Select APLVersion
      :Case '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣MoveFileEx* <0T <0T I4' ⍝ Try for best function
          :If 0≠MoveFileExA filenames,3 ⍝ REPLACE_EXISTING (1) + COPY_ALLOWED (2)
              :Return
          :EndIf
          ⎕NA'I4 kernel32.C32|GetLastError'
          :Select err←GetLastError
          :Case 120                     ⍝ ERROR_CALL_NOT_IMPLIMENTED
              'MoveFileX'⎕NA'I Kernel32.C32∣MoveFile* <0T <0T' ⍝ accept 2nd best - win 95
              :If 0≠MoveFileX filenames
                  :Return
              :EndIf
              err←GetLastError
          :EndSelect
          11 ⎕SIGNAL⍨'MoveFile error:',⍕err
      :EndSelect
    ∇

    ∇ tn←Nopen name
      :Trap 0
          tn←name ⎕NCREATE 0
      :Else
          tn←name ⎕NTIE 0
      :EndTrap
    ∇

    ∇ r←data Put name
     ⍝ Write data to file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND(0 ⎕NRESIZE ⍵)(⎕DR data)}Nopen name
    ∇

    ∇ r←text PutText name;tn
     ⍝ Write text to file (must be single byte text)
      tn←Nopen name
      0 ⎕NRESIZE tn
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ RmDir path;RemoveDirectoryA;GetLastError
     ⍝ Remove folder/directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣RemoveDirectory* <0T'
          →(0≠RemoveDirectory,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'RemoveDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ SetCurrentDirectory path;SCD;GetLastError
     ⍝ Set Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          'SCD'⎕NA'I kernel32.C32∣SetCurrentDirectory* <0T'
          →(0≠SCD,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'SetCurrentDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ r←{noext}SplitFilename filename;filesep;mask;path;file;ext
    ⍝ splits a filename into: path name ext
      noext←{6::0 ⋄ noext}''
      filesep←(~isWin)↓'\/'
      mask←⌽∨\⌽filename∊filesep
      path←mask/filename
      file←(~mask)/filename
      :If noext
          r←path file
      :Else
          mask←∨\⌽<\⌽'.'=file
          ext←mask/file
          file←(~mask)/file
          r←path file ext
      :EndIf
    ∇

    :Section Windows
    ∇ rslt←_Filetime_to_TS filetime;⎕IO
      :If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
      :OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
          rslt←0 0                   ⍝ if either call failed then zero the time elements
      :EndIf
      rslt←1 1 0 1 1 1 1 1/1⊃rslt    ⍝ remove day of week
    ∇

    ∇ _FindDefine;WIN32_FIND_DATA
      :If 0=⎕NC'FindFirstFileX'
          WIN32_FIND_DATA←'{I4 {I4 I4} {I4 I4} {I4 I4} {U4 U4} {I4 I4} T[260] T[14]}'
          'FindFirstFileX'⎕NA'I4 kernel32.C32|FindFirstFile* <0T >',WIN32_FIND_DATA
          'FindNextFileX'⎕NA'U4 kernel32.C32|FindNextFile* I4 >',WIN32_FIND_DATA
          ⎕NA'kernel32.C32|FindClose I4'
          ⎕NA'I4 kernel32.C32|FileTimeToLocalFileTime <{I4 I4} >{I4 I4}'
          ⎕NA'I4 kernel32.C32|FileTimeToSystemTime <{I4 I4} >{I2 I2 I2 I2 I2 I2 I2 I2}'
          ⎕NA'I4 kernel32.C32∣GetLastError'
      :EndIf
    ∇

    ∇ rslt←_FindFirstFile name;⎕IO
      rslt←FindFirstFileX name(⎕IO←0)
      :If ¯1=0⊃rslt                   ⍝ INVALID_HANDLE_VALUE
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0        ⍝ shorten the file name at the null delimiter
          (1 7⊃rslt)_FindTrim←0        ⍝ and for the alternate name
      :EndIf
    ∇

    ∇ rslt←_FindNextFile handle;⎕IO
      rslt←FindNextFileX handle(⎕IO←0)
      :If 1≠0⊃rslt
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0             ⍝ shorten the filename
          (1 7⊃rslt)_FindTrim←0             ⍝ shorten the alternate name
      :EndIf
    ∇

    ∇ name←name _FindTrim ignored;⎕IO
     ⍝ Truncates a character vector at the null delimiting byte.
     ⍝ The null is not included in the result.
      ⎕IO←0
      name↑⍨←name⍳⎕UCS 0
    ∇

    ∇ {r}←{suppress}_CMD cmd
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕CMD cmd
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ r←isWin
      r←'Win'≡APLVersion
    ∇

    :endsection

    :section *nix
    ∇ {r}←{suppress}_SH cmd
    ⍝ SH cover to suppress any error messages
    ⍝ suppress will suppress error from being signaled
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕SH cmd,' 2>/dev/null'
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ f←unixfix f
    ⍝ replaces Windows file separator \ with Unix file separator /
    ⍝ this approach is mindnumbingly simple and probably dangerous
    ⍝ which is why we call unixfix very cautiously
      :If (⊂APLVersion)∊'*nix' 'Mac' ⋄ ((f='\')/f)←'/' ⋄ :EndIf
    ∇

    ∇ r←APLVersion
      :Select 3↑⊃'.'⎕WG'APLVersion'
      :CaseList 'Lin' 'AIX' 'Sol'
          r←'*nix'
      :Case 'Win'
          r←'Win'
      :Case 'Mac'
          r←'Mac'
      :Else
          ∘∘∘ ⍝ unknown version
      :EndSelect
    ∇
    :endsection

    ∇ r←a FREAD w;t
      t←w ⎕FSTIE 0
      r←⎕FREAD t a
      ⎕FUNTIE t
    ∇

    ∇ r←filename Fopen tieno
    ⍝ tieno is tie number and optionally 1 to tie exclusively
      tieno←2↑tieno ⍝ default to shares tie
      :Trap 22
          r←filename{⍵[2]:⍺ ⎕FTIE ⍵[1] ⋄ ⍺ ⎕FSTIE ⍵}tieno
      :Else
          r←filename{⍵[2]:⍵[3] ⋄ ⍺ ⎕FSTIE ⍵[2]⊣⎕FUNTIE ⍵[3]}tieno,filename ⎕FCREATE tieno[1]
      :EndTrap
    ∇
:EndNamespace

:Namespace Files

⍝ Provides cover functions for many file operations
⍝ Eventually all will be implemented for both Windows and *nix

    (⎕IO ⎕ML)←1

    ∇ r←data Append name
     ⍝ Append data to existing file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND ⍵}Nopen name
    ∇

    ∇ r←text AppendText name;tn
     ⍝ Append text to existing file (must be single byte text)
      tn←Nopen name
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ (tn name)←{ext}CreateTemp folder;cnt
     ⍝ Create a temp file
     ⍝ folder - folder to create temp file in
     ⍝ ext - file extension
      :If 0=⎕NC'ext' ⋄ ext←'tmp' ⋄ :EndIf
      :If 0∊⍴ext ⋄ ext←'tmp' ⋄ :EndIf
      cnt←tn←0
      :Repeat
          name←folder,('/\'∊⍨¯1↑folder)↓'/',⎕A[?8⍴⍴⎕A],'.',ext
          :Trap 22 ⋄ tn←name ⎕NCREATE 0 ⋄ :EndTrap
          cnt+←1
      :Until (cnt>20)∨tn≠0
      'Unable to create temporary file'⎕SIGNAL tn↓22
    ∇

    ∇ {protect}Copy FmTo;CopyFileX;GetLastError ⍝ Copy file Fm -> To
      :Select APLVersion
      :Case '*nix'
          ∘
      :Case 'Win'
          :If 0=⎕NC'protect'     ⍝ Copy fails if <protect> and 'To' exists.
              protect←0          ⍝ Default unprotected copy.
          :EndIf
          'CopyFileX'⎕NA'I kernel32.C32∣CopyFile* <0T <0T I'
          :If 0=CopyFileX FmTo,protect
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'CopyFile error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Delete name;DeleteFileX;GetLastError;FindFirstFile;FindNextFile;FindClose;handle;rslt;ok;next;⎕IO;path
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          _SH'rm ',unixfix name
      :Case 'Win'
          'DeleteFileX'⎕NA'I kernel32.C32∣DeleteFile* <0T'
          ⎕NA'I4 kernel32.C32|GetLastError'
          :If ∨/'*?'∊name ⍝ wildcards?
              path←{(⌽∨\⌽⍵∊'\/')/⍵}name
              _FindDefine
              handle rslt←_FindFirstFile name
              :If 0=handle
                  :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
              :EndIf
              :If '.'≠⊃6⊃rslt
                  {}DeleteFileX⊂path,6⊃rslt
              :EndIf
              :While 1=0⊃ok next←_FindNextFile handle
                  :If '.'≠⊃6⊃next
                      {}DeleteFileX⊂path,6⊃next
                  :EndIf
              :EndWhile
              :If 0 18∨.≠ok next
                  ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
              :EndIf
              {}FindClose handle
          :Else
              :If 0=DeleteFileX⊂name
                  11 ⎕SIGNAL⍨'DeleteFile error:',⍕GetLastError
              :EndIf
          :EndIf
      :EndSelect
    ∇

    ∇ r←DirExists path
      r←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :Trap 11
              r←0<⍴_SH'ls -adl ',unixfix path
          :EndTrap
      :Case 'Win'
          r←{1≠⊃⍴⍵:0 ⋄ ⍵[1;4]}'.'List path
      :EndSelect
    ∇

    ∇ r←Exists name
     ⍝ Does file exist?
      r←1
      :Trap 22
          :Trap 19 ⍝ file access error means file exists
              ⎕NUNTIE(unixfix name)⎕NTIE 0
          :EndTrap
      :Else
          r←0
      :EndTrap
    ∇

    ∇ r←GetCurrentDirectory;GCD;GetLastError
     ⍝ Get Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          r←⊃_SH'pwd'
      :Case 'Win'
          'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
          :If 0≠1⊃r←GCD 256 256
              r←2⊃r
          :Else
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Chars←GetText name;nid;signature;nums;sz;b
     ⍝ Read ANSI or Unicode character file
      sz←⎕NSIZE nid←(unixfix name)⎕NTIE 0
      signature←⎕NREAD nid 83 3 0
      :If signature≡¯17 ¯69 ¯65 ⍝ UTF-8?
          nums←⎕NREAD nid 83 sz
          Chars←'UTF-8'⎕UCS 256|nums ⍝ Signed ints
      :ElseIf ∨/b←(2↑signature)∧.=2 2⍴¯1 ¯2 ¯2 ⍝ Unicode (UTF-16)
          Chars←{,⌽(2,⍨2÷⍨⍴⍵)⍴⍵}⍣(⊣/b)⎕NREAD nid 83 sz 2
          Chars←'UTF-16'⎕UCS(2*16)|163 ⎕DR Chars
      :Else ⍝ ANSI or UTF-8
          Chars←{11::⎕UCS ⍵ ⋄ 'UTF-8'⎕UCS ⍵}256|⎕NREAD nid 83 sz 0
      :EndIf
      ⎕NUNTIE nid
    ∇

    ∇ vtv←GetVTV name
     ⍝ Read ANSI or Unicode character file as vector of text vectors
      vtv←{1↓¨(v=n)⊂v←(n←⎕UCS 10),⍵}(GetText name)~⎕UCS 13
    ∇

    ∇ r←LikelyURL w
      →0↓⍨r←(⎕DR w)∊80 82
      r←{(0∊⍴⍵)<∧/⍵∊'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%-._~:/?#[]@!$&''()*+,;='}w ⍝ identify likely URIs
    ∇

    ∇ r←{pattern}List path;z;rslt;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError;isFile;filter;isFolder
    ⍝ path and pattern are related.
    ⍝ If there is no pattern (or pattern is empty)
    ⍝   If path ends with '/' or '\' then return information for the contents of the folder, otherwise, return information about the folder (or file) itself
    ⍝ If a non-empty pattern exists, it is used as a filter on the contents of path (path is treated as a folder name)
    ⍝ Examples:
    ⍝   List '/dir/foo'  ⍝ returns information about /dir/foo
    ⍝   List '/dir/foo/' ⍝ returns information about the contents of /dir/foo/
    ⍝   '*.dyalog' List '/dir/foo'  ⍝ returns information about the .dyalog files in /dir/foo/
    ⍝   List '/dir/foo/*.dyalog'    ⍝ also returns information about the .dyalog files in /dir/foo/

    ⍝ Information returned is:
    ⍝ [;0] Name [;1] Length [;2] LastAccessTime [;3] IsDirectory
      ⎕IO←0
      :If 0=⎕NC'pattern' ⋄ pattern←'' ⋄ :EndIf
      isFolder←'/\'∊⍨¯1↑path
      filter←''
      :If isFile←~0∊⍴pattern
          filter←isFolder↓'/',pattern
      :EndIf

      r←0 4⍴'' 0 0 0

      :Select APLVersion
      :Case '*nix'
          →(0∊⍴rslt←1 _SH'ls -al',isFolder↓'d --time-style=full-iso ',unixfix path,filter)⍴0
          rslt←↑rslt
          rslt←' ',('total '≡6⍴rslt)↓[0]rslt
          r←((1↑⍴rslt),4)⍴0
          z←∧⌿' '=rslt ⍝ entirely blank columns
          z←z∧10>+\z    ⍝ Do not split file names
          rslt←z⊂rslt
          r[;3]←'d'=(0⊃rslt)[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI,4⊃rslt ⍝ Size
          z←,(5⊃rslt),6⊃rslt ⋄ ((z∊'-:')/z)←' ' ⋄ z←((1↑⍴r),6)⍴1⊃⎕VFI z
          r[;2]←↓⌊z,1000×1|z[;5]                ⍝ Add msec to Timestamp
          r[;0]←{(⌽~∨\⌽⍵='/')/⍵}¨{(-+/∧\' '=⌽⍵)↓¨↓⍵}0 1↓8⊃rslt    ⍝ Name

      :Case 'Mac'
          →(0∊⍴rslt←1 _SH'stat -lt "%F %T %z" ',unixfix path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter)⍴0
          r←((1↑⍴rslt),4)⍴0
          rslt←↑{⎕ML←3 ⋄ ⍵⊂⍨~{⍵∧9>+\⍵}' '=⍵}¨rslt
          r[;3]←'d'=0⊃¨rslt[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃¨⎕VFI¨rslt[;4]       ⍝ Size
          z←↑∊¨↓{w←⍵ ⋄ ((w∊'-:')/w)←' ' ⋄ 1⊃⎕VFI w}¨rslt[;5 6] ⍝
          r[;2]←↓z,0                            ⍝ 0 msec for MacOS to Timestamp
          r[;0]←rslt[;8]                        ⍝ Name

      :Case 'Win'
      ⍝ See DirX for explanations of results of _FindNextFile etc
          _FindDefine
          handle rslt←_FindFirstFile path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter
          :If 0=handle
              :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          →(0∊⍴rslt←↓[0]↑rslt)⍴0
          r←((1↑⍴0⊃rslt),4)⍴0
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt    ⍝ Get attributes into bits
          r[;3]←(0⊃rslt)[;27]              ⍝ IsDirectory?
          r[;1]←0(2*32)⊥⍉↑4⊃rslt           ⍝ combine size elements
          r[;2]←_Filetime_to_TS¨3⊃rslt     ⍝ As ⎕TS vector
          r[;0]←6⊃rslt                     ⍝ Name
      :EndSelect
      r←r[⍋↑r[;0];]
    ∇

    ∇ MkDir path;CreateDirectory;GetLastError;err
      ⍝ Create a folder
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :If ~DirExists path
              1 _SH'mkdir ',unixfix path
              ('mkdir error on ',path)⎕SIGNAL 11/⍨~DirExists path
          :EndIf
      :Case 'Win'
          ⎕NA'I kernel32.C32∣CreateDirectory* <0T I4' ⍝ Try for best function
          →(0≠CreateDirectory path 0)⍴0 ⍝ 0 means "default security attributes"
          ⎕NA'I4 kernel32.C32|GetLastError'
          err ⎕SIGNAL⍨'CreateDirectory error:',⍕err←GetLastError
      :EndSelect
    ∇

    ∇ Move filenames;MoveFileX;MoveFileExA;GetLastError;err
    ⍝ Move (from to) - move/rename file
      :Select APLVersion
      :Case '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣MoveFileEx* <0T <0T I4' ⍝ Try for best function
          :If 0≠MoveFileExA filenames,3 ⍝ REPLACE_EXISTING (1) + COPY_ALLOWED (2)
              :Return
          :EndIf
          ⎕NA'I4 kernel32.C32|GetLastError'
          :Select err←GetLastError
          :Case 120                     ⍝ ERROR_CALL_NOT_IMPLIMENTED
              'MoveFileX'⎕NA'I Kernel32.C32∣MoveFile* <0T <0T' ⍝ accept 2nd best - win 95
              :If 0≠MoveFileX filenames
                  :Return
              :EndIf
              err←GetLastError
          :EndSelect
          11 ⎕SIGNAL⍨'MoveFile error:',⍕err
      :EndSelect
    ∇

    ∇ tn←Nopen name
      :Trap 0
          tn←name ⎕NCREATE 0
      :Else
          tn←name ⎕NTIE 0
      :EndTrap
    ∇

    ∇ r←data Put name
     ⍝ Write data to file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND(0 ⎕NRESIZE ⍵)(⎕DR data)}Nopen name
    ∇

    ∇ r←text PutText name;tn
     ⍝ Write text to file (must be single byte text)
      tn←Nopen name
      0 ⎕NRESIZE tn
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ RmDir path;RemoveDirectoryA;GetLastError
     ⍝ Remove folder/directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣RemoveDirectory* <0T'
          →(0≠RemoveDirectory,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'RemoveDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ SetCurrentDirectory path;SCD;GetLastError
     ⍝ Set Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          'SCD'⎕NA'I kernel32.C32∣SetCurrentDirectory* <0T'
          →(0≠SCD,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'SetCurrentDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ r←{noext}SplitFilename filename;filesep;mask;path;file;ext
    ⍝ splits a filename into: path name ext
      noext←{6::0 ⋄ noext}''
      filesep←(~isWin)↓'\/'
      mask←⌽∨\⌽filename∊filesep
      path←mask/filename
      file←(~mask)/filename
      :If noext
          r←path file
      :Else
          mask←∨\⌽<\⌽'.'=file
          ext←mask/file
          file←(~mask)/file
          r←path file ext
      :EndIf
    ∇

    :Section Windows
    ∇ rslt←_Filetime_to_TS filetime;⎕IO
      :If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
      :OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
          rslt←0 0                   ⍝ if either call failed then zero the time elements
      :EndIf
      rslt←1 1 0 1 1 1 1 1/1⊃rslt    ⍝ remove day of week
    ∇

    ∇ _FindDefine;WIN32_FIND_DATA
      :If 0=⎕NC'FindFirstFileX'
          WIN32_FIND_DATA←'{I4 {I4 I4} {I4 I4} {I4 I4} {U4 U4} {I4 I4} T[260] T[14]}'
          'FindFirstFileX'⎕NA'I4 kernel32.C32|FindFirstFile* <0T >',WIN32_FIND_DATA
          'FindNextFileX'⎕NA'U4 kernel32.C32|FindNextFile* I4 >',WIN32_FIND_DATA
          ⎕NA'kernel32.C32|FindClose I4'
          ⎕NA'I4 kernel32.C32|FileTimeToLocalFileTime <{I4 I4} >{I4 I4}'
          ⎕NA'I4 kernel32.C32|FileTimeToSystemTime <{I4 I4} >{I2 I2 I2 I2 I2 I2 I2 I2}'
          ⎕NA'I4 kernel32.C32∣GetLastError'
      :EndIf
    ∇

    ∇ rslt←_FindFirstFile name;⎕IO
      rslt←FindFirstFileX name(⎕IO←0)
      :If ¯1=0⊃rslt                   ⍝ INVALID_HANDLE_VALUE
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0        ⍝ shorten the file name at the null delimiter
          (1 7⊃rslt)_FindTrim←0        ⍝ and for the alternate name
      :EndIf
    ∇

    ∇ rslt←_FindNextFile handle;⎕IO
      rslt←FindNextFileX handle(⎕IO←0)
      :If 1≠0⊃rslt
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0             ⍝ shorten the filename
          (1 7⊃rslt)_FindTrim←0             ⍝ shorten the alternate name
      :EndIf
    ∇

    ∇ name←name _FindTrim ignored;⎕IO
     ⍝ Truncates a character vector at the null delimiting byte.
     ⍝ The null is not included in the result.
      ⎕IO←0
      name↑⍨←name⍳⎕UCS 0
    ∇

    ∇ {r}←{suppress}_CMD cmd
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕CMD cmd
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ r←isWin
      r←'Win'≡APLVersion
    ∇

    :endsection

    :section *nix
    ∇ {r}←{suppress}_SH cmd
    ⍝ SH cover to suppress any error messages
    ⍝ suppress will suppress error from being signaled
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕SH cmd,' 2>/dev/null'
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ f←unixfix f
    ⍝ replaces Windows file separator \ with Unix file separator /
    ⍝ this approach is mindnumbingly simple and probably dangerous
    ⍝ which is why we call unixfix very cautiously
      :If (⊂APLVersion)∊'*nix' 'Mac' ⋄ ((f='\')/f)←'/' ⋄ :EndIf
    ∇

    ∇ r←APLVersion
      :Select 3↑⊃'.'⎕WG'APLVersion'
      :CaseList 'Lin' 'AIX' 'Sol'
          r←'*nix'
      :Case 'Win'
          r←'Win'
      :Case 'Mac'
          r←'Mac'
      :Else
          ∘∘∘ ⍝ unknown version
      :EndSelect
    ∇
    :endsection

    ∇ r←a FREAD w;t
      t←w ⎕FSTIE 0
      r←⎕FREAD t a
      ⎕FUNTIE t
    ∇

    ∇ r←filename Fopen tieno
    ⍝ tieno is tie number and optionally 1 to tie exclusively
      tieno←2↑tieno ⍝ default to shares tie
      :Trap 22
          r←filename{⍵[2]:⍺ ⎕FTIE ⍵[1] ⋄ ⍺ ⎕FSTIE ⍵}tieno
      :Else
          r←filename{⍵[2]:⍵[3] ⋄ ⍺ ⎕FSTIE ⍵[2]⊣⎕FUNTIE ⍵[3]}tieno,filename ⎕FCREATE tieno[1]
      :EndTrap
    ∇
:EndNamespace


jqAddClassSimple QA

jqAddClassSimple QA

 msg←Test dummy;output;button;Then;Is
 (output button)←Find¨'output' 'button'
 Is←{⍵≡output.GetAttribute⊂'class'}
 Then←{
     ~0∊⍴msg←'Wrong initial class'/⍨~Is ⍺:msg
     ~0∊⍴msg←'Missing transistion'/⍨(⍺≢⍵)∧Is ⍵⊣⎕DL 0.1⊣Click button:msg
     ~0∊⍴msg←'Wrong final class'/⍨~Is ⍵⊣⎕DL 1:msg
     ''
 }

 :If 1 ⍝ for consistency and easy of inserting more tests

 :AndIf 0=⍴msg←''Then'redclass' ⍝ transition to new class

 :AndIf 0=⍴msg←'redclass'Then'redclass' ⍝ make sure it doesn't toggle

 :EndIf


bench function

bench function

$dot ← {
  WA ← (1↓⍴⍵),⍴⍺
  KA ← (⊃⍴⍴⍺)-1
  VA ← ⍳ ⊃ ⍴WA
  ZA ← (KA⌽¯1↓VA),¯1↑VA
  TA ← ZA⍉WA⍴⍺    ⍝ Replicate, transpose
  WB ← (¯1↓⍴⍺),⍴⍵
  KB ← ⊃ ⍴⍴⍺
  VB ← ⍳ ⊃ ⍴WB
  ZB0 ← (-KB) ↓ KB ⌽ ⍳(⊃⍴VB)
  ZB ← (¯1↓(⍳ KB)),ZB0,KB
  TB ← ZB⍉WB⍴⍵    ⍝ Replicate, transpose
  ⍺⍺ / TA ⍵⍵ TB   ⍝ Compute the result
}

$out ← {
  A ← ⍺
  B ← ⍵
  T ← (⊃(⍴⍴A))⌽⍳⊃⍴(⍴B),⍴A
  x ← T ⍉ ((⍴B),(⍴A)) ⍴ A
  y ← ((⍴A),(⍴B)) ⍴ B
  x ⍺⍺ y
}

$slashbar ← {
  ⍉ ⍺⍺ / ⍉ ⍵
}

$log ← {
  ⍝ Dyadic logarithm
  (⍟⍵)÷⍟⍺
}

ReadCSVDouble ← ⎕ReadDoubleVecFile
ReadCSVInt ← ⎕ReadIntVecFile
xor ← ⎕INT32XOR
and ← ⎕INT32AND
sll ← ⎕INT32SHL
srl ← ⎕INT32SHR
testBit ← { 0≠⍵ and 1 sll (⍺-1) }

now ← ⎕NOW

⍝ [(f bench n) 0] runs f n times and prints the avg time in ms.
bench ← ⎕BENCH

$dot ← {
  WA ← (1↓⍴⍵),⍴⍺
  KA ← (⊃⍴⍴⍺)-1
  VA ← ⍳ ⊃ ⍴WA
  ZA ← (KA⌽¯1↓VA),¯1↑VA
  TA ← ZA⍉WA⍴⍺    ⍝ Replicate, transpose
  WB ← (¯1↓⍴⍺),⍴⍵
  KB ← ⊃ ⍴⍴⍺
  VB ← ⍳ ⊃ ⍴WB
  ZB0 ← (-KB) ↓ KB ⌽ ⍳(⊃⍴VB)
  ZB ← (¯1↓(⍳ KB)),ZB0,KB
  TB ← ZB⍉WB⍴⍵    ⍝ Replicate, transpose
  ⍺⍺ / TA ⍵⍵ TB   ⍝ Compute the result
}

$out ← {
  A ← ⍺
  B ← ⍵
  T ← (⊃(⍴⍴A))⌽⍳⊃⍴(⍴B),⍴A
  x ← T ⍉ ((⍴B),(⍴A)) ⍴ A
  y ← ((⍴A),(⍴B)) ⍴ B
  x ⍺⍺ y
}

$slashbar ← {
  ⍉ ⍺⍺ / ⍉ ⍵
}

$log ← {
  ⍝ Dyadic logarithm
  (⍟⍵)÷⍟⍺
}

ReadCSVDouble ← ⎕ReadDoubleVecFile
ReadCSVInt ← ⎕ReadIntVecFile
xor ← ⎕INT32XOR
and ← ⎕INT32AND
sll ← ⎕INT32SHL
srl ← ⎕INT32SHR
testBit ← { 0≠⍵ and 1 sll (⍺-1) }

now ← ⎕NOW

bench ← {
  init ← ⍵
  f ← ⍺⍺
  g ← { ⍵ ⋄ f init }
  r ← ⌷g init
  t0 ← now 0
  r ← ⌷(g ⍣ ⍺) r
  t1 ← now 1
  ⎕ ← 'ITERATIONS: '
  ⎕ ← ⍺
  ⎕ ← 'RESULT: '
  ⎕ ← ⍵⍵ r
  ⎕ ← 'AVGTIMING: '
  (t1-t0)÷⍺
}