123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- // ll1 is a tool to parse LL1 grammar definitions and to use those then
- // to generate code or reports using Go templates.
- // Inside the ll1 templates, the following template functions are available:
- // - Most functions from the strings package (see go doc strings).
- // - CompileRegexp compiles a regexp package regexp which can be used as such.
- // - ToString to convert anything anything that isn't a string to a string.
- // - NewMap creates a map based it's argumens wich have string keys and interface{} values
- // This is handly to pass multiple aruments to a sub-template
- // - NewList creates a list from the given arguments.
- package main
- import "flag"
- import "os"
- import "strings"
- import "text/template"
- import "fmt"
- import "path"
- import "sort"
- func showUsage() {
- fmt.Fprintf(flag.CommandLine.Output(),
- "%s: %s [options] input_file.ll1 [template_file.ext*]\n",
- os.Args[0], os.Args[0])
-
- fmt.Fprintf(flag.CommandLine.Output(),
- "\n [options] may be one of the following:\n\n")
- flag.PrintDefaults()
- fmt.Fprintf(flag.CommandLine.Output(), "\n")
- }
- const helpText = `
- ll1 is a tool to parse and check LL(1) specifications, and to generate
- code or reports using Go templates based on these specifications.
- ll1 specifications must contain a definition for an ll1 grammar, and
- may optionally also specify a lexer for that grammar.
- Usage:
- ll1 [options] input_file.ll1 [template_file.ext*]
- The [options] are:
- -a file
- Name of output file to append. Takes precedence over -out.
- -d definition
- Add a definition for the template, in the form of key:value or
- []key:value. Keys that start with a [] are arrays and can be
- concatenated to by specifying the same definition key again.
- Non array keys will be overwoitten if they are specified again.
- -help -h
- Shows the help page.
- -l format
- Inject line directives with the given format before template expansion.
- -o file
- Name of output file to overwrite.
- -t file
- Template file to expand. This may be repeated to make use
- of several templates to generate one output file.
- -v
- Be more verbose. Shows the scanned tokens as well.
- The names of template files may be given with the -t option, or after the
- ll1 input file.
- The syntax of an LL1 grammar itself is:
-
- Specification -> Grammar OptLexer.
- Grammar -> Rules.
- Rules -> Rule OptRules .
- OptRules -> dot Rules | epsilon.
- Rule -> Name arrow Definition Template.
- Name -> ruleName .
- Template -> rawString | epsilon .
- // Alternates consist of sequences.
- Definition -> Alternates .
- Alternates -> Sequence OptSequences .
- OptSequences -> or Alternates | epsilon.
- Sequence -> Element OptElements .
- OptElements -> Element OptElements | epsilon .
- Element -> Parenthesis .
- Element -> Name .
- Element -> literal .
- Parenthesis -> '(' Definition ')' .
- OptLexer -> LexerTerminal OptLexerTerminals | epsilon .
- LexerTerminal -> terminalName arrow LexerDefinition Template .
- LexerDefinition -> LexerAlternates .
- LexerAlternates -> LexerPattern OptLexerMatches .
- OptLexerMatches -> or LexerPattern | epsilon.
- LexerPattern -> literal .
- OptElements -> Element OptElements | epsilon .
- Element -> Parenthesis .
- Element -> Name .
- Element -> literal /*| Rule */.
- // Lexer specification starts here:
- dot -> '.'
- or -> '|'
- literal -> characterLiteral | stringLiteral
- ruleName -> "re:[[:isUpper:]][[:isAlNum]]"
- terminalName -> "re:[[:isLower:]][[:isAlNum]]"
- epsilon -> "epsilon" | 'ε'
- arrow -> "->" | '→'
- The syntax of an ll1 grammar has the following elements:
- - //comment : Line comments start with //, /*block comments*/ are C-like
- - RuleName : names that start with an upper case letter are
- rule names or nonterminals defined by the grammar.
- - terminal : names that start with a lower case letter are names of
- teminals that the lexer produces.
- - 'l' : single quoted strings are rune literals that the lexer produces.
- - "literal" : double quoted strings are rune literals that the lexer produces.
- - arrow : a literal -> → as a separator.
- - epsion : a literal "epsilon" or 'ε', which indicates the empty rule.
- this is used in conjunction with alternates to make a rule
- optional.
- If no templates are given, ll1 simply checks the grammar and outputs a
- simple text report to the output file.
- If a template is given, it will be expanded and output to the output file.
- Inside the template the following variables are available:
- - .Grammar: contains the .Rules of the grammar.
- - .InName: contains the name of the ll1 input file.
- - .OutName: contains the name of the output file specified with -a or -o.
- - .Templates: contains the names of the templates read.
- - .Definitions: contains the keys of the available definitions.
- - All other variables defined with -d
-
- Inside the ll1 templates, the following template functions are available:
- - Most functions from the strings package (see go doc strings).
- - CompileRegexp compiles a regexp package regexp which can be used as such.
- - ToString to convert anything anything that isn't a string to a string.
- - NewMap creates a map based it's argumens wich have string keys and interface{} values
- This is handly to pass multiple aruments to a sub-template
- - NewList creates a list from the given arguments.
- `
- func showHelp() {
- fmt.Fprintf(flag.CommandLine.Output(), "\n%s\n", helpText)
- }
- type arrayFlags []string
- func (i *arrayFlags) String() string {
- return "my string representation"
- }
- func (i *arrayFlags) Set(value string) error {
- *i = append(*i, value)
- return nil
- }
- // The prefix for array definitions
- const definitionArrayPrefix = "[]"
- type definitionMap map[string]interface{}
- func (i *definitionMap) Set(in string) error {
- parts := strings.SplitN(in, ":", 2)
- if len(parts) < 2 {
- return fmt.Errorf("Could not split definition on ':' for %s ", in)
- }
- key := parts[0]
- value := parts[1]
- if strings.HasPrefix(key, definitionArrayPrefix) {
- key = strings.TrimPrefix(key, definitionArrayPrefix)
- existing, exists := (*i)[key]
- if !exists {
- slice := make([]string, 0)
- slice = append(slice, value)
- (*i)[key] = slice
- } else {
- slice, isSlice := existing.([]string)
- if isSlice {
- slice = append(slice, value)
- (*i)[key] = slice
- } else {
- return fmt.Errorf("Cannot mix array and non array definitions: %s -> %s:\n", key, value)
- }
- }
- } else {
- (*i)[key] = value
- }
- return nil
- }
- func (i *definitionMap) String() string {
- s := "{"
- for k, v := range *i {
- switch vv := v.(type) {
- case string: s = s + fmt.Sprintf("%s:%s;", k, vv)
- case []string: s = s + fmt.Sprintf("%s:%v;", k, vv)
- default: s = s + fmt.Sprintf("%s:<omitted>;", k)
- }
- }
- s = s + "}"
- return s
- }
- func (i *definitionMap) Keys() []string {
- res := []string{}
- for k, _ := range *i {
- res = append(res, k)
- }
- sort.Strings(res)
- return res
- }
- // LL1 contains the options and variables of the ll1 program.
- type Ll1 struct {
- definitions definitionMap
- templateNames arrayFlags
- outName string
- usedName string
- appendName string
- help bool
- verbose bool
- debug bool
- lineFormat string
- fout *os.File
- parser *Parser
- grammar *Grammar
- tmpl *template.Template
- }
- func main() {
- var err error
- flag.Usage = showUsage
- ll1 := Ll1{}
-
- // Set up a few default definitions
- ll1.definitions = make(definitionMap)
- ll1.definitions["Package"] = "main"
- ll1.definitions["Prefix"] = "Ll1"
-
- flag.BoolVar(&ll1.verbose, "v", false, "Be more verbose. ")
- flag.BoolVar(&ll1.debug, "D", false, "Show debug info. Shows the scanned tokens as well.")
- flag.Var(&ll1.templateNames, "t", "Template `file` to expand.")
- flag.StringVar(&ll1.outName, "o", "", "Name of output `file` to overwrite.")
- flag.StringVar(&ll1.appendName, "a", "", "Name of output `file` to append.")
- flag.StringVar(&ll1.lineFormat, "l", "", "Inject line directives before template epansion.")
- flag.Var(&ll1.definitions, "d", "Add a `definition` for the template, in the form of key:value or []key:value.")
- flag.BoolVar(&ll1.help, "h", false, "Shows the help page.")
- flag.BoolVar(&ll1.help, "help", false, "Shows the help page.")
- flag.Parse()
-
- if ll1.help {
- showUsage()
- showHelp()
- os.Exit(1)
- }
-
- if (len(flag.Args()) < 1) {
- showUsage()
- os.Exit(1)
- return
- }
-
- ll1Name := flag.Arg(0)
-
- // other file names after the first are templates.
- for i := 1 ; i < len(flag.Args()) ; i ++ {
- ll1.templateNames.Set(flag.Arg(i))
- }
-
-
- // Parse grammar
- ll1.parser, ll1.grammar, err = ParseFile(ll1Name, ll1.debug)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- for _, e := range ll1.parser.Errors {
- fmt.Fprintf(os.Stderr, "%v\n", e)
- }
- os.Exit(2)
- }
-
- // Check grammar and report errors
- errs := ll1.grammar.Check()
- if len(errs) > 0 {
- for _, err := range errs {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- }
- os.Exit(5)
- }
-
- // If not templates given, output just a grammar report
- if len(ll1.templateNames) < 1 {
- fmt.Fprintf(ll1.fout, "Grammar:\n%v\n", ll1.grammar)
- os.Exit(0)
- }
-
- // parse the templates
- if len(ll1.templateNames) > 0 {
- name := path.Base(ll1.templateNames[0])
- if ll1.verbose {
- fmt.Printf("Parsing templates: %s\n", name)
- }
-
- ll1.tmpl, err = template.New(name).Funcs(templateFunctionMap).ParseFiles([]string(ll1.templateNames)...)
- if err != nil {
- fmt.Printf("%s: %s: template parsing error\n", err, name)
- os.Exit(7)
- }
- }
- // Determine output file
- ll1.usedName = ll1.outName
-
- if ll1.appendName != "" {
- ll1.fout, err = os.OpenFile(ll1.appendName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Could not append to output file %s: %s\n", ll1.appendName, err)
- os.Exit(9)
- } else {
- ll1.usedName = ll1.appendName
- }
- defer ll1.fout.Close()
- } else if ll1.outName == "" {
- ll1.fout = os.Stdout
- } else {
- ll1.fout, err = os.Create(ll1.outName)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Could not open output file %s: %s\n", ll1.outName, err)
- }
- defer ll1.fout.Close()
- }
-
- // Set up the template definitions
- ll1.definitions["InName"] = ll1Name
- ll1.definitions["OutName"] = ll1.usedName
- ll1.definitions["Templates"] = strings.Join(ll1.templateNames, "\n")
- ll1.definitions["Grammar"] = ll1.grammar
- ll1.definitions["Parser"] = ll1.parser
- ll1.definitions["Definitions"] = ll1.definitions.Keys()
- // And execute the template, generating output
- err = ll1.tmpl.Execute(ll1.fout, ll1.definitions)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%s: template execution error", err)
- if ll1.usedName != "" {
- os.Remove(ll1.usedName)
- }
- os.Exit(10)
- }
- }
|