Version 0 of Preprocessing and Radical Language Modification

Updated 2002-04-23 21:44:15

I often run into syntactic prejudice when I am writing software to spec. This often seems to preclude using Tcl, which, of course, tends to bloat the amount of work and the time and money needed to accomplish it. Eventually, however, I came up with the "run" command - it's a replacement for "source" but it does some extensive preprocessing before running the code, allowing some really radical language changes without any run-time overhead.

run.tcl:

proc run { filename { macrolist "" } } {

  if { "$macrolist" != "" } {
    upvar $macrolist macros
  }
  if [catch { set f [ open $filename r ] } err ] { return -code $err }
  set src [ read $f ]
  foreach key [array names macros] {
    regsub -all $key $src $macros($key) src
  }
  set exp ""
  while 1 {
    if &#91;regexp "(.*)(<<<.*>>>)(.*)" $src -> head exp tail&#93; &#123;
      regsub <<< $exp "" exp
      regsub >>> $exp "" exp
      set result &#91; uplevel eval $exp &#93;
      set src "$head$result$tail"
    &#125; else &#123;
      break
    &#125;
  &#125;
  # puts $src ;# uncomment to see translation
  uplevel eval $src

&#125;

Now, suppose we wish to add some syntactic sugar to expr, and to have an input language looking like Oberon or Modula. We code this:

source run.tcl # source deref.tcl

proc compute &#123; args &#125; &#123;

  set exp ""
  set id ""
  regsub "''" &#91; string trim $args &#93; "@@@" args
  while 1 &#123;
    regexp "(\&#91;^a-zA-Z_'\&#93;*)(\&#91;a-zA-Z0-9_'\&#93;*)(.*)" $args -> head id tail
    if !&#91; string length $id &#93; &#123;
      set exp "$exp$head"
      break
    &#125;
    set dollar ""
    if !&#91; string equal &#91; string index $id 0 &#93; "'" &#93; &#123;
      if !&#91; string equal &#91;info commands $id&#93; "" &#93; &#123;
        set id "\&#91; $id"
        regexp &#123;&#91;^\(&#93;*\((&#91;^\)&#93;*)\)(.*)&#125; $tail -> params tail
        set tail " $params \&#93;$tail"
      &#125; else &#123; set dollar "\$" &#125;
    &#125;
    append exp "$head$dollar$id"
    set args $tail
  &#125;
  regsub -all "'" $exp "\"" exp
  set map "@@@ ' and && or || not ! <> != true 1 false 0 on 1 off 0 yes 1 no 0 pi 3.1415926535"
  foreach &#123; from to &#125; $map &#123;
    regsub $from $exp $to exp
  &#125;
  set exp &#91; uplevel subst -novariable \&#123;$exp\&#125; &#93;
  return "\&#123; $exp \&#125;"

&#125;

set xlate(IF) "if <<< compute " set xlate(THEN) ">>> \&#123;"" set xlate(ELSE) "\&#125; else \&#123;" set xlate(ELSIF) "\&#125; elseif \&#91; compute " set xlate(END) "\&#125;" set xlate(WHILE) "while \&#123; \&#91; compute " set xlate(DO) "\&#93; \&#125; \&#123;"

"compute" is a macro that handles the translation of "expr", and "xlate" is a simple textual substitution table. Now we can take the file "foo.tcl":

# basic tcl syntax is still there: set x 1

# So is new modula-style IF x <> 1 THEN

  puts "x is NOT 1"

ELSE

  puts "x IS 1"

END

and run it from a tcl script:

run foo.tcl xlate

and what is actually sourced is:

set x 1

if &#123; $x != 1 &#125; &#123;

  puts "x is NOT 1"

&#125; else &#123;

  puts "x IS 1"

&#125;

> !v vi temp > cat temp

I often run into syntactic prejudice when I am writing software to spec. This often seems to preclude using Tcl, which, of course, tends to bloat the amount of work and the time and money needed to accomplish it. Eventually, however, I came up with the "run" command - it's a replacement for "source" but it does some extensive preprocessing before running the code, allowing some really radical language changes without any run-time overhead.

run.tcl:

proc run &#123; filename &#123; macrolist "" &#125; &#125; &#123;

  if &#123; "$macrolist" != "" &#125; &#123;
    upvar $macrolist macros
  &#125;
  if &#91;catch &#123; set f &#91; open $filename r &#93; &#125; err &#93; &#123; return -code $err &#125;
  set src &#91; read $f &#93;
  foreach key &#91;array names macros&#93; &#123;
    regsub -all $key $src $macros($key) src
  &#125;
  set exp ""
  while 1 &#123;
    if &#91;regexp "(.*)(<<<.*>>>)(.*)" $src -> head exp tail&#93; &#123;
      regsub <<< $exp "" exp
      regsub >>> $exp "" exp
      set result &#91; uplevel eval $exp &#93;
      set src "$head$result$tail"
    &#125; else &#123;
      break
    &#125;
  &#125;
  # puts $src ;# uncomment to see translation
  uplevel eval $src

&#125;

Now, suppose we wish to add some syntactic sugar to expr, and to have an input language looking like Oberon or Modula. We code this:

source run.tcl # source deref.tcl

proc compute &#123; args &#125; &#123;

  set exp ""
  set id ""
  regsub "''" &#91; string trim $args &#93; "@@@" args
  while 1 &#123;
    regexp "(&#92;&#91;^a-zA-Z_'&#92;&#93;*)(&#92;&#91;a-zA-Z0-9_'&#92;&#93;*)(.*)" $args -> head id tail
    if !&#91; string length $id &#93; &#123;
      set exp "$exp$head"
      break
    &#125;
    set dollar ""
    if !&#91; string equal &#91; string index $id 0 &#93; "'" &#93; &#123;
      if !&#91; string equal &#91;info commands $id&#93; "" &#93; &#123;
        set id "&#92;&#91; $id"
        regexp &#123;&#91;^&#92;(&#93;*&#92;((&#91;^&#92;)&#93;*)&#92;)(.*)&#125; $tail -> params tail
        set tail " $params &#92;&#93;$tail"
      &#125; else &#123; set dollar "&#92;$" &#125;
    &#125;
    append exp "$head$dollar$id"
    set args $tail
  &#125;
  regsub -all "'" $exp "&#92;"" exp
  set map "@@@ ' and && or || not ! <> != true 1 false 0 on 1 off 0 yes 1 no 0 pi 3.1415926535"
  foreach &#123; from to &#125; $map &#123;
    regsub $from $exp $to exp
  &#125;
  set exp &#91; uplevel subst -novariable &#92;&#123;$exp&#92;&#125; &#93;
  return "&#92;&#123; $exp &#92;&#125;"

&#125;

set xlate(IF) "if <<< compute " set xlate(THEN) ">>> &#92;&#123;"" set xlate(ELSE) "&#92;&#125; else &#92;&#123;" set xlate(ELSIF) "&#92;&#125; elseif &#92;&#91; compute " set xlate(END) "&#92;&#125;" set xlate(WHILE) "while &#92;&#123; &#92;&#91; compute " set xlate(DO) "&#92;&#93; &#92;&#125; &#92;&#123;"

"compute" is a macro that handles the translation of "expr", and "xlate" is a simple textual substitution table. Now we can take the file "foo.tcl":

# basic tcl syntax is still there: set x 1

# So is new modula-style IF x <> 1 THEN

  puts "x is NOT 1"

ELSE

  puts "x IS 1"

END

and run it from a tcl script:

run foo.tcl xlate

and what is actually sourced is:

set x 1

if &#123; $x != 1 &#125; &#123;

  puts "x is NOT 1"

&#125; else &#123;

  puts "x IS 1"

&#125;