// 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:;", 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) } }