home *** CD-ROM | disk | FTP | other *** search
/ ftp.cs.arizona.edu / ftp.cs.arizona.edu.tar / ftp.cs.arizona.edu / icon / historic / v941.tgz / icon.v941src.tar / icon.v941src / ipl / progs / rsg.icn < prev    next >
Text File  |  2002-03-26  |  11KB  |  392 lines

  1. ############################################################################
  2. #
  3. #    File:     rsg.icn
  4. #
  5. #    Subject:  Program to generate randomly selected sentences
  6. #
  7. #    Author:   Ralph E. Griswold
  8. #
  9. #    Date:     March 26, 2002
  10. #
  11. ############################################################################
  12. #
  13. #   This file is in the public domain.
  14. #
  15. ############################################################################
  16. #  
  17. #     This program generates randomly selected strings (``sen-
  18. #  tences'') from a grammar specified by the user.  Grammars are
  19. #  basically context-free and resemble BNF in form, although there
  20. #  are a number of extensions.
  21. #
  22. ############################################################################
  23. #  
  24. #     The program works interactively, allowing the user to build,
  25. #  test, modify, and save grammars. Input to rsg consists of various
  26. #  kinds of specifications, which can be intermixed:
  27. #  
  28. #     Productions define nonterminal symbols in a syntax similar to
  29. #  the rewriting rules of BNF with various alternatives consisting
  30. #  of the concatenation of nonterminal and terminal symbols.  Gen-
  31. #  eration specifications cause the generation of a specified number
  32. #  of sentences from the language defined by a given nonterminal
  33. #  symbol.  Grammar output specifications cause the definition of a
  34. #  specified nonterminal or the entire current grammar to be written
  35. #  to a given file.  Source specifications cause subsequent input to
  36. #  be read from a specified file.
  37. #  
  38. #     In addition, any line beginning with # is considered to be a
  39. #  comment, while any line beginning with = causes the rest of that
  40. #  line to be used subsequently as a prompt to the user whenever rsg
  41. #  is ready for input (there normally is no prompt). A line consist-
  42. #  ing of a single = stops prompting.
  43. #  
  44. #  Productions: Examples of productions are:
  45. #  
  46. #          <expr>::=<term>|<term>+<expr>
  47. #          <term>::=<elem>|<elem>*<term>
  48. #          <elem>::=x|y|z|(<expr>)
  49. #  
  50. #  Productions may occur in any order. The definition for a nonter-
  51. #  minal symbol can be changed by specifying a new production for
  52. #  it.
  53. #  
  54. #     There are a number of special devices to facilitate the defin-
  55. #  ition of grammars, including eight predefined, built-in nontermi-
  56. #  nal symbols:
  57. #     symbol   definition
  58. #     <lb>     <
  59. #     <rb>     >
  60. #     <vb>     |
  61. #     <nl>     newline
  62. #     <>       empty string
  63. #     <&lcase> any single lowercase letter
  64. #     <&ucase> any single uppercase letter
  65. #     <&digit> any single digit
  66. #  
  67. #  In addition, if the string between a < and a > begins and ends
  68. #  with a single quotation mark, it stands for any single character
  69. #  between the quotation marks. For example,
  70. #  
  71. #          <'xyz'>
  72. #  
  73. #  is equivalent to
  74. #  
  75. #          x|y|z
  76. #  
  77. #  Generation Specifications: A generation specification consists of
  78. #  a nonterminal symbol followed by a nonnegative integer. An exam-
  79. #  ple is
  80. #  
  81. #          <expr>10
  82. #  
  83. #  which specifies the generation of 10 <expr>s. If the integer is
  84. #  omitted, it is assumed to be 1. Generated sentences are written
  85. #  to standard output.
  86. #  
  87. #  Grammar Output Specifications: A grammar output specification
  88. #  consists of a nonterminal symbol, followed by ->, followed by a
  89. #  file name. Such a specification causes the current definition of
  90. #  the nonterminal symbol to be written to the given file. If the
  91. #  file is omitted, standard output is assumed. If the nonterminal
  92. #  symbol is omitted, the entire grammar is written out. Thus,
  93. #  
  94. #          ->
  95. #  
  96. #  causes the entire grammar to be written to standard output.
  97. #  
  98. #  Source Specifications: A source specification consists of @ fol-
  99. #  lowed by a file name.  Subsequent input is read from that file.
  100. #  When an end of file is encountered, input reverts to the previous
  101. #  file. Input files can be nested.
  102. #  
  103. #  Options: The following options are available:
  104. #  
  105. #       -s n Set the seed for random generation to n.
  106. #  
  107. #    -r   In the absence of -s, set the seed to 0 for repeatable
  108. #         results.  Otherwise the seed is set to a different value
  109. #         for each run (as far as this is possible). -r is equivalent
  110. #         to -s 0.
  111. #
  112. #       -l n Terminate generation if the number of symbols remaining
  113. #            to be processed exceeds n. The default is limit is 1000.
  114. #  
  115. #       -t   Trace the generation of sentences. Trace output goes to
  116. #            standard error output.
  117. #  
  118. #  Diagnostics: Syntactically erroneous input lines are noted but
  119. #  are otherwise ignored.  Specifications for a file that cannot be
  120. #  opened are noted and treated as erroneous.
  121. #  
  122. #     If an undefined nonterminal symbol is encountered during gen-
  123. #  eration, an error message that identifies the undefined symbol is
  124. #  produced, followed by the partial sentence generated to that
  125. #  point. Exceeding the limit of symbols remaining to be generated
  126. #  as specified by the -l option is handled similarly.
  127. #  
  128. #  Caveats: Generation may fail to terminate because of a loop in
  129. #  the rewriting rules or, more seriously, because of the progres-
  130. #  sive accumulation of nonterminal symbols. The latter problem can
  131. #  be identified by using the -t option and controlled by using the
  132. #  -l option. The problem often can be circumvented by duplicating
  133. #  alternatives that lead to fewer rather than more nonterminal sym-
  134. #  bols. For example, changing
  135. #  
  136. #          <term>::=<elem>|<elem>*<term>
  137. #  
  138. #  to
  139. #  
  140. #          <term>::=<elem>|<elem>|<elem>*<term>
  141. #  
  142. #  increases the probability of selecting <elem> from 1/2 to 2/3.
  143. #  
  144. #     There are many possible extensions to the program. One of the
  145. #  most useful would be a way to specify the probability of select-
  146. #  ing an alternative.
  147. #  
  148. ############################################################################
  149. #
  150. #  Links: options, random
  151. #
  152. ############################################################################
  153.  
  154. link options
  155. link random
  156.  
  157. global defs, ifile, in, limit, prompt, tswitch
  158.  
  159. record nonterm(name)
  160. record charset(chars)
  161.  
  162. procedure main(args)
  163.    local line, plist, s, opts
  164.                     # procedures to try on input lines
  165.    plist := [define,generate,grammar,source,comment,prompter,error]
  166.    defs := table()            # table of definitions
  167.    defs["lb"] := [["<"]]        # built-in definitions
  168.    defs["rb"] := [[">"]]
  169.    defs["vb"] := [["|"]]
  170.    defs["nl"] := [["\n"]]
  171.    defs[""] := [[""]]
  172.    defs["&lcase"] := [[charset(&lcase)]]
  173.    defs["&ucase"] := [[charset(&ucase)]]
  174.    defs["&digit"] := [[charset(&digits)]]
  175.  
  176.    opts := options(args,"tl+s+r")
  177.    limit := \opts["l"] | 1000
  178.    tswitch := \opts["t"]
  179.    &random := \opts["s"]
  180.    if /opts["s"] & /opts["r"] then randomize()
  181.  
  182.    ifile := [&input]            # stack of input files
  183.    prompt := ""
  184.    while in := pop(ifile) do {        # process all files
  185.       repeat {
  186.          if *prompt ~= 0 then writes(prompt)
  187.          line := read(in) | break
  188.          while line[-1] == "\\" do line := line[1:-1] || read(in) | break
  189.          (!plist)(line)
  190.          }
  191.       close(in)
  192.       }
  193. end
  194.  
  195. #  process alternatives
  196. #
  197. procedure alts(defn)
  198.    local alist
  199.    alist := []
  200.    defn ? while put(alist,syms(tab(upto('|') | 0))) do move(1) | break
  201.    return alist
  202. end
  203.  
  204. #  look for comment
  205. #
  206. procedure comment(line)
  207.    if line[1] == "#" then return
  208. end
  209.  
  210. #  look for definition
  211. #
  212. procedure define(line)
  213.    return line ?
  214.       defs[(="<",tab(find(">::=")))] := (move(4),alts(tab(0)))
  215. end
  216.  
  217. #  define nonterminal
  218. #
  219. procedure defnon(sym)
  220.    local chars, name
  221.    if sym ? {
  222.       ="'" &
  223.       chars := cset(tab(-1)) &
  224.       ="'"
  225.       }
  226.    then return charset(chars)
  227.    else return nonterm(sym)
  228. end
  229.  
  230. #  note erroneous input line
  231. #
  232. procedure error(line)
  233.    write("*** erroneous line:  ",line)
  234.    return
  235. end
  236.  
  237. #  generate sentences
  238. #
  239. procedure gener(goal)
  240.    local pending, symbol
  241.    pending := [nonterm(goal)]
  242.    while symbol := get(pending) do {
  243.       if \tswitch then
  244.          write(&errout,symimage(symbol),listimage(pending))
  245.       case type(symbol) of {
  246.          "string":   writes(symbol)
  247.          "charset":  writes(?symbol.chars)
  248.          "nonterm":  {
  249.             pending := ?\defs[symbol.name] ||| pending | {
  250.                write(&errout,"*** undefined nonterminal:  <",symbol.name,">")
  251.                break 
  252.                }
  253.             if *pending > \limit then {
  254.                write(&errout,"*** excessive symbols remaining")
  255.                break 
  256.                }
  257.             }
  258.          }
  259.       }
  260.    write()
  261. end
  262.  
  263. #  look for generation specification
  264. #
  265. procedure generate(line)
  266.    local goal, count
  267.    if line ? {
  268.       ="<" &
  269.       goal := tab(upto('>')) \ 1 &
  270.       move(1) &
  271.       count := (pos(0) & 1) | integer(tab(0))
  272.       }
  273.    then {
  274.       every 1 to count do
  275.          gener(goal)
  276.       return
  277.       }
  278.    else fail
  279. end
  280.  
  281. #  get right hand side of production
  282. #
  283. procedure getrhs(a)
  284.    local rhs
  285.    rhs := ""
  286.    every rhs ||:= listimage(!a) || "|"
  287.    return rhs[1:-1]
  288. end
  289.  
  290. #  look for request to write out grammar
  291. #
  292. procedure grammar(line)
  293.    local file, out, name
  294.    if line ? {
  295.       name := tab(find("->")) &
  296.       move(2) &
  297.       file := tab(0) &
  298.       out := if *file = 0 then &output else {
  299.          open(file,"w") | {
  300.             write(&errout,"*** cannot open ",file)
  301.             fail
  302.             }
  303.          }
  304.       }
  305.    then {
  306.       (*name = 0) | (name[1] == "<" & name[-1] == ">") | fail
  307.       pwrite(name,out)
  308.       if *file ~= 0 then close(out)
  309.       return
  310.       }
  311.    else fail
  312. end
  313.  
  314. #  produce image of list of grammar symbols
  315. #
  316. procedure listimage(a)
  317.    local s, x
  318.    s := ""
  319.    every x := !a do
  320.       s ||:= symimage(x)
  321.    return s
  322. end
  323.  
  324. #  look for new prompt symbol
  325. #
  326. procedure prompter(line)
  327.    if line[1] == "=" then {
  328.       prompt := line[2:0]
  329.       return
  330.       }
  331. end
  332.  
  333. #  write out grammar
  334. #
  335. procedure pwrite(name,ofile)
  336.    local nt, a
  337.    static builtin
  338.    initial builtin := ["lb","rb","vb","nl","","&lcase","&ucase","&digit"]
  339.    if *name = 0 then {
  340.       a := sort(defs,3)
  341.       while nt := get(a) do {
  342.          if nt == !builtin then {
  343.             get(a)
  344.             next
  345.             }
  346.          write(ofile,"<",nt,">::=",getrhs(get(a)))
  347.          }
  348.       }
  349.    else write(ofile,name,"::=",getrhs(\defs[name[2:-1]])) |
  350.       write("*** undefined nonterminal:  ",name)
  351. end
  352.  
  353. #  look for file with input
  354. #
  355. procedure source(line)
  356.    local file, new
  357.  
  358.    return line ? {
  359.       if ="@" then {
  360.          new := open(file := tab(0)) | {
  361.             write(&errout,"*** cannot open ",file)
  362.             fail
  363.             }
  364.          push(ifile,in) &
  365.          in := new
  366.          return
  367.          }
  368.       }
  369. end
  370.  
  371. #  produce string image of grammar symbol
  372. #
  373. procedure symimage(x)
  374.    return case type(x) of {
  375.       "string":   x
  376.       "nonterm":  "<" || x.name || ">"
  377.       "charset":  "<'" || x.chars || "'>"
  378.       }
  379. end
  380.  
  381. #  process the symbols in an alternative
  382. #
  383. procedure syms(alt)
  384.    local slist
  385.    static nonbrack
  386.    initial nonbrack := ~'<'
  387.    slist := []
  388.    alt ? while put(slist,tab(many(nonbrack)) |
  389.       defnon(2(="<",tab(upto('>')),move(1))))
  390.    return slist
  391. end
  392.