Q7

ECL variables support

Details

  • Type: Improvement Improvement
  • Status: Resolved Resolved
  • Priority: Major Major
  • Resolution: Fixed
  • Affects Version/s: 1.3.3
  • Fix Version/s: 1.3.6
  • Component/s: ECL
  • Labels:
    None
  • Test Mode:
    Manual

Description

Introduction

Introducing two kinds of variables: pipes and vals.

  • pipe – a named FIFO queue. Once the value is read from pipe, it is gone.
  • val – a single object. Can be read as many times, as needed

Few /internal/ commands to manipilate these vars:

  • set valName – sets named value with a first value from input pipe. If value is not registered in current scope, generates runtime error. If pipe is empty, val remains unset/retains old value
  • get valName – gets named value. If value is not registered or not set, generates runtime error
  • read pipeName count – reads up to count values (or all values if count == -1) from named pipe and writes them into output pipe. Runtime error if pipe is not registered
  • write pipeName – reads values from input pipe and writes into named pipe

Syntax sugar

In ECL scripts pipes and variables can be used by using $<pipeOrValName>. Parser validates that names are valid (to be described later) and performs replacement of $<name> with one of internal commands by using the following rules:

command | $val // command | set val
$val | command // get val | command
command $val // command [get val]

$pipe | $val // read pipe 1 | set val
$val | $pipe // get val | write pipe


command | $pipe // command | write pipe

command $pipe // determines an object count to take according to command argument,
              // if an argument has multiplicity 1, then:
              //   command [read pipe 1]
              // if an argument is unbound, then:
              //   command [read pipe -1]

$pipe | command // if command has input parameter, same as above, otherwise
                // read pipe -1 | command

Variables declaration

Two more internal commands can be used to create a 'variable' object:

  • pipe name [values] [-input] – Constructs a PipeDeclaration object. If values are given, then it will have this set of values inside, if -input argument is given, then it will be initialized with a conents of a caller's input pipe. Examples:
        pipe foo // creates a Pipe object with empty contents
        pipe foo 1 2 3  // creates a Pipe object with encapsulated [1,2,3] values
        pipe foo -input // creates a Pipe object with values to be taken from 
                        // input pipe
    
  • val name [value] [-input] – Constructs a ValDeclaration object. If value is given, val will have this value, if '-input is given, then it will be initialized with a value from input pipe. If neither 'value', nor '-input' is set, then such value can be used only in proc.
        val bar // creates a Val object without value
        val bar 42 // creates a Val object with value 42
        val bar $pipe // creates a Val object with value taken from pipe
    

By itself these commands are pretty useless, but they make a perfect sence when it comes to proc and let

  • proc var... body – Creates a new procedure with given pipes/values
  • let var... body – Executes in-place command with given pipes/value
    • Examples
  1. Filling values to New Java class dialog.

Using 'proc':

        proc fill-dialog [val window -input] [val className] [val packageName] {
            with $window {
                get-editbox -after [get-label "Class"] | set-text $className
                get-editbox -after [get-label "Package"] | set-text $packageName
            }
        }
        
        get-window "New Java Class" | fill-dialog Program org.example
      

Using 'let':

        get-window "New Java Class" | let [val className Program] [val packageName org.example] {
            get-editbox -after [get-label "Class"] | set-text $className    
            get-editbox -after [get-label "Package"] | set-text $packageName
        }
      
  1. Reusing a value from UI (inspired by ticket #299)

Using 'let'

        with [get-editor "Navigation.java"] {
            let [val currentLine 
                     [get-text-viewer | get-property "caretPosition.line" -raw | str]]
                [val rulerColumn 
                     [get-left-ruler | get-ruler-column AnnotationColumn]] {
        
                $rulerColumn | hover-ruler $currentLine
            }
        }
      

Using 'proc'

        proc hover-current-line [val editor -input] [val columnName] {
            let [val currentLine 
                     [$editor | get-text-viewer | get-property "caretPosition.line" -raw | str]]
                [val rulerColumn
                     [$editor | get-left-ruler | get-ruler-column $columnName]] {
                
                $rulerColumn | hover-ruler $currentLine
            }
        }
      
  1. Repeating actions based on CSV values (inspired by ticket #344)

Using 'let'

        // get-rows writes list of Row objects into output pipe
        read-csv-file "data.csv" | get-rows | foreach {
            // get-values writes to output all row values
            get-values | let [val greeting -input] {
                // See Caveat #2
                with [void | get-editor "MyEditor"] {
                    get-editbox -after [get-label "Name: "] | set-text $greeting
                    get-button "Run Message" | click
                    get-editbox -after [get-label "Message"] | 
                         get-property text |
                         equals $greeting | verify-true
                }
            }
        }
      

Using 'proc'

        // this proc does not use input pipe
        proc send-echo-once [val editor] [val greeting] {
            with $editor {
                get-editbox -after [get-label "Name: "] | set-text $greeting        
                get-button "Run Message" | click                            
                get-editbox -after [get-label "Message"] |                  
                     get-property text |                                    
                     equals $greeting | verify-true 
            }
        }
        
        
        proc send-echo [val editor -input] [val file] {
            read-csv-file "data.csv" | get-rows | foreach {
                // That's an example how a value from input pipe (foreach semantics)
                // is being passed as an argument to other command
                let [val greeting [get-values]] {
                    send-echo-once $editor $greeting
                }
            }
        }
        
        get-editor "MyEditor" | send-echo "data.csv"
      

Caveats

  1. Initially I did not plan an '-input' argument for val/proc and thought that this can be 'emulated' just by using implicit declaration of $in pipe (which aliases to current input of current 'let' command. So that this script should write "1 2 3" to log:
          emit 1 2 3 | let [val one $in] [val two $in] [val three $in] { 
              log [concat $one $two $three] 
          } 
          

    But our current evaluation model works in a way that all three 'val' commands will get the same input and output would be "1 1 1". However now I like an '-input' parameter even more than 'automagic' $in.

  1. 'let' should pass its input as an input to its body to reduce surprise so that scripts like this work (i.e. any block nested into 'with' can be wrapped into a 'let'):
             with [get-window "New Java Class"] {
                 let [val className foo] [val packageName bar] {
                      get-editbox -after [get-label "Name:"] | set-text $className
                      get-editbox -after [get-label "Package:"] | set-text $packageName
                 }
             }
           

    But this creates a problem that if we are using 'let' inside some 'foreach', inner script is being 'trapped' into parent's pipe. I see several possible solutions:

    • Implement 'let' in that way that if it has '-input' vals, 'body' does not receive an input. Probably that's a way to go!
    • have a command 'void' which returns an empty pipe
    • control an input of 'let' script by some special argument '-input' which can accept arbitrary value:
                let -input 100500 {
                }
                //is equivalent to 
                with 100500 {
                }
                
                get-window Foo | let -input [void] { cmd }
                // is equivalent to 
                cmd
              
  2. Current ECL cannot correctly handle when argument vith variable argument count is not the last, but that's exactly what we have in 'let':
    #+BEGIN_SRC fundamental
            let decl body
            let decl1 decl2 body
            let decl1 decl2 decl3 body
          

However this should be easy to fix (worst case scenario: under-the-hood hardcoded special binding of args for 'let' and 'proc'.

Interesting consequences

  1. Everything described here for now can be implemented without modifying parser/compiler (its possbile to use internal commands for now without '$'-expansion)
  2. Without breaking a compatibility we can modify a 'foreach' implementation so that it also accepts list of variables:
            //old way. 'log' takes message from input
            emit a b c | foreach { log } 
            //new way. Value from input is grabbed and 
            emit a b c | foreach [val msg -input] { log -message $msg]
          
  3. Probably we don't need named pipes yet, as for three use cases above I did not use them at all.

Activity

There are no comments yet on this issue.

People

  • Assignee:
    komaz
    Reporter:
    komaz
Vote (0)
Watch (0)

Dates

  • Created:
    27/Jun/13 5:57 PM
    Updated:
    02/Sep/13 4:00 PM
    Resolved:
    02/Sep/13 4:00 PM