main.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // ll1 is a tool to parse LL1 grammar definitions and to use those then
  2. // to generate code or reports using Go templates.
  3. // Inside the ll1 templates, the following template functions are available:
  4. // - Most functions from the strings package (see go doc strings).
  5. // - CompileRegexp compiles a regexp package regexp which can be used as such.
  6. // - ToString to convert anything anything that isn't a string to a string.
  7. // - NewMap creates a map based it's argumens wich have string keys and interface{} values
  8. // This is handly to pass multiple aruments to a sub-template
  9. // - NewList creates a list from the given arguments.
  10. package main
  11. import "flag"
  12. import "os"
  13. import "strings"
  14. import "text/template"
  15. import "fmt"
  16. import "path"
  17. import "sort"
  18. func showUsage() {
  19. fmt.Fprintf(flag.CommandLine.Output(),
  20. "%s: %s [options] input_file.ll1 [template_file.ext*]\n",
  21. os.Args[0], os.Args[0])
  22. fmt.Fprintf(flag.CommandLine.Output(),
  23. "\n [options] may be one of the following:\n\n")
  24. flag.PrintDefaults()
  25. fmt.Fprintf(flag.CommandLine.Output(), "\n")
  26. }
  27. const helpText = `
  28. ll1 is a tool to parse and check LL(1) specifications, and to generate
  29. code or reports using Go templates based on these specifications.
  30. ll1 specifications must contain a definition for an ll1 grammar, and
  31. may optionally also specify a lexer for that grammar.
  32. Usage:
  33. ll1 [options] input_file.ll1 [template_file.ext*]
  34. The [options] are:
  35. -a file
  36. Name of output file to append. Takes precedence over -out.
  37. -d definition
  38. Add a definition for the template, in the form of key:value or
  39. []key:value. Keys that start with a [] are arrays and can be
  40. concatenated to by specifying the same definition key again.
  41. Non array keys will be overwoitten if they are specified again.
  42. -help -h
  43. Shows the help page.
  44. -l format
  45. Inject line directives with the given format before template expansion.
  46. -o file
  47. Name of output file to overwrite.
  48. -t file
  49. Template file to expand. This may be repeated to make use
  50. of several templates to generate one output file.
  51. -v
  52. Be more verbose. Shows the scanned tokens as well.
  53. The names of template files may be given with the -t option, or after the
  54. ll1 input file.
  55. The syntax of an LL1 grammar itself is:
  56. Specification -> Grammar OptLexer.
  57. Grammar -> Rules.
  58. Rules -> Rule OptRules .
  59. OptRules -> dot Rules | epsilon.
  60. Rule -> Name arrow Definition Template.
  61. Name -> ruleName .
  62. Template -> rawString | epsilon .
  63. // Alternates consist of sequences.
  64. Definition -> Alternates .
  65. Alternates -> Sequence OptSequences .
  66. OptSequences -> or Alternates | epsilon.
  67. Sequence -> Element OptElements .
  68. OptElements -> Element OptElements | epsilon .
  69. Element -> Parenthesis .
  70. Element -> Name .
  71. Element -> literal .
  72. Parenthesis -> '(' Definition ')' .
  73. OptLexer -> LexerTerminal OptLexerTerminals | epsilon .
  74. LexerTerminal -> terminalName arrow LexerDefinition Template .
  75. LexerDefinition -> LexerAlternates .
  76. LexerAlternates -> LexerPattern OptLexerMatches .
  77. OptLexerMatches -> or LexerPattern | epsilon.
  78. LexerPattern -> literal .
  79. OptElements -> Element OptElements | epsilon .
  80. Element -> Parenthesis .
  81. Element -> Name .
  82. Element -> literal /*| Rule */.
  83. // Lexer specification starts here:
  84. dot -> '.'
  85. or -> '|'
  86. literal -> characterLiteral | stringLiteral
  87. ruleName -> "re:[[:isUpper:]][[:isAlNum]]"
  88. terminalName -> "re:[[:isLower:]][[:isAlNum]]"
  89. epsilon -> "epsilon" | 'ε'
  90. arrow -> "->" | '→'
  91. The syntax of an ll1 grammar has the following elements:
  92. - //comment : Line comments start with //, /*block comments*/ are C-like
  93. - RuleName : names that start with an upper case letter are
  94. rule names or nonterminals defined by the grammar.
  95. - terminal : names that start with a lower case letter are names of
  96. teminals that the lexer produces.
  97. - 'l' : single quoted strings are rune literals that the lexer produces.
  98. - "literal" : double quoted strings are rune literals that the lexer produces.
  99. - arrow : a literal -> → as a separator.
  100. - epsion : a literal "epsilon" or 'ε', which indicates the empty rule.
  101. this is used in conjunction with alternates to make a rule
  102. optional.
  103. If no templates are given, ll1 simply checks the grammar and outputs a
  104. simple text report to the output file.
  105. If a template is given, it will be expanded and output to the output file.
  106. Inside the template the following variables are available:
  107. - .Grammar: contains the .Rules of the grammar.
  108. - .InName: contains the name of the ll1 input file.
  109. - .OutName: contains the name of the output file specified with -a or -o.
  110. - .Templates: contains the names of the templates read.
  111. - .Definitions: contains the keys of the available definitions.
  112. - All other variables defined with -d
  113. Inside the ll1 templates, the following template functions are available:
  114. - Most functions from the strings package (see go doc strings).
  115. - CompileRegexp compiles a regexp package regexp which can be used as such.
  116. - ToString to convert anything anything that isn't a string to a string.
  117. - NewMap creates a map based it's argumens wich have string keys and interface{} values
  118. This is handly to pass multiple aruments to a sub-template
  119. - NewList creates a list from the given arguments.
  120. `
  121. func showHelp() {
  122. fmt.Fprintf(flag.CommandLine.Output(), "\n%s\n", helpText)
  123. }
  124. type arrayFlags []string
  125. func (i *arrayFlags) String() string {
  126. return "my string representation"
  127. }
  128. func (i *arrayFlags) Set(value string) error {
  129. *i = append(*i, value)
  130. return nil
  131. }
  132. // The prefix for array definitions
  133. const definitionArrayPrefix = "[]"
  134. type definitionMap map[string]interface{}
  135. func (i *definitionMap) Set(in string) error {
  136. parts := strings.SplitN(in, ":", 2)
  137. if len(parts) < 2 {
  138. return fmt.Errorf("Could not split definition on ':' for %s ", in)
  139. }
  140. key := parts[0]
  141. value := parts[1]
  142. if strings.HasPrefix(key, definitionArrayPrefix) {
  143. key = strings.TrimPrefix(key, definitionArrayPrefix)
  144. existing, exists := (*i)[key]
  145. if !exists {
  146. slice := make([]string, 0)
  147. slice = append(slice, value)
  148. (*i)[key] = slice
  149. } else {
  150. slice, isSlice := existing.([]string)
  151. if isSlice {
  152. slice = append(slice, value)
  153. (*i)[key] = slice
  154. } else {
  155. return fmt.Errorf("Cannot mix array and non array definitions: %s -> %s:\n", key, value)
  156. }
  157. }
  158. } else {
  159. (*i)[key] = value
  160. }
  161. return nil
  162. }
  163. func (i *definitionMap) String() string {
  164. s := "{"
  165. for k, v := range *i {
  166. switch vv := v.(type) {
  167. case string: s = s + fmt.Sprintf("%s:%s;", k, vv)
  168. case []string: s = s + fmt.Sprintf("%s:%v;", k, vv)
  169. default: s = s + fmt.Sprintf("%s:<omitted>;", k)
  170. }
  171. }
  172. s = s + "}"
  173. return s
  174. }
  175. func (i *definitionMap) Keys() []string {
  176. res := []string{}
  177. for k, _ := range *i {
  178. res = append(res, k)
  179. }
  180. sort.Strings(res)
  181. return res
  182. }
  183. // LL1 contains the options and variables of the ll1 program.
  184. type Ll1 struct {
  185. definitions definitionMap
  186. templateNames arrayFlags
  187. outName string
  188. usedName string
  189. appendName string
  190. help bool
  191. verbose bool
  192. debug bool
  193. lineFormat string
  194. fout *os.File
  195. parser *Parser
  196. grammar *Grammar
  197. tmpl *template.Template
  198. }
  199. func main() {
  200. var err error
  201. flag.Usage = showUsage
  202. ll1 := Ll1{}
  203. // Set up a few default definitions
  204. ll1.definitions = make(definitionMap)
  205. ll1.definitions["Package"] = "main"
  206. ll1.definitions["Prefix"] = "Ll1"
  207. flag.BoolVar(&ll1.verbose, "v", false, "Be more verbose. ")
  208. flag.BoolVar(&ll1.debug, "D", false, "Show debug info. Shows the scanned tokens as well.")
  209. flag.Var(&ll1.templateNames, "t", "Template `file` to expand.")
  210. flag.StringVar(&ll1.outName, "o", "", "Name of output `file` to overwrite.")
  211. flag.StringVar(&ll1.appendName, "a", "", "Name of output `file` to append.")
  212. flag.StringVar(&ll1.lineFormat, "l", "", "Inject line directives before template epansion.")
  213. flag.Var(&ll1.definitions, "d", "Add a `definition` for the template, in the form of key:value or []key:value.")
  214. flag.BoolVar(&ll1.help, "h", false, "Shows the help page.")
  215. flag.BoolVar(&ll1.help, "help", false, "Shows the help page.")
  216. flag.Parse()
  217. if ll1.help {
  218. showUsage()
  219. showHelp()
  220. os.Exit(1)
  221. }
  222. if (len(flag.Args()) < 1) {
  223. showUsage()
  224. os.Exit(1)
  225. return
  226. }
  227. ll1Name := flag.Arg(0)
  228. // other file names after the first are templates.
  229. for i := 1 ; i < len(flag.Args()) ; i ++ {
  230. ll1.templateNames.Set(flag.Arg(i))
  231. }
  232. // Parse grammar
  233. ll1.parser, ll1.grammar, err = ParseFile(ll1Name, ll1.debug)
  234. if err != nil {
  235. fmt.Fprintf(os.Stderr, "%v\n", err)
  236. for _, e := range ll1.parser.Errors {
  237. fmt.Fprintf(os.Stderr, "%v\n", e)
  238. }
  239. os.Exit(2)
  240. }
  241. // Check grammar and report errors
  242. errs := ll1.grammar.Check()
  243. if len(errs) > 0 {
  244. for _, err := range errs {
  245. fmt.Fprintf(os.Stderr, "%v\n", err)
  246. }
  247. os.Exit(5)
  248. }
  249. // If not templates given, output just a grammar report
  250. if len(ll1.templateNames) < 1 {
  251. fmt.Fprintf(ll1.fout, "Grammar:\n%v\n", ll1.grammar)
  252. os.Exit(0)
  253. }
  254. // parse the templates
  255. if len(ll1.templateNames) > 0 {
  256. name := path.Base(ll1.templateNames[0])
  257. if ll1.verbose {
  258. fmt.Printf("Parsing templates: %s\n", name)
  259. }
  260. ll1.tmpl, err = template.New(name).Funcs(templateFunctionMap).ParseFiles([]string(ll1.templateNames)...)
  261. if err != nil {
  262. fmt.Printf("%s: %s: template parsing error\n", err, name)
  263. os.Exit(7)
  264. }
  265. }
  266. // Determine output file
  267. ll1.usedName = ll1.outName
  268. if ll1.appendName != "" {
  269. ll1.fout, err = os.OpenFile(ll1.appendName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
  270. if err != nil {
  271. fmt.Fprintf(os.Stderr, "Could not append to output file %s: %s\n", ll1.appendName, err)
  272. os.Exit(9)
  273. } else {
  274. ll1.usedName = ll1.appendName
  275. }
  276. defer ll1.fout.Close()
  277. } else if ll1.outName == "" {
  278. ll1.fout = os.Stdout
  279. } else {
  280. ll1.fout, err = os.Create(ll1.outName)
  281. if err != nil {
  282. fmt.Fprintf(os.Stderr, "Could not open output file %s: %s\n", ll1.outName, err)
  283. }
  284. defer ll1.fout.Close()
  285. }
  286. // Set up the template definitions
  287. ll1.definitions["InName"] = ll1Name
  288. ll1.definitions["OutName"] = ll1.usedName
  289. ll1.definitions["Templates"] = strings.Join(ll1.templateNames, "\n")
  290. ll1.definitions["Grammar"] = ll1.grammar
  291. ll1.definitions["Parser"] = ll1.parser
  292. ll1.definitions["Definitions"] = ll1.definitions.Keys()
  293. // And execute the template, generating output
  294. err = ll1.tmpl.Execute(ll1.fout, ll1.definitions)
  295. if err != nil {
  296. fmt.Fprintf(os.Stderr, "%s: template execution error", err)
  297. if ll1.usedName != "" {
  298. os.Remove(ll1.usedName)
  299. }
  300. os.Exit(10)
  301. }
  302. }