From bac7d3836e6a5fefaf1e3f6c025c7e397be99bd0 Mon Sep 17 00:00:00 2001 From: wangjin Date: Mon, 30 Sep 2024 21:00:55 +0800 Subject: [PATCH] add project config --- Makefile | 2 +- go.mod | 2 + go.sum | 3 + src/compile/annotation.go | 4 +- src/compile/compiler.go | 287 +-------- src/compile/generate.go | 36 +- src/compile/next.go | 204 +++++-- src/compile/project.go | 407 +++++++++++++ src/compile/template.go | 8 +- src/grammar/grammar.go | 684 ++++++++++++++++------ website/docs/api/preview/command_line.mdx | 205 ++++++- website/docs/api/preview/grammar.mdx | 630 +++++++++++++++----- website/example/example.proj.yaml | 53 ++ website/example/grammar.json | 441 +++++++------- website/example/grammar.yaml | 213 +++++++ 15 files changed, 2343 insertions(+), 836 deletions(-) create mode 100644 src/compile/project.go create mode 100644 website/example/example.proj.yaml create mode 100644 website/example/grammar.yaml diff --git a/Makefile b/Makefile index 3147e67..76b942c 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ example/gen: -O php=${EXAMPLE_DIR}/gen/php -T php=${EXAMPLE_DIR}/templates/php \ -O lua=${EXAMPLE_DIR}/gen/lua -T lua=${EXAMPLE_DIR}/templates/lua \ -M "c.vector=void*" -M "c.map=void*" \ - -s -g ${EXAMPLE_DIR}/grammar.json \ + -s -g ${EXAMPLE_DIR}/grammar.yaml \ ${EXAMPLE_DIR}/next/ @if [ -d ${EXAMPLE_DIR}/gen/rust ]; then cd ${EXAMPLE_DIR}/gen/rust && cargo init --vcs none -q; fi diff --git a/go.mod b/go.mod index 14989fe..5b71fa0 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,5 @@ require ( github.com/gopherd/core v0.0.0-20240926071129-6d591f1f639c github.com/mattn/go-shellwords v1.0.12 ) + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a345011..895d9cc 100644 --- a/go.sum +++ b/go.sum @@ -6,3 +6,6 @@ github.com/gopherd/core v0.0.0-20240926071129-6d591f1f639c h1:jMKV3OIqDnGW6VlIdL github.com/gopherd/core v0.0.0-20240926071129-6d591f1f639c/go.mod h1:KfAPtxaKLEFiby8PpGwdgp86auCmLU82+khBzHhUTaM= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/compile/annotation.go b/src/compile/annotation.go index b7fd16d..8442b72 100644 --- a/src/compile/annotation.go +++ b/src/compile/annotation.go @@ -597,8 +597,8 @@ func (c *Compiler) solveAnnotations() error { parser := shellwords.NewParser() parser.ParseEnv = true programs := make(map[string][][]string) - keys := make([]string, 0, len(c.flags.solvers)) - for name, solvers := range c.flags.solvers { + keys := make([]string, 0, len(c.options.Solvers)) + for name, solvers := range c.options.Solvers { if name == "next" { return fmt.Errorf("'next' is a reserved annotation for the next compiler, please don't use solvers for it") } diff --git a/src/compile/compiler.go b/src/compile/compiler.go index 7eb62e5..e141f1f 100644 --- a/src/compile/compiler.go +++ b/src/compile/compiler.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/gopherd/core/flags" - "github.com/gopherd/core/term" "github.com/next/next/src/ast" "github.com/next/next/src/constant" @@ -26,19 +25,8 @@ const verboseTrace = 2 // Compiler represents a compiler of a Next program type Compiler struct { - // command line flags - flags struct { - verbose int - test bool - head string - grammar string - strict bool - envs flags.Map - outputs flags.Map - templates flags.MapSlice - mappings flags.Map - solvers flags.MapSlice - } + // command line options + options Configuration platform Platform builtin FileSystem grammar grammar.Grammar @@ -80,258 +68,17 @@ func NewCompiler(platform Platform, builtin FileSystem) *Compiler { searchDirs: createSearchDirs(platform), annotations: make(map[token.Pos]*linkedAnnotation), } - c.flags.envs = make(flags.Map) - c.flags.outputs = make(flags.Map) - c.flags.templates = make(flags.MapSlice) - c.flags.mappings = make(flags.Map) - c.flags.solvers = make(flags.MapSlice) + c.options.Env = make(flags.Map) + c.options.Output = make(flags.Map) + c.options.Templates = make(flags.MapSlice) + c.options.Mapping = make(flags.Map) + c.options.Solvers = make(flags.MapSlice) return c } func (c *Compiler) SetupCommandFlags(flagSet *flag.FlagSet, u flags.UsageFunc) { - isAnsiSupported := term.IsTerminal(flagSet.Output()) && term.IsSupportsAnsi() - grey := func(s string) string { - if isAnsiSupported { - return term.Gray.Format(s) - } - return s - } - b := func(s string) string { - if isAnsiSupported { - return term.Bold.Format(s) - } - return s - } - - // @api(CommandLine/Flag/-v) represents the verbosity level of the compiler. - // The default value is **0**, which only shows error messages. - // The value **1** shows debug messages, and **2** shows trace messages. - // Usually, the trace message is used for debugging the compiler itself. - // Levels **1** (debug) and above enable execution of: - // - // - **print** and **printf** in Next source files (.next). - // - **debug** in Next template files (.npl). - // - // Example: - // - // ```sh - // next -v 1 ... - // ``` - flagSet.IntVar(&c.flags.verbose, "v", 0, u(""+ - "Control verbosity of compiler output and debugging information.\n"+ - "`VERBOSE` levels: "+b("0")+"=error, "+b("1")+"=debug, "+b("2")+"=trace.\n"+ - "Usually, the trace message used for debugging the compiler itself.\n"+ - "Levels "+b("1")+" (debug) and above enable execution of:\n"+ - " -"+b("print")+" and "+b("printf")+" in Next source files (.next).\n"+ - " -"+b("debug")+" in Next template files (.npl).\n", - )) - - // @api(CommandLine/Flag/-t) represents the test mode of the compiler. - // The default value is **false**, which means the compiler is not in test mode. - // The value **true** enables the test mode, which is used for validating but not generating code. - // - // Example: - // - // ```sh - // next -t ... - // ``` - flagSet.BoolVar(&c.flags.test, "t", false, u(""+ - "Enable test mode for validating but not generating code.\n"+ - "Example:\n"+ - " next -t ...\n", - )) - - // @api(CommandLine/Flag/-head) represents the header comment for generated code. - // The value is a string that represents the header comment for generated code. - // The default value is an empty string, which means default header comment is used. - // - // Example: - // - // ```sh - // next -head "Code generated by Next; DO NOT EDIT." ... - // ``` - // - // :::note - // - // Do not add the comment characters like `//` or `/* */` in the header. Next will add them automatically - // based on the target language comment style. - // - // ::: - flagSet.StringVar(&c.flags.head, "head", "", u(""+ - "Set the `HEAD` comment for each generated file. It will be added to the top of each generated file by {{head}}.\n"+ - "Example:\n"+ - " next -head \"Code generated by Next; DO NOT EDIT.\"\n", - )) - - // @api(CommandLine/Flag/-g) represents the custom grammar for the next source code. - // - // Example: - // - // ```sh - // next -g grammar.json ... - // ``` - // - // :::note - // By default, the compiler uses the built-in grammar for the next source code. - // You can set a custom grammar file to define a subset of the next grammar. - // The grammar file is a JSON file that contains rules. - // - // If `-s` is not set, the compiler will ignore unknown annotations and unknown annotation parameters. - // ::: - flagSet.StringVar(&c.flags.grammar, "g", "", u(""+ - "Set the custom `GRAMMAR` file for the next source code.\n"+ - "The grammar is used to define a subset of the next grammar. It can limit the features of the next code according\n"+ - "by your requirements. The grammar file is a JSON file that contains rules.\n", - )) - - // @api(CommandLine/Flag/-s) represents the strict mode of the compiler. - // The default value is **false**, which means the compiler is not in strict mode. - // The value **true** enables the strict mode, which is used for strict validation of the next source code, - // such as unknown annotations and unknown annotation parameters. - // - // Example: - // - // ```sh - // next -s ... - // ``` - flagSet.BoolVar(&c.flags.strict, "s", false, u(""+ - "Enable strict mode for strict validation of the next source code.\n"+ - "Example:\n"+ - " next -s ...\n", - )) - - // @api(CommandLine/Flag/-D) represents the custom environment variables for code generation. - // The value is a map of environment variable names and their optional values. - // - // Example: - // - // ```sh - // next -D VERSION=2.1 -D DEBUG -D NAME=myapp ... - // ``` - // - // ```npl - // {{env.NAME}} - // {{env.VERSION}} - // ``` - // - // Output: - // - // ``` - // myapp - // 2.1 - // ``` - flagSet.Var(&c.flags.envs, "D", u(""+ - "Define custom environment variables for use in code generation templates.\n"+ - "`NAME"+grey("[=VALUE]")+"` represents the variable name and its optional value.\n"+ - "Example:\n"+ - " next -D VERSION=2.1 -D DEBUG -D NAME=myapp\n"+ - "And then, use the variables in templates like this: {{env.NAME}}, {{env.VERSION}}\n", - )) - - // @api(CommandLine/Flag/-O) represents the output directories for generated code of each target language. - // - // Example: - // - // ```sh - // next -O go=./output/go -O ts=./output/ts ... - // ``` - // - // :::tip - // - // The `{{meta.path}}` is relative to the output directory. - // - // ::: - flagSet.Var(&c.flags.outputs, "O", u(""+ - "Set output directories for generated code, organized by target language.\n"+ - "`LANG=DIR` specifies the target language and its output directory.\n"+ - "Example:\n"+ - " next -O go=./output/go -O ts=./output/ts\n", - )) - - // @api(CommandLine/Flag/-T) represents the custom template directories or files for each target language. - // You can specify multiple templates for a single language. - // - // Example: - // - // ```sh - // next -T go=./templates/go \ - // -T go=./templates/go_extra.npl \ - // -T python=./templates/python.npl \ - // ... - // ``` - flagSet.Var(&c.flags.templates, "T", u(""+ - "Specify custom template directories or files for each target language.\n"+ - "`LANG=PATH` defines the target language and its template directory or file.\n"+ - "Multiple templates can be specified for a single language.\n"+ - "Example:\n"+ - " next \\\n"+ - " -T go=./templates/go \\\n"+ - " -T go=./templates/go_extra.npl \\\n"+ - " -T python=./templates/python.npl\n", - )) - - // @api(CommandLine/Flag/-M) represents the language-specific type mappings and features. - // **%T%**, **%T.E%**, **%N%**, **%K%**, **%V%** are placeholders replaced with actual types or values. - // **%T.E%** is the final element type of a vector or array. It's used to get the element type of multi-dimensional arrays. - // - // Example: - // - // ```sh - // next -M cpp.vector="std::vector<%T%>" \ - // -M java.array="ArrayList<%T%>" \ - // -M go.map="map[%K%]%V%" \ - // -M python.ext=.py \ - // -M protobuf.vector="repeated %T.E%" \ - // -M ruby.comment="# %T%" \ - // ... - // ``` - flagSet.Var(&c.flags.mappings, "M", u(""+ - "Configure language-specific type mappings and features.\n"+ - "`LANG.KEY=VALUE` specifies the mappings for a given language and type/feature.\n"+ - "Type mappings: Map Next types to language-specific types.\n"+ - " Primitive types: int, int8, int16, int32, int64, bool, string, any, byte, bytes\n"+ - " Generic types: vector, array, map\n"+ - " "+b("%T%")+", "+b("%T.E%")+", "+b("%N%")+", "+b("%K%")+", "+b("%V%")+" are placeholders replaced with actual types or values.\n"+ - " "+b("%T.E%")+" is the final element type of a vector or array. It's used to get the element type of multi-dimensional arrays.\n"+ - "Feature mappings: Set language-specific properties like file extensions or comment styles.\n"+ - "Example:\n"+ - " next \\\n"+ - " -M cpp.vector=\"std::vector<%T%>\" \\\n"+ - " -M java.array=\"ArrayList<%T%>\" \\\n"+ - " -M go.map=\"map[%K%]%V%\" \\\n"+ - " -M python.ext=.py \\\n"+ - " -M protobuf.vector=\"repeated %T.E%\" \\\n"+ - " -M ruby.comment=\"# %T%\"\n", - )) - - // @api(CommandLine/Flag/-X) represents the custom annotation solver programs for code generation. - // Annotation solvers are executed in a separate process to solve annotations. - // All annotations are passed to the solver program via stdin and stdout. - // The built-in annotation `next` is reserved for the Next compiler. - // - // Example: - // - // ```sh - // next -X message="message-type-allocator message-types.json" ... - // ``` - // - // :::tip - // - // In the example above, the `message-type-allocator` is a custom annotation solver program that - // reads the message types from the `message-types.json` file and rewrite the message types to the - // `message-types.json` file. - // - // ::: - flagSet.Var(&c.flags.solvers, "X", u(""+ - "Specify custom annotation solver programs for code generation.\n"+ - "`ANNOTATION=PROGRAM` defines the target annotation and its solver program.\n"+ - "Annotation solvers are executed in a separate process to solve annotations.\n"+ - "All annotations are passed to the solver program via stdin and stdout.\n"+ - b("NOTE")+": built-in annotation 'next' is reserved for the Next compiler.\n"+ - "Example:\n"+ - " next -X message=\"message-type-allocator message-types.json\"\n", - )) + c.options.SetupCommandFlags(flagSet, u) } // FileSet returns the file set used to track file positions @@ -392,14 +139,14 @@ func (c *Compiler) log(msg string, args ...any) { // Trace logs a message if trace logging is enabled func (c *Compiler) Trace(msg string, args ...any) { - if c.flags.verbose >= verboseTrace { + if c.options.Verbose >= verboseTrace { c.log(msg, args...) } } // Debug logs a message if debug logging is enabled func (c *Compiler) Debug(msg string, args ...any) { - if c.flags.verbose >= verboseDebug { + if c.options.Verbose >= verboseDebug { c.log(msg, args...) } } @@ -804,11 +551,11 @@ func (c *Compiler) ValidateGrammar() error { return err } // Validate imports - if c.grammar.Import.Off && len(pkg.imports.List) > 0 { + if c.grammar.Import.Disabled && len(pkg.imports.List) > 0 { c.addErrorf(pkg.imports.List[0].pos, "import declaration is not allowed") } // Validate constants - if c.grammar.Const.Off && len(pkg.decls.consts) > 0 { + if c.grammar.Const.Disabled && len(pkg.decls.consts) > 0 { c.addErrorf(pkg.decls.consts[0].pos, "const declaration is not allowed") } else { for _, x := range pkg.decls.consts { @@ -818,7 +565,7 @@ func (c *Compiler) ValidateGrammar() error { } } // Validate enums - if c.grammar.Enum.Off && len(pkg.decls.enums) > 0 { + if c.grammar.Enum.Disabled && len(pkg.decls.enums) > 0 { c.addErrorf(pkg.decls.enums[0].pos, "enum declaration is not allowed") } else { for _, x := range pkg.decls.enums { @@ -828,7 +575,7 @@ func (c *Compiler) ValidateGrammar() error { } } // Validate structs - if c.grammar.Struct.Off && len(pkg.decls.structs) > 0 { + if c.grammar.Struct.Disabled && len(pkg.decls.structs) > 0 { c.addErrorf(pkg.decls.structs[0].pos, "struct declaration is not allowed") } else { for _, x := range pkg.decls.structs { @@ -838,7 +585,7 @@ func (c *Compiler) ValidateGrammar() error { } } // Validate interfaces - if c.grammar.Interface.Off && len(pkg.decls.interfaces) > 0 { + if c.grammar.Interface.Disabled && len(pkg.decls.interfaces) > 0 { c.addErrorf(pkg.decls.interfaces[0].pos, "interface declaration is not allowed") } else { for _, x := range pkg.decls.interfaces { @@ -982,7 +729,7 @@ func (c *Compiler) validateAnnotations(node Node, annotations grammar.Annotation for name, annotation := range node.Annotations() { ga := grammar.LookupAnnotation(annotations, name) if ga == nil { - if !c.flags.strict { + if !c.options.Strict { continue } s, score := stringutil.FindBestMatchFunc(slices.All(annotations), name, stringutil.DefaultSimilarityThreshold, func(i int, a grammar.Options[grammar.Annotation]) string { @@ -1002,7 +749,7 @@ func (c *Compiler) validateAnnotations(node Node, annotations grammar.Annotation } gp := grammar.LookupAnnotationParameter(ga.Parameters, p) if gp == nil { - if !c.flags.strict { + if !c.options.Strict { continue } s, score := stringutil.FindBestMatchFunc(slices.All(ga.Parameters), p, stringutil.DefaultSimilarityThreshold, func(i int, a grammar.Options[grammar.AnnotationParameter]) string { diff --git a/src/compile/generate.go b/src/compile/generate.go index 5870e1c..76d46d0 100644 --- a/src/compile/generate.go +++ b/src/compile/generate.go @@ -68,21 +68,21 @@ func createSearchDirs(platform Platform) []string { // Genertate generates files for each language specified in the flags.outputs. func Generate(c *Compiler) error { - if len(c.flags.outputs) == 0 { + if len(c.options.Output) == 0 { return nil } - c.Trace("flags.envs: ", c.flags.envs) - c.Trace("flags.outputs: ", c.flags.outputs) - c.Trace("flags.templates: ", c.flags.templates) - c.Trace("flags.mappings: ", c.flags.mappings) + c.Trace("flags.envs: ", c.options.Env) + c.Trace("flags.outputs: ", c.options.Output) + c.Trace("flags.templates: ", c.options.Templates) + c.Trace("flags.mappings: ", c.options.Mapping) - if c.flags.outputs.Get("next") != "" { + if c.options.Output.Get("next") != "" { return fmt.Errorf("output language 'next' is not supported") } // Check whether the template directory or file exists for each language - for lang := range c.flags.outputs { - for _, tmplPath := range c.flags.templates[lang] { + for lang := range c.options.Output { + for _, tmplPath := range c.options.Templates[lang] { if c.platform.IsNotExist(tmplPath) { return fmt.Errorf("template path %q not found: %q", lang, tmplPath) } @@ -91,16 +91,16 @@ func Generate(c *Compiler) error { // Load all mappings from all map files m := make(flags.Map) - for lang := range c.flags.outputs { + for lang := range c.options.Output { if err := loadMap(c, m, lang); err != nil { return err } } - for k, v := range c.flags.mappings { + for k, v := range c.options.Mapping { m[k] = v } - c.flags.mappings = m - if c.flags.verbose >= verboseTrace { + c.options.Mapping = m + if c.options.Verbose >= verboseTrace { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) @@ -112,15 +112,15 @@ func Generate(c *Compiler) error { } // Generate files for each language - langs := make([]string, 0, len(c.flags.outputs)) - for lang := range c.flags.outputs { + langs := make([]string, 0, len(c.options.Output)) + for lang := range c.options.Output { langs = append(langs, lang) } slices.Sort(langs) for _, lang := range langs { - dir := c.flags.outputs[lang] - ext := op.Or(c.flags.mappings[lang+".ext"], "."+lang) - tempPaths := c.flags.templates[lang] + dir := c.options.Output[lang] + ext := op.Or(c.options.Mapping[lang+".ext"], "."+lang) + tempPaths := c.options.Templates[lang] if len(tempPaths) == 0 { return fmt.Errorf("no template directory specified for %q", lang) } @@ -490,7 +490,7 @@ func gen[T Decl](tc *templateContext, t *template.Template, decl T, content []by } // Do not write the generated content to the output file if the test flag is set - if tc.compiler.flags.test { + if tc.compiler.options.test { return nil } diff --git a/src/compile/next.go b/src/compile/next.go index a4bbc55..5b98675 100644 --- a/src/compile/next.go +++ b/src/compile/next.go @@ -8,6 +8,7 @@ import ( "flag" "fmt" "io" + "maps" "os" "path/filepath" "slices" @@ -16,9 +17,12 @@ import ( "text/template" "github.com/gopherd/core/builder" + "github.com/gopherd/core/encoding" "github.com/gopherd/core/flags" "github.com/gopherd/core/op" "github.com/gopherd/core/term" + "gopkg.in/yaml.v3" + "github.com/next/next/src/grammar" "github.com/next/next/src/internal/fsutil" "github.com/next/next/src/parser" @@ -38,20 +42,12 @@ const website = "https://next.as" const repository = "https://github.com/next/next" type command struct { - desc string - fn func(io.Writer, []string) error -} - -func newCommand(desc string, fn func(io.Writer, []string) error) *command { - return &command{desc: desc, fn: fn} -} - -func (c *command) description() string { - return c.desc + description string + run func(*Compiler, []string) error } -func (c *command) run(stderr io.Writer, args []string) error { - return c.fn(stderr, args) +func newCommand(desc string, run func(*Compiler, []string) error) *command { + return &command{description: desc, run: run} } var commands = map[string]*command{ @@ -69,47 +65,147 @@ var commands = map[string]*command{ // ``` // next v0.0.4(main: 51864a35de7890d63bfd8acecdb62d20372ca963) built at 2024/09/27T22:58:21+0800 by go1.23.0 // ``` - "version": newCommand("print the version of the next compiler", func(stderr io.Writer, _ []string) error { - builder.PrintInfo() - unwrap(stderr, 0) - return nil - }), + "version": newCommand( + "Print the version of the next compiler", + func(c *Compiler, _ []string) error { + builder.PrintInfo() + unwrap(c.platform.Stderr(), 0) + return nil + }, + ), // @api(CommandLine/Command/grammar) command generates the default grammar for the next files. // // Example: // // ```sh - // next grammar grammar.json + // next grammar # generate the default grammar to the standard output with YAML format + // next grammar grammar.yaml # generate the default grammar with YAML format + // next grammar grammar.yml # generate the default grammar with YAML format + // next grammar grammar.json # generate the default grammar with JSON format // ``` // - // Or you can run the following command to generate the default grammar to the standard output: + // :::note + // The default grammar is generated in YAML format if the file extension is not provided. + // Currently, the supported file extensions are `.json`, `.yaml`, and `.yml` (alias of `.yaml`). + // ::: + "grammar": newCommand( + "Generate the default grammar for the next files: next grammar [filename]", + func(_ *Compiler, args []string) error { + var path string + var ext string + if len(args) > 2 { + return fmt.Errorf("too many arguments: next %s [filename]", args[0]) + } + if len(args) == 2 { + path = args[1] + ext = filepath.Ext(path) + } + + content, err := json.MarshalIndent(grammar.Default, "", " ") + if err == nil { + switch ext { + case ".json": + case "", ".yaml", ".yml": + var buf bytes.Buffer + enc := yaml.NewEncoder(&buf) + enc.SetIndent(2) + content, err = encoding.Transform(content, json.Unmarshal, func(x any) ([]byte, error) { + if err := enc.Encode(x); err != nil { + return nil, err + } + return buf.Bytes(), nil + }) + default: + return fmt.Errorf("unsupported file extension: %q, use .json, .yaml, or .yml", ext) + } + } + if err != nil { + return err + } + if path != "" { + return os.WriteFile(path, content, 0644) + } + fmt.Println(string(content)) + return nil + }, + ), + + // @api(CommandLine/Command/run) command runs a next project. + // It reads the project file and compiles the source files according to the project configuration. + // The project file is a YAML file that contains the project [Configuration](#CommandLine/Configuration). + // + // Example: + // // ```sh - // next grammar + // next run demo.yaml // ``` - "grammar": newCommand("generate the default grammar for the next files", func(stderr io.Writer, args []string) error { - if len(args) > 2 { - return fmt.Errorf("too many arguments: next %s [filename]", args[0]) - } - content, err := json.MarshalIndent(grammar.Default, "", " ") - if err != nil { - return err - } - if len(args) == 2 { - return os.WriteFile(args[1], content, 0644) - } - fmt.Println(string(content)) - return nil - }), + "run": newCommand( + "Run a next project: next run ", + func(c *Compiler, args []string) error { + if len(args) != 2 { + return fmt.Errorf("wrong number of arguments: next run ") + } + path := args[1] + content, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read %q: %w", path, err) + } + var options struct { + *Configuration `yaml:",inline"` + + // @api(CommandLine/Configuration.sources) represents the source directories or files. + // + // Example: + // + // ```yaml + // sources: + // - demo.next + // - src/next/ + // ``` + Sources []string `yaml:"sources" json:"sources"` + } + options.Configuration = &c.options + var unmarshaler func([]byte, any) error + ext := filepath.Ext(path) + switch ext { + case ".json": + unmarshaler = json.Unmarshal + case "", ".yaml", ".yml": + unmarshaler = yaml.Unmarshal + default: + return fmt.Errorf("unsupported file extension: %q, use .json, .yaml, or .yml", ext) + } + if err := unmarshaler(content, &options); err != nil { + return fmt.Errorf("failed to parse %q: %v", path, err) + } + options.resolvePath(filepath.Dir(path)) + var files []string + for _, arg := range options.Sources { + arg = filepath.Join(filepath.Dir(path), arg) + file, err := fsutil.AppendFiles(files, arg, nextExt, false) + if err != nil { + return fmt.Errorf("failed to read %q: %v", arg, err) + } + files = result(c.platform.Stderr(), file, err) + } + if len(files) == 0 { + return fmt.Errorf("no source files") + } + doCompile(c, files, nil) + return nil + }, + ), } // Compile compiles the next files. func Compile(platform Platform, builtin FileSystem, args []string) { + compiler := NewCompiler(platform, builtin) stdin, stderr := platform.Stdin(), platform.Stderr() if len(args) >= 2 { if cmd, ok := commands[args[1]]; ok { - unwrap(stderr, cmd.run(stderr, args[1:])) + unwrap(stderr, cmd.run(compiler, args[1:])) unwrap(stderr, 0) } } @@ -117,7 +213,6 @@ func Compile(platform Platform, builtin FileSystem, args []string) { flagSet := flag.NewFlagSet(args[0], flag.ContinueOnError) flagSet.Usage = func() {} - compiler := NewCompiler(platform, builtin) compiler.SetupCommandFlags(flagSet, flags.UseUsage(flagSet.Output(), flags.NameColor(term.Bold))) // set output color for error messages @@ -129,8 +224,18 @@ func Compile(platform Platform, builtin FileSystem, args []string) { term.Fprint(flagSet.Output(), "Usage:\n") term.Fprintf(flagSet.Output(), " %s [Options] [source_dirs_or_files...] (default: current directory)\n", name) term.Fprintf(flagSet.Output(), " %s [Options] \n", name) - term.Fprintf(flagSet.Output(), " %s version # print the version of the next compiler\n", name) - term.Fprintf(flagSet.Output(), " %s grammar [filename] # generate the default grammar\n", name) + term.Fprintf(flagSet.Output(), " %s [Command-Args]\n", name) + term.Fprintf(flagSet.Output(), "\nCommands:\n") + + var maxCommandLength int + for _, name := range slices.Sorted(maps.Keys(commands)) { + maxCommandLength = max(maxCommandLength, len(name)) + } + maxCommandLength += 4 // add 4 spaces for padding + for _, name := range slices.Sorted(maps.Keys(commands)) { + term.Fprintf(flagSet.Output(), " %s%s%s\n", term.BrightMagenta.Colorize(name), strings.Repeat(" ", maxCommandLength-len(name)), commands[name].description) + } + term.Fprintf(flagSet.Output(), "\nOptions:\n") flagSet.PrintDefaults() term.Fprintf(flagSet.Output(), `For more information: @@ -179,10 +284,18 @@ func Compile(platform Platform, builtin FileSystem, args []string) { files = result(stderr, path, err) } } + if len(files) == 0 { usage(flagSet, stderr, "no source files") } + doCompile(compiler, files, source) +} + +func doCompile(compiler *Compiler, files []string, source io.Reader) { + platform := compiler.platform + stderr := compiler.platform.Stderr() + // compute absolute path and remove duplicated files if source == nil { seen := make(map[string]bool) @@ -198,13 +311,22 @@ func Compile(platform Platform, builtin FileSystem, args []string) { } // read grammar file - if compiler.flags.grammar != "" { - content, err := platform.ReadFile(compiler.flags.grammar) + if compiler.options.Grammar != "" { + content, err := platform.ReadFile(compiler.options.Grammar) content = result(stderr, content, err) - unwrap(stderr, json.Unmarshal(content, &compiler.grammar)) + switch ext := filepath.Ext(compiler.options.Grammar); ext { + case ".json": + case "", ".yaml", ".yml": + content, err = encoding.Transform(content, yaml.Unmarshal, json.Marshal) + content = result(stderr, content, err) + default: + unwrap(stderr, fmt.Sprintf("unsupported file extension: %q, use .json, .yaml, or .yml", ext)) + } if err := compiler.grammar.Resolve(); err != nil { return } + unwrap(stderr, json.Unmarshal(content, &compiler.grammar)) + unwrap(stderr, compiler.grammar.Resolve()) } else { compiler.grammar = grammar.Default } diff --git a/src/compile/project.go b/src/compile/project.go new file mode 100644 index 0000000..8489fed --- /dev/null +++ b/src/compile/project.go @@ -0,0 +1,407 @@ +package compile + +import ( + "flag" + "path/filepath" + + "github.com/gopherd/core/flags" + "github.com/gopherd/core/term" +) + +// @api(CommandLine/Configuration) +// +// import CodeBlock from "@theme/CodeBlock"; +// import ExampleProjSource from "!!raw-loader!@site/example/example.proj.yaml"; +// +// Configuration represents the configuration of the Next project. +// The configuration is used to mamange the compiler options, such as verbosity, output directories, and custom templates. +// If the configuration is provided, you can generate code like this: +// +// ```sh +// next run example.proj.yaml +// ``` +// +// The configuration file is a YAML or JSON file that contains the compiler options. +// Here is an example of the configuration file: +// +// +// {ExampleProjSource} +// +type Configuration struct { + test bool `yaml:"-" json:"-"` + + // @api(CommandLine/Configuration.verbose) represents the verbosity level of the compiler. + // + // Example: + // + // ```yaml + // verbose: 1 + // ``` + // + // See the [-v](#CommandLine/Flag/-v) flag for more information. + Verbose int `yaml:"verbose" json:"verbose"` + + // @api(CommandLine/Configuration.head) represents the header comment for generated code. + // + // Example: + // + // ```yaml + // head: "Code generated by Next; DO NOT EDIT." + // ``` + // + // See the [-head](#CommandLine/Flag/-head) flag for more information. + Head string `yaml:"head" json:"head"` + + // @api(CommandLine/Configuration.grammar) represents the custom grammar for the next source code. + // + // Example: + // + // ```yaml + // grammar: grammar.yaml + // ``` + // + // See the [-g](#CommandLine/Flag/-g) flag for more information. + Grammar string `yaml:"grammar" json:"grammar"` + + // @api(CommandLine/Configuration.strict) represents the strict mode of the compiler. + // + // Example: + // + // ```yaml + // strict: true + // ``` + // + // See the [-s](#CommandLine/Flag/-s) flag for more information. + Strict bool `yaml:"strict" json:"strict"` + + // @api(CommandLine/Configuration.env) represents the custom environment variables for code generation. + // + // Example: + // + // ```yaml + // env: + // VERSION: "2.1" + // DEBUG: "" + // NAME: myapp + // ``` + // + // See the [-D](#CommandLine/Flag/-D) flag for more information. + Env flags.Map `yaml:"env" json:"env"` + + // @api(CommandLine/Configuration.output) represents the output directories for generated code of each target language. + // + // Example: + // + // ```yaml + // output: + // go: ./output/go + // ts: ./output/ts + // ``` + // + // See the [-O](#CommandLine/Flag/-O) flag for more information. + Output flags.Map `yaml:"output" json:"output"` + + // @api(CommandLine/Configuration.mapping) represents the language-specific type mappings and features. + // + // Example: + // + // ```yaml + // mapping: + // cpp.vector: "std::vector<%T%>" + // java.array: "ArrayList<%T%>" + // go.map: "map[%K%]%V%" + // python.ext: ".py" + // protobuf.vector: "repeated %T.E%" + // ruby.comment: "# %T%" + // ``` + // + // See the [-M](#CommandLine/Flag/-M) flag for more information. + Mapping flags.Map `yaml:"mapping" json:"mapping"` + + // @api(CommandLine/Configuration.templates) represents the custom template directories or files for each target language. + // + // Example: + // + // ```yaml + // templates: + // go: + // - ./templates/go + // - ./templates/go_extra.npl + // python: + // - ./templates/python.npl + // ``` + // + // See the [-T](#CommandLine/Flag/-T) flag for more information. + Templates flags.MapSlice `yaml:"templates" json:"templates"` + + // @api(CommandLine/Configuration.solvers) represents the custom annotation solver programs for code generation. + // + // Example: + // + // ```yaml + // solvers: + // message: "message-type-allocator message-types.json" + // ``` + // + // See the [-X](#CommandLine/Flag/-X) flag for more information. + Solvers flags.MapSlice `yaml:"solvers" json:"solvers"` +} + +func (o *Configuration) SetupCommandFlags(flagSet *flag.FlagSet, u flags.UsageFunc) { + isAnsiSupported := term.IsTerminal(flagSet.Output()) && term.IsSupportsAnsi() + grey := func(s string) string { + if isAnsiSupported { + return term.Gray.Format(s) + } + return s + } + b := func(s string) string { + if isAnsiSupported { + return term.Bold.Format(s) + } + return s + } + + // @api(CommandLine/Flag/-v) represents the verbosity level of the compiler. + // The default value is **0**, which only shows error messages. + // The value **1** shows debug messages, and **2** shows trace messages. + // Usually, the trace message is used for debugging the compiler itself. + // Levels **1** (debug) and above enable execution of: + // + // - **print** and **printf** in Next source files (.next). + // - **debug** in Next template files (.npl). + // + // Example: + // + // ```sh + // next -v 1 ... + // ``` + flagSet.IntVar(&o.Verbose, "v", 0, u(""+ + "Control verbosity of compiler output and debugging information.\n"+ + "`VERBOSE` levels: "+b("0")+"=error, "+b("1")+"=debug, "+b("2")+"=trace.\n"+ + "Usually, the trace message used for debugging the compiler itself.\n"+ + "Levels "+b("1")+" (debug) and above enable execution of:\n"+ + " -"+b("print")+" and "+b("printf")+" in Next source files (.next).\n"+ + " -"+b("debug")+" in Next template files (.npl).\n", + )) + + // @api(CommandLine/Flag/-t) represents the test mode of the compiler. + // The default value is **false**, which means the compiler is not in test mode. + // The value **true** enables the test mode, which is used for validating but not generating code. + // + // Example: + // + // ```sh + // next -t ... + // ``` + flagSet.BoolVar(&o.test, "t", false, u(""+ + "Enable test mode for validating but not generating code.\n"+ + "Example:\n"+ + " next -t ...\n", + )) + + // @api(CommandLine/Flag/-head) represents the header comment for generated code. + // The value is a string that represents the header comment for generated code. + // The default value is an empty string, which means default header comment is used. + // + // Example: + // + // ```sh + // next -head "Code generated by Next; DO NOT EDIT." ... + // ``` + // + // :::note + // + // Do not add the comment characters like `//` or `/* */` in the header. Next will add them automatically + // based on the target language comment style. + // + // ::: + flagSet.StringVar(&o.Head, "head", "", u(""+ + "Set the `HEAD` comment for each generated file. It will be added to the top of each generated file by {{head}}.\n"+ + "Example:\n"+ + " next -head \"Code generated by Next; DO NOT EDIT.\"\n", + )) + + // @api(CommandLine/Flag/-g) represents the custom grammar for the next source code. + // + // Example: + // + // ```sh + // next -g grammar.yaml ... + // ``` + // + // :::note + // By default, the compiler uses the built-in grammar for the next source code. + // You can set a custom grammar file to define a subset of the next grammar. + // The grammar file is a JSON file that contains rules. + // + // If `-s` is not set, the compiler will ignore unknown annotations and unknown annotation parameters. + // ::: + flagSet.StringVar(&o.Grammar, "g", "", u(""+ + "Set the custom `GRAMMAR` file for the next source code.\n"+ + "The grammar is used to define a subset of the next grammar. It can limit the features of the next code according\n"+ + "by your requirements. The grammar file is a JSON file that contains rules.\n", + )) + + // @api(CommandLine/Flag/-s) represents the strict mode of the compiler. + // The default value is **false**, which means the compiler is not in strict mode. + // The value **true** enables the strict mode, which is used for strict validation of the next source code, + // such as unknown annotations and unknown annotation parameters. + // + // Example: + // + // ```sh + // next -s ... + // ``` + flagSet.BoolVar(&o.Strict, "s", false, u(""+ + "Enable strict mode for strict validation of the next source code.\n"+ + "Example:\n"+ + " next -s ...\n", + )) + + // @api(CommandLine/Flag/-D) represents the custom environment variables for code generation. + // The value is a map of environment variable names and their optional values. + // + // Example: + // + // ```sh + // next -D VERSION=2.1 -D DEBUG -D NAME=myapp ... + // ``` + // + // ```npl + // {{env.NAME}} + // {{env.VERSION}} + // ``` + // + // Output: + // + // ``` + // myapp + // 2.1 + // ``` + flagSet.Var(&o.Env, "D", u(""+ + "Define custom environment variables for use in code generation templates.\n"+ + "`NAME"+grey("[=VALUE]")+"` represents the variable name and its optional value.\n"+ + "Example:\n"+ + " next -D VERSION=2.1 -D DEBUG -D NAME=myapp\n"+ + "And then, use the variables in templates like this: {{env.NAME}}, {{env.VERSION}}\n", + )) + + // @api(CommandLine/Flag/-O) represents the output directories for generated code of each target language. + // + // Example: + // + // ```sh + // next -O go=./output/go -O ts=./output/ts ... + // ``` + // + // :::tip + // + // The `{{meta.path}}` is relative to the output directory. + // + // ::: + flagSet.Var(&o.Output, "O", u(""+ + "Set output directories for generated code, organized by target language.\n"+ + "`LANG=DIR` specifies the target language and its output directory.\n"+ + "Example:\n"+ + " next -O go=./output/go -O ts=./output/ts\n", + )) + + // @api(CommandLine/Flag/-T) represents the custom template directories or files for each target language. + // You can specify multiple templates for a single language. + // + // Example: + // + // ```sh + // next -T go=./templates/go \ + // -T go=./templates/go_extra.npl \ + // -T python=./templates/python.npl \ + // ... + // ``` + flagSet.Var(&o.Templates, "T", u(""+ + "Specify custom template directories or files for each target language.\n"+ + "`LANG=PATH` defines the target language and its template directory or file.\n"+ + "Multiple templates can be specified for a single language.\n"+ + "Example:\n"+ + " next \\\n"+ + " -T go=./templates/go \\\n"+ + " -T go=./templates/go_extra.npl \\\n"+ + " -T python=./templates/python.npl\n", + )) + + // @api(CommandLine/Flag/-M) represents the language-specific type mappings and features. + // **%T%**, **%T.E%**, **%N%**, **%K%**, **%V%** are placeholders replaced with actual types or values. + // **%T.E%** is the final element type of a vector or array. It's used to get the element type of multi-dimensional arrays. + // + // Example: + // + // ```sh + // next -M cpp.vector="std::vector<%T%>" \ + // -M java.array="ArrayList<%T%>" \ + // -M go.map="map[%K%]%V%" \ + // -M python.ext=.py \ + // -M protobuf.vector="repeated %T.E%" \ + // -M ruby.comment="# %T%" \ + // ... + // ``` + flagSet.Var(&o.Mapping, "M", u(""+ + "Configure language-specific type mappings and features.\n"+ + "`LANG.KEY=VALUE` specifies the mappings for a given language and type/feature.\n"+ + "Type mappings: Map Next types to language-specific types.\n"+ + " Primitive types: int, int8, int16, int32, int64, bool, string, any, byte, bytes\n"+ + " Generic types: vector, array, map\n"+ + " "+b("%T%")+", "+b("%T.E%")+", "+b("%N%")+", "+b("%K%")+", "+b("%V%")+" are placeholders replaced with actual types or values.\n"+ + " "+b("%T.E%")+" is the final element type of a vector or array. It's used to get the element type of multi-dimensional arrays.\n"+ + "Feature mappings: Set language-specific properties like file extensions or comment styles.\n"+ + "Example:\n"+ + " next \\\n"+ + " -M cpp.vector=\"std::vector<%T%>\" \\\n"+ + " -M java.array=\"ArrayList<%T%>\" \\\n"+ + " -M go.map=\"map[%K%]%V%\" \\\n"+ + " -M python.ext=.py \\\n"+ + " -M protobuf.vector=\"repeated %T.E%\" \\\n"+ + " -M ruby.comment=\"# %T%\"\n", + )) + + // @api(CommandLine/Flag/-X) represents the custom annotation solver programs for code generation. + // Annotation solvers are executed in a separate process to solve annotations. + // All annotations are passed to the solver program via stdin and stdout. + // The built-in annotation `next` is reserved for the Next compiler. + // + // Example: + // + // ```sh + // next -X message="message-type-allocator message-types.json" ... + // ``` + // + // :::tip + // + // In the example above, the `message-type-allocator` is a custom annotation solver program that + // reads the message types from the `message-types.json` file and rewrite the message types to the + // `message-types.json` file. + // + // ::: + flagSet.Var(&o.Solvers, "X", u(""+ + "Specify custom annotation solver programs for code generation.\n"+ + "`ANNOTATION=PROGRAM` defines the target annotation and its solver program.\n"+ + "Annotation solvers are executed in a separate process to solve annotations.\n"+ + "All annotations are passed to the solver program via stdin and stdout.\n"+ + b("NOTE")+": built-in annotation 'next' is reserved for the Next compiler.\n"+ + "Example:\n"+ + " next -X message=\"message-type-allocator message-types.json\"\n", + )) +} + +func (o *Configuration) resolvePath(currentPath string) { + if o.Grammar != "" { + o.Grammar = filepath.Join(currentPath, o.Grammar) + } + for k, v := range o.Output { + o.Output[k] = filepath.Join(currentPath, v) + } + for k, v := range o.Templates { + for i, t := range v { + o.Templates[k][i] = filepath.Join(currentPath, t) + } + } +} diff --git a/src/compile/template.go b/src/compile/template.go index fbf63e5..b240670 100644 --- a/src/compile/template.go +++ b/src/compile/template.go @@ -566,7 +566,7 @@ func (tc *templateContext) this() reflect.Value { } func (tc *templateContext) env() flags.Map { - return tc.compiler.flags.envs + return tc.compiler.options.Env } func (tc *templateContext) debug(msg string, args ...any) string { @@ -627,7 +627,7 @@ func (tc *templateContext) type_(t any, langs ...string) (string, error) { } func (tc *templateContext) resolveLangType(lang string, t any) (result string, err error) { - mappings := tc.compiler.flags.mappings + mappings := tc.compiler.options.Mapping defer func() { if err == nil && strings.Contains(result, "box(") { // replace box(...) with the actual type @@ -759,11 +759,11 @@ func finalElementType(t Type) Type { } func (tc *templateContext) head() string { - p, ok := tc.compiler.flags.mappings[tc.lang+".comment"] + p, ok := tc.compiler.options.Mapping[tc.lang+".comment"] if !ok { return "" } - header := tc.compiler.flags.head + header := tc.compiler.options.Head if header == "" { header = `Code generated by "next ` + strings.TrimPrefix(builder.Info().Version, "v") + `"; DO NOT EDIT.` } diff --git a/src/grammar/grammar.go b/src/grammar/grammar.go index 8b5554d..5b33b67 100644 --- a/src/grammar/grammar.go +++ b/src/grammar/grammar.go @@ -42,9 +42,9 @@ func duplicated[S ~[]T, T comparable](s S) error { // Context represents the contextual data by the key-value pair. // The key is a string and the value is a JSON object. type Context struct { - Annotations map[string]json.RawMessage `json:",omitempty"` - AnnotationParameters map[string]json.RawMessage `json:",omitempty"` - Validators map[string]json.RawMessage `json:",omitempty"` + Annotations map[string]json.RawMessage `json:"annotations"` + AnnotationParameters map[string]json.RawMessage `json:"annotation_parameters"` + Validators map[string]json.RawMessage `json:"validators"` } // Options represents the options with the id and/or the options. @@ -100,27 +100,42 @@ func (o *Options[T]) resolve(source map[string]json.RawMessage) error { // @api(Grammar) represents the custom grammar for the next files. // // import CodeBlock from "@theme/CodeBlock"; -// import ExampleGrammarSource from "!!raw-loader!@site/example/grammar.json"; +// import Tabs from "@theme/Tabs"; +// import TabItem from "@theme/TabItem"; +// import ExampleGrammarJSONSource from "!!raw-loader!@site/example/grammar.json"; +// import ExampleGrammarYAMLSource from "!!raw-loader!@site/example/grammar.yaml"; // // The grammar is used to define a subset of the next files. It can limit the features of the next code according -// by your requirements. The grammar is a JSON object that contains rules. +// by your requirements. The grammar is a yaml file that contains rules. // // Here is an example of the grammar file: // -//
-// grammar.json -// -// {ExampleGrammarSource} +// +// +// +// {ExampleGrammarJSONSource} // -//
+// +// +// +// {ExampleGrammarYAMLSource} +// +// +// type Grammar struct { - Context Context - Package Package - Import Import - Const Const - Enum Enum - Struct Struct - Interface Interface + Context Context `json:"context"` + Package Package `json:"package"` + Import Import `json:"import"` + Const Const `json:"const"` + Enum Enum `json:"enum"` + Struct Struct `json:"struct"` + Interface Interface `json:"interface"` } type Validators []Options[Validator] @@ -128,17 +143,17 @@ type Validators []Options[Validator] // @api(Grammar/Common/Validator) represents the validator for the grammar rules. type Validator struct { // @api(Grammar/Common/Validator.Name) represents the validator name. - Name string + Name string `json:"name"` // @api(Grammar/Common/Validator.Expression) represents the validator expression. // The expression is a template string that can access the data by the `.` operator. The expression // must return a boolean value. // // The data is the current context object. For example, **package** object for the package validator. - Expression string + Expression string `json:"expression"` // @api(Grammar/Common/Validator.Message) represents the error message when the validator is failed. - Message string + Message string `json:"message"` } func (v Validator) Validate(data any) (bool, error) { @@ -158,24 +173,32 @@ func (v Validator) Validate(data any) (bool, error) { // // Example: // +// +// // ```json // { -// "Struct": { -// "Annotations": [ +// "struct": { +// "annotations": [ // { -// "Name": "message", -// "Description": "Sets the struct as a message.", -// "Parameters": [ +// "name": "message", +// "description": "Sets the struct as a message.", +// "parameters": [ // { -// "Name": "type", -// "Description": "Sets the message type id.", -// "Type": "int", -// "Required": true -// "Validators": [ +// "name": "type", +// "description": "Sets the message type id.", +// "type": "int", +// "required": true +// "validators": [ // { -// "Name": "MessageTypeMustBePositive", -// "Expression": "{{gt . 0}}", -// "Message": "message type must be positive" +// "name": "MessageTypeMustBePositive", +// "expression": "{{gt . 0}}", +// "message": "message type must be positive" // } // ] // } @@ -185,6 +208,25 @@ func (v Validator) Validate(data any) (bool, error) { // } // } // ``` +// +// +// ```yaml +// struct: +// annotations: +// - name: message +// description: Sets the struct as a message. +// parameters: +// - name: type +// description: Sets the message type id. +// type: int +// required: true +// validators: +// - name: MessageTypeMustBePositive +// expression: "{{gt . 0}}" +// message: message type must be positive +// ``` +// +// // // ```next // package demo; @@ -222,13 +264,13 @@ func (v Validator) Validate(data any) (bool, error) { // ``` type Annotation struct { // @api(Grammar/Common/Annotation.Name) represents the annotation name. - Name string + Name string `json:"name"` // @api(Grammar/Common/Annotation.Description) represents the annotation description. - Description string + Description string `json:"description"` // @api(Grammar/Common/Annotation.Parameters) represents the annotation parameters. - Parameters []Options[AnnotationParameter] `json:",omitempty"` + Parameters []Options[AnnotationParameter] `json:"parameters"` } func LookupAnnotation(annotations Annotations, name string) *Annotation { @@ -255,10 +297,10 @@ type AnnotationParameter struct { // - "**type**": matches the annotation name `type` // - "**x|y**": matches the annotation name `x` or `y` // - "**.+_package**": matches the annotation name that ends with `_package`, for example, `cpp_package`, `java_package`, etc. - Name string + Name string `json:"name"` // @api(Grammar/Common/AnnotationParameter.Description) represents the parameter description. - Description string + Description string `json:"description"` // @api(Grammar/Common/AnnotationParameter.Type) represents the parameter type. // The type is a string that can be one of the following types: @@ -268,13 +310,13 @@ type AnnotationParameter struct { // - **float**: float type, the value can be a positive or negative float, for example, `1.23` // - **string**: string type, the value can be a string, for example, `"hello"` // - **type**: any type name, for example, `int`, `float`, `string`, etc. Custom type names are supported. - Type string + Type string `json:"type"` // @api(Grammar/Common/AnnotationParameter.Required) represents the parameter is required or not. - Required bool `json:",omitempty"` + Required bool `json:"required"` // @api(Grammar/Common/AnnotationParameter.Validators) represents the [Validator](#Grammar/Common/Validator) for the annotation parameter. - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` parsed struct { name *regexp.Regexp @@ -298,7 +340,7 @@ func LookupAnnotationParameter(parameters []Options[AnnotationParameter], name s // @api(Grammar/Package) represents the grammar rules for the package declaration. type Package struct { // @api(Grammar/Package.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the package declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/Package.Validators) represents the [Validator](#Grammar/Common/Validator) for the package declaration. // It's used to validate the package name. For example, You can limit the package name must be @@ -307,42 +349,77 @@ type Package struct { // // Example: // + // + // // ```json // { - // "Package": { - // "Validators": [ + // "package": { + // "validators": [ // { - // "Name": "PackageNameNotStartWithUnderscore", - // "Expression": "{{not (hasPrefix `_` .Name)}}", - // "Message": "package name must not start with an underscore" + // "name": "PackageNameNotStartWithUnderscore", + // "expression": "{{not (hasPrefix `_` .Name)}}", + // "message": "package name must not start with an underscore" // } // ] // } // } // ``` + // + // + // ```yaml + // package: + // validators: + // - name: PackageNameNotStartWithUnderscore + // expression: "{{not (hasPrefix `_` .Name)}}" + // message: package name must not start with an underscore + // ``` + // + // // // ```next // // This will error // package _test; // // Error: package name must not start with an underscore // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` } // @api(Grammar/Import) represents the grammar rules for the import declaration. type Import struct { - // @api(Grammar/Import.Off) represents the import declaration is off or not. + // @api(Grammar/Import.Disabled) represents the import declaration is off or not. // If the import declaration is off, the import declaration is not allowed in the next files. // // Example: // + // + // // ```json // { - // "Import": { - // "Off": true + // "import": { + // "disabled": true // } // } // ``` + // + // + // ```yaml + // import: + // disabled: true + // ``` + // + // // // ```next // package demo; @@ -351,23 +428,39 @@ type Import struct { // import "other.next"; // // Error: import declaration is not allowed // ``` - Off bool `json:",omitempty"` + Disabled bool `json:"disabled"` } // @api(Grammar/Const) represents the grammar rules for the const declaration. type Const struct { - // @api(Grammar/Const.Off) represents the const declaration is off or not. + // @api(Grammar/Const.Disabled) represents the const declaration is off or not. // If the const declaration is off, the const declaration is not allowed in the next files. // // Example: // + // + // // ```json // { - // "Const": { - // "Off": true + // "const": { + // "disabled": true // } // } // ``` + // + // + // ```yaml + // const: + // disabled: true + // ``` + // + // // // ```next // package demo; @@ -376,7 +469,7 @@ type Const struct { // const x = 1; // // Error: const declaration is not allowed // ``` - Off bool `json:",omitempty"` + Disabled bool `json:"disabled"` // @api(Grammar/Const.Types) represents a list of type names that are supported in the const declaration. // If no types are defined, the const declaration supports all types. Otherwise, the const declaration @@ -391,13 +484,31 @@ type Const struct { // // Example: // + // + // // ```json // { - // "Const": { - // "Types": ["int", "float"] + // "const": { + // "types": ["int", "float"] // } // } // ``` + // + // + // ```yaml + // const: + // types: + // - int + // - float + // ``` + // + // // // ```next // package demo; @@ -409,29 +520,48 @@ type Const struct { // const z = "hello"; // // Error: string type is not allowed in the const declaration // ``` - Types []string `json:",omitempty"` + Types []string `json:"types"` // @api(Grammar/Const.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the const declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/Const.Validators) represents the [Validator](#Grammar/Common/Validator) for the const declaration. // It's used to validate the const name. You can access the const name by `.Name`. // // Example: // + // + // // ```json // { - // "Const": { - // "Validators": [ + // "const": { + // "validators": [ // { - // "Name": "ConstNameMustBeCapitalized", - // "Expression": "{{eq .Name (.Name | capitalize)}}", - // "Message": "const name must be capitalized" + // "name": "ConstNameMustBeCapitalized", + // "expression": "{{eq .Name (.Name | capitalize)}}", + // "message": "const name must be capitalized" // } // ] // } // } // ``` + // + // + // ```yaml + // const: + // validators: + // - name: ConstNameMustBeCapitalized + // expression: "{{eq .Name (.Name | capitalize)}}" + // message: const name must be capitalized + // ``` + // + // // // ```next // package demo; @@ -442,23 +572,39 @@ type Const struct { // const world = 1; // // Error: const name must be capitalized, expected: World // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` } // @api(Grammar/Enum) represents the grammar rules for the enum declaration. type Enum struct { - // @api(Grammar/Enum.Off) represents the enum declaration is off or not. + // @api(Grammar/Enum.Disabled) represents the enum declaration is off or not. // If the enum declaration is off, the enum declaration is not allowed in the next files. // // Example: // + // + // // ```json // { - // "Enum": { - // "Off": true + // "enum": { + // "disabled": true // } // } // ``` + // + // + // ```yaml + // enum: + // disabled: true + // ``` + // + // // // ```next // package demo; @@ -471,29 +617,48 @@ type Enum struct { // } // // Error: enum declaration is not allowed // ``` - Off bool `json:",omitempty"` + Disabled bool `json:"disabled"` // @api(Grammar/Enum.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the enum declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/Enum.Validators) represents the [Validator](#Grammar/Common/Validator) for the enum declaration. // It's used to validate the enum name. You can access the enum name by `.Name`. // // Example: // + // + // // ```json // { - // "Enum": { - // "Validators": [ + // "enum": { + // "validators": [ // { - // "Name": "EnumNameMustBeCapitalized", - // "Expression": "{{eq .Name (.Name | capitalize)}}", - // "Message": "enum name must be capitalized" + // "name": "EnumNameMustBeCapitalized", + // "expression": "{{eq .Name (.Name | capitalize)}}", + // "message": "enum name must be capitalized" // } // ] // } // } // ``` + // + // + // ```yaml + // enum: + // validators: + // - name: EnumNameMustBeCapitalized + // expression: "{{eq .Name (.Name | capitalize)}}" + // message: enum name must be capitalized + // ``` + // + // // // ```next // package demo; @@ -513,10 +678,10 @@ type Enum struct { // } // // Error: enum name must be capitalized, expected: Size // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` // @api(Grammar/Enum.Member) represents the [EnumMember](#Grammar/EnumMember) grammar rules for the enum declaration. - Member EnumMember + Member EnumMember `json:"member"` } // @api(Grammar/EnumMember) represents the grammar rules for the enum member declaration. @@ -534,15 +699,33 @@ type EnumMember struct { // // Example: // + // + // // ```json // { - // "Enum": { - // "Member": { - // "Types": ["int"] + // "enum": { + // "member": { + // "types": ["int"] // } // } // } // ``` + // + // + // ```yaml + // enum: + // member: + // types: + // - int + // ``` + // + // // // ```next // package demo; @@ -562,22 +745,39 @@ type EnumMember struct { // } // // Error: string type is not allowed in the enum declaration // ``` - Types []string `json:",omitempty"` + Types []string `json:"types"` // @api(Grammar/EnumMember.ValueRequired) represents the enum member value is required or not. // If the enum member value is required, the enum member value must be specified in the next files. // // Example: // + // + // // ```json // { - // "Enum": { - // "Member": { - // "ValueRequired": true + // "enum": { + // "member": { + // "value_required": true // } // } // } // ``` + // + // + // ```yaml + // enum: + // member: + // value_required: true + // ``` + // + // // // ```next // package demo; @@ -590,7 +790,7 @@ type EnumMember struct { // // Error: enum member value is required // } // ``` - ValueRequired bool `json:",omitempty"` + ValueRequired bool `json:"value_required"` // @api(Grammar/EnumMember.ZeroRequired) represents the enum member zero value for integer types is required or not. // @@ -598,15 +798,32 @@ type EnumMember struct { // // Example: // + // + // // ```json // { - // "Enum": { - // "Member": { - // "ZeroRequired": true + // "enum": { + // "member": { + // "zero_required": true // } // } // } // ``` + // + // + // ```yaml + // enum: + // member: + // zero_required: true + // ``` + // + // // // ```next // package demo; @@ -624,10 +841,10 @@ type EnumMember struct { // // Large = 2 // // } // ``` - ZeroRequired bool `json:",omitempty"` + ZeroRequired bool `json:"zero_required"` // @api(Grammar/EnumMember.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the enum member declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/EnumMember.Validators) represents the [Validator](#Grammar/Common/Validator) for the enum member declaration. // @@ -635,21 +852,41 @@ type EnumMember struct { // // Example: // + // + // // ```json // { - // "Enum": { - // "Member": { - // "Validators": [ + // "enum": { + // "member": { + // "validators": [ // { - // "Name": "EnumMemberNameMustBeCapitalized", - // "Expression": "{{eq .Name (.Name | capitalize)}}", - // "Message": "enum member name must be capitalized" + // "name": "EnumMemberNameMustBeCapitalized", + // "expression": "{{eq .Name (.Name | capitalize)}}", + // "message": "enum member name must be capitalized" // } // ] // } // } // } // ``` + // + // + // ```yaml + // enum: + // member: + // validators: + // - name: EnumMemberNameMustBeCapitalized + // expression: "{{eq .Name (.Name | capitalize)}}" + // message: enum member name must be capitalized + // ``` + // + // // // ```next // package demo; @@ -662,23 +899,39 @@ type EnumMember struct { // // Error: enum member name must be capitalized, expected: Large // } // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` } // @api(Grammar/Struct) represents the grammar rules for the struct declaration. type Struct struct { - // @api(Grammar/Struct.Off) represents the struct declaration is off or not. + // @api(Grammar/Struct.Disabled) represents the struct declaration is off or not. // If the struct declaration is off, the struct declaration is not allowed in the next files. // // Example: // + // + // // ```json // { - // "Struct": { - // "Off": true + // "struct": { + // "disabled": true // } // } // ``` + // + // + // ```yaml + // struct: + // disabled: true + // ``` + // + // // // ```next // package demo; @@ -690,29 +943,48 @@ type Struct struct { // } // // Error: struct declaration is not allowed // ``` - Off bool `json:",omitempty"` + Disabled bool `json:"disabled"` // @api(Grammar/Struct.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the struct declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/Struct.Validators) represents the [Validator](#Grammar/Common/Validator) for the struct declaration. // It's used to validate the struct name. You can access the struct name by `.Name`. // // Example: // + // + // // ```json // { - // "Struct": { - // "Validators": [ + // "struct": { + // "validators": [ // { - // "Name": "StructNameMustBeCapitalized", - // "Expression": "{{eq .Name (.Name | capitalize)}}", - // "Message": "struct name must be capitalized" + // "name": "StructNameMustBeCapitalized", + // "expression": "{{eq .Name (.Name | capitalize)}}", + // "message": "struct name must be capitalized" // } // ] // } // } // ``` + // + // + // ```yaml + // struct: + // validators: + // - name: StructNameMustBeCapitalized + // expression: "{{eq .Name (.Name | capitalize)}}" + // message: struct name must be capitalized + // ``` + // + // // // ```next // package demo; @@ -730,37 +1002,57 @@ type Struct struct { // } // // Error: struct name must be capitalized, expected: Point // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` // @api(Grammar/Struct.Field) represents the [StructField](#Grammar/StructField) grammar rules for the struct declaration. - Field StructField + Field StructField `json:"field"` } // @api(Grammar/StructField) represents the grammar rules for the struct field declaration. type StructField struct { // @api(Grammar/StructField.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the struct field declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/StructField.Validators) represents the [Validator](#Grammar/Common/Validator) for the struct field declaration. // It's used to validate the struct field name. You can access the struct field name by `.Name`. // // Example: // + // + // // ```json // { - // "Struct": { - // "Field": { - // "Validators": [ + // "struct": { + // "field": { + // "validators": [ // { - // "Name": "StructFieldNameMustNotBeCapitalized", - // "Expression": "{{ne .Name (capitalize .Name)}}", - // "Message": "struct field name must not be capitalized" + // "name": "StructFieldNameMustNotBeCapitalized", + // "expression": "{{ne .Name (capitalize .Name)}}", + // "message": "struct field name must not be capitalized" // } // ] // } // } // } // ``` + // + // + // ```yaml + // struct: + // field: + // validators: + // - name: StructFieldNameMustNotBeCapitalized + // expression: "{{ne .Name (capitalize .Name)}}" + // message: struct field name must not be capitalized + // ``` + // + // // // ```next // package demo; @@ -772,23 +1064,39 @@ type StructField struct { // // Error: struct field name must not be capitalized, expected: name // } // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` } // @api(Grammar/Interface) represents the grammar rules for the interface declaration. type Interface struct { - // @api(Grammar/Interface.Off) represents the interface declaration is off or not. + // @api(Grammar/Interface.Disabled) represents the interface declaration is off or not. // If the interface declaration is off, the interface declaration is not allowed in the next files. // // Example: // + // + // // ```json // { - // "Interface": { - // "Off": true + // "interface": { + // "disabled": true // } // } // ``` + // + // + // ```yaml + // interface: + // disabled: true + // ``` + // + // // // ```next // package demo; @@ -799,29 +1107,48 @@ type Interface struct { // } // // Error: interface declaration is not allowed // ``` - Off bool `json:",omitempty"` + Disabled bool `json:"disabled"` // @api(Grammar/Interface.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the interface declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/Interface.Validators) represents the [Validator](#Grammar/Common/Validator) for the interface declaration. // It's used to validate the interface name. You can access the interface name by `.Name`. // // Example: // + // + // // ```json // { - // "Interface": { - // "Validators": [ + // "interface": { + // "validators": [ // { - // "Name": "InterfaceNameMustBeCapitalized", - // "Expression": "{{eq .Name (.Name | capitalize)}}", - // "Message": "interface name must be capitalized" + // "name": "InterfaceNameMustBeCapitalized", + // "expression": "{{eq .Name (.Name | capitalize)}}", + // "message": "interface name must be capitalized" // } // ] // } // } // ``` + // + // + // ```yaml + // interface: + // validators: + // - name: InterfaceNameMustBeCapitalized + // expression: "{{eq .Name (.Name | capitalize)}}" + // message: interface name must be capitalized + // ``` + // + // // // ```next // package demo; @@ -837,65 +1164,99 @@ type Interface struct { // } // // Error: interface name must be capitalized, expected: User // ``` - Validators Validators `json:",omitempty"` + Validators Validators `json:"validators"` // @api(Grammar/Interface.Method) represents the [InterfaceMethod](#Grammar/InterfaceMethod) grammar rules for the interface declaration. - Method InterfaceMethod + Method InterfaceMethod `json:"method"` } // @api(Grammar/InterfaceMethod) represents the grammar rules for the interface method declaration. // // Example: // +// +// // ```json // { -// "Interface": { -// "Method": { -// "Annotations": [ +// "interface": { +// "method": { +// "annotations": [ // { -// "Name": "http", -// "Description": "Sets the method as an HTTP handler.", -// "Parameters": [ +// "name": "http", +// "description": "Sets the method as an HTTP handler.", +// "parameters": [ // { -// "Name": "method", -// "Description": "Sets the HTTP method.", -// "Type": "string", -// "Required": true -// "Validators": [ +// "name": "method", +// "description": "Sets the HTTP method.", +// "type": "string", +// "required": true +// "validators": [ // { -// "Name": "HTTPMethodMustBeValid", -// "Expression": "{{includes (list `GET` `POST` `PUT` `DELETE` `PATCH` `HEAD` `OPTIONS` `TRACE` `CONNECT`) .}}", -// "Message": "http method must be valid" +// "name": "HTTPMethodMustBeValid", +// "expression": "{{includes (list `GET` `POST` `PUT` `DELETE` `PATCH` `HEAD` `OPTIONS` `TRACE` `CONNECT`) .}}", +// "message": "http method must be valid" // } // ] // } // ] // } // ], -// "Validators": [ +// "validators": [ // { -// "Name": "MethodNameMustBeCapitalized", -// "Expression": "{{eq .Name (.Name | capitalize)}}", -// "Message": "method name must be capitalized" +// "name": "MethodNameMustBeCapitalized", +// "expression": "{{eq .Name (.Name | capitalize)}}", +// "message": "method name must be capitalized" // } // ] // } // } // } // ``` +// +// +// ```yaml +// interface: +// method: +// annotations: +// - name: http +// description: Sets the method as an HTTP handler. +// parameters: +// - name: method +// description: Sets the HTTP method. +// type: string +// required: true +// validators: +// - name: HTTPMethodMustBeValid +// expression: "{{includes (list `GET` `POST` `PUT` `DELETE` `PATCH` `HEAD` `OPTIONS` `TRACE` `CONNECT`) .}}" +// message: http method must be valid +// validators: +// - name: MethodNameMustBeCapitalized +// expression: "{{eq .Name (.Name | capitalize)}}" +// message: method name must be capitalized +// ``` +// +// type InterfaceMethod struct { // @api(Grammar/InterfaceMethod.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the interface method declaration. - Annotations Annotations `json:",omitempty"` + Annotations Annotations `json:"annotations"` // @api(Grammar/InterfaceMethod.Validators) represents the [Validator](#Grammar/Common/Validator) for the interface method declaration. - Validators Validators `json:",omitempty"` - - // @api(Grammar/InterfaceMethod/Parameter) represents the grammar rules for the interface method parameter declaration. - Parameter struct { - // @api(Grammar/InterfaceMethod/Parameter.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the interface method parameter declaration. - Annotations Annotations `json:",omitempty"` - // @api(Grammar/InterfaceMethod/Parameter.Validators) represents the [Validator](#Grammar/Common/Validator) for the interface method parameter declaration. - Validators Validators `json:",omitempty"` - } + Validators Validators `json:"validators"` + // @api(Grammar/InterfaceMethod.Parameter) represents the [InterfaceMethodParameter](#Grammar/InterfaceMethodParameter) grammar rules for the interface method declaration. + Parameter InterfaceMethodParameter `json:"parameter"` +} + +// @api(Grammar/InterfaceMethodParameter) represents the grammar rules for the interface method parameter declaration. +type InterfaceMethodParameter struct { + // @api(Grammar/InterfaceMethodParameter.Annotations) represents the [Annotation](#Grammar/Common/Annotation) grammar rules for the interface method parameter declaration. + Annotations Annotations `json:"annotations"` + // @api(Grammar/InterfaceMethodParameter.Validators) represents the [Validator](#Grammar/Common/Validator) for the interface method parameter declaration. + Validators Validators `json:"validators"` } // Resolve resolves the grammar rules. @@ -1160,7 +1521,7 @@ func LANG_type() AnnotationParameter { // Run the following command to generate the default grammar: // // ```sh -// next grammar grammar.json +// next grammar grammar.yaml // ``` // // You can use this grammar as a starting point for your own grammar. @@ -1217,10 +1578,7 @@ var Default = Grammar{ Annotations: at("next@interface", "deprecated"), Method: InterfaceMethod{ Annotations: at("next@interface.method", "deprecated"), - Parameter: struct { - Annotations Annotations `json:",omitempty"` - Validators Validators `json:",omitempty"` - }{ + Parameter: InterfaceMethodParameter{ Annotations: at("next@interface.method.parameter", "deprecated"), }, }, diff --git a/website/docs/api/preview/command_line.mdx b/website/docs/api/preview/command_line.mdx index 2c2c848..25e143d 100644 --- a/website/docs/api/preview/command_line.mdx +++ b/website/docs/api/preview/command_line.mdx @@ -11,12 +11,25 @@ pagination_next: null Example: ```sh -next grammar grammar.json +next grammar # generate the default grammar to the standard output with YAML format +next grammar grammar.yaml # generate the default grammar with YAML format +next grammar grammar.yml # generate the default grammar with YAML format +next grammar grammar.json # generate the default grammar with JSON format ``` -Or you can run the following command to generate the default grammar to the standard output: +:::note +The default grammar is generated in YAML format if the file extension is not provided. +Currently, the supported file extensions are `.json`, `.yaml`, and `.yml` (alias of `.yaml`). +::: + +### run {#user-content-CommandLine_Command_run} + +`run` command runs a next project. It reads the project file and compiles the source files according to the project configuration. The project file is a YAML file that contains the project [Configuration](#user-content-CommandLine_Configuration). + +Example: + ```sh -next grammar +next run demo.yaml ``` ### version {#user-content-CommandLine_Command_version} @@ -35,6 +48,190 @@ Output: next v0.0.4(main: 51864a35de7890d63bfd8acecdb62d20372ca963) built at 2024/09/27T22:58:21+0800 by go1.23.0 ``` +## Configuration {#user-content-CommandLine_Configuration} + +import CodeBlock from "@theme/CodeBlock"; +import ExampleProjSource from "!!raw-loader!@site/example/example.proj.yaml"; + +Configuration represents the configuration of the Next project. The configuration is used to mamange the compiler options, such as verbosity, output directories, and custom templates. If the configuration is provided, you can generate code like this: + +```sh +next run example.proj.yaml +``` + +The configuration file is a YAML or JSON file that contains the compiler options. Here is an example of the configuration file: + + + {ExampleProjSource} + + +###### .env {#user-content-CommandLine_Configuration__env} +
+ +`.env` represents the custom environment variables for code generation. + +Example: + +```yaml +env: + VERSION: "2.1" + DEBUG: "" + NAME: myapp +``` + +See the [-D](#user-content-CommandLine_Flag_-D) flag for more information. + +
+ +###### .grammar {#user-content-CommandLine_Configuration__grammar} +
+ +`.grammar` represents the custom grammar for the next source code. + +Example: + +```yaml +grammar: grammar.yaml +``` + +See the [-g](#user-content-CommandLine_Flag_-g) flag for more information. + +
+ +###### .head {#user-content-CommandLine_Configuration__head} +
+ +`.head` represents the header comment for generated code. + +Example: + +```yaml +head: "Code generated by Next; DO NOT EDIT." +``` + +See the [-head](#user-content-CommandLine_Flag_-head) flag for more information. + +
+ +###### .mapping {#user-content-CommandLine_Configuration__mapping} +
+ +`.mapping` represents the language-specific type mappings and features. + +Example: + +```yaml +mapping: + cpp.vector: "std::vector<%T%>" + java.array: "ArrayList<%T%>" + go.map: "map[%K%]%V%" + python.ext: ".py" + protobuf.vector: "repeated %T.E%" + ruby.comment: "# %T%" +``` + +See the [-M](#user-content-CommandLine_Flag_-M) flag for more information. + +
+ +###### .output {#user-content-CommandLine_Configuration__output} +
+ +`.output` represents the output directories for generated code of each target language. + +Example: + +```yaml +output: + go: ./output/go + ts: ./output/ts +``` + +See the [-O](#user-content-CommandLine_Flag_-O) flag for more information. + +
+ +###### .solvers {#user-content-CommandLine_Configuration__solvers} +
+ +`.solvers` represents the custom annotation solver programs for code generation. + +Example: + +```yaml +solvers: + message: "message-type-allocator message-types.json" +``` + +See the [-X](#user-content-CommandLine_Flag_-X) flag for more information. + +
+ +###### .sources {#user-content-CommandLine_Configuration__sources} +
+ +`.sources` represents the source directories or files. + +Example: + +```yaml +sources: + - demo.next + - src/next/ +``` + +
+ +###### .strict {#user-content-CommandLine_Configuration__strict} +
+ +`.strict` represents the strict mode of the compiler. + +Example: + +```yaml +strict: true +``` + +See the [-s](#user-content-CommandLine_Flag_-s) flag for more information. + +
+ +###### .templates {#user-content-CommandLine_Configuration__templates} +
+ +`.templates` represents the custom template directories or files for each target language. + +Example: + +```yaml +templates: + go: + - ./templates/go + - ./templates/go_extra.npl + python: + - ./templates/python.npl +``` + +See the [-T](#user-content-CommandLine_Flag_-T) flag for more information. + +
+ +###### .verbose {#user-content-CommandLine_Configuration__verbose} +
+ +`.verbose` represents the verbosity level of the compiler. + +Example: + +```yaml +verbose: 1 +``` + +See the [-v](#user-content-CommandLine_Flag_-v) flag for more information. + +
+ ## Flag {#user-content-CommandLine_Flag} ### -D {#user-content-CommandLine_Flag_-D} @@ -126,7 +323,7 @@ In the example above, the `message-type-allocator` is a custom annotation solver Example: ```sh -next -g grammar.json ... +next -g grammar.yaml ... ``` :::note diff --git a/website/docs/api/preview/grammar.mdx b/website/docs/api/preview/grammar.mdx index 36dd9d4..c348b17 100644 --- a/website/docs/api/preview/grammar.mdx +++ b/website/docs/api/preview/grammar.mdx @@ -7,18 +7,33 @@ pagination_next: null `Grammar` represents the custom grammar for the next files. import CodeBlock from "@theme/CodeBlock"; -import ExampleGrammarSource from "!!raw-loader!@site/example/grammar.json"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import ExampleGrammarJSONSource from "!!raw-loader!@site/example/grammar.json"; +import ExampleGrammarYAMLSource from "!!raw-loader!@site/example/grammar.yaml"; -The grammar is used to define a subset of the next files. It can limit the features of the next code according by your requirements. The grammar is a JSON object that contains rules. +The grammar is used to define a subset of the next files. It can limit the features of the next code according by your requirements. The grammar is a yaml file that contains rules. Here is an example of the grammar file: -
- grammar.json - - {ExampleGrammarSource} + + + + {ExampleGrammarJSONSource} -
+ + + + {ExampleGrammarYAMLSource} + + + ###### .default {#user-content-Grammar__default}
@@ -30,7 +45,7 @@ Here is an example of the grammar file: Run the following command to generate the default grammar: ```sh -next grammar grammar.json +next grammar grammar.yaml ``` You can use this grammar as a starting point for your own grammar. @@ -46,24 +61,32 @@ You can use this grammar as a starting point for your own grammar. Example: + + ```json { - "Struct": { - "Annotations": [ + "struct": { + "annotations": [ { - "Name": "message", - "Description": "Sets the struct as a message.", - "Parameters": [ + "name": "message", + "description": "Sets the struct as a message.", + "parameters": [ { - "Name": "type", - "Description": "Sets the message type id.", - "Type": "int", - "Required": true - "Validators": [ + "name": "type", + "description": "Sets the message type id.", + "type": "int", + "required": true + "validators": [ { - "Name": "MessageTypeMustBePositive", - "Expression": "{{gt . 0}}", - "Message": "message type must be positive" + "name": "MessageTypeMustBePositive", + "expression": "{{gt . 0}}", + "message": "message type must be positive" } ] } @@ -73,6 +96,25 @@ Example: } } ``` + + +```yaml +struct: + annotations: + - name: message + description: Sets the struct as a message. + parameters: + - name: type + description: Sets the message type id. + type: int + required: true + validators: + - name: MessageTypeMustBePositive + expression: "{{gt . 0}}" + message: message type must be positive +``` + + ```next package demo; @@ -223,20 +265,36 @@ The data is the current context object. For example, **package** object for the
-###### .Off {#user-content-Grammar_Const__Off} +###### .Disabled {#user-content-Grammar_Const__Disabled}
-`.Off` represents the const declaration is off or not. If the const declaration is off, the const declaration is not allowed in the next files. +`.Disabled` represents the const declaration is off or not. If the const declaration is off, the const declaration is not allowed in the next files. Example: + + ```json { - "Const": { - "Off": true + "const": { + "disabled": true } } ``` + + +```yaml +const: + disabled: true +``` + + ```next package demo; @@ -262,13 +320,31 @@ Currently, each type name can be one of the following types: Example: + + ```json { - "Const": { - "Types": ["int", "float"] + "const": { + "types": ["int", "float"] } } ``` + + +```yaml +const: + types: + - int + - float +``` + + ```next package demo; @@ -290,19 +366,38 @@ const z = "hello"; Example: + + ```json { - "Const": { - "Validators": [ + "const": { + "validators": [ { - "Name": "ConstNameMustBeCapitalized", - "Expression": "{{eq .Name (.Name | capitalize)}}", - "Message": "const name must be capitalized" + "name": "ConstNameMustBeCapitalized", + "expression": "{{eq .Name (.Name | capitalize)}}", + "message": "const name must be capitalized" } ] } } ``` + + +```yaml +const: + validators: + - name: ConstNameMustBeCapitalized + expression: "{{eq .Name (.Name | capitalize)}}" + message: const name must be capitalized +``` + + ```next package demo; @@ -327,27 +422,36 @@ const world = 1;
-###### .Member {#user-content-Grammar_Enum__Member} +###### .Disabled {#user-content-Grammar_Enum__Disabled}
-`.Member` represents the [EnumMember](#user-content-Grammar_EnumMember) grammar rules for the enum declaration. - -
- -###### .Off {#user-content-Grammar_Enum__Off} -
- -`.Off` represents the enum declaration is off or not. If the enum declaration is off, the enum declaration is not allowed in the next files. +`.Disabled` represents the enum declaration is off or not. If the enum declaration is off, the enum declaration is not allowed in the next files. Example: + + ```json { - "Enum": { - "Off": true + "enum": { + "disabled": true } } ``` + + +```yaml +enum: + disabled: true +``` + + ```next package demo; @@ -363,6 +467,13 @@ enum Color {
+###### .Member {#user-content-Grammar_Enum__Member} +
+ +`.Member` represents the [EnumMember](#user-content-Grammar_EnumMember) grammar rules for the enum declaration. + +
+ ###### .Validators {#user-content-Grammar_Enum__Validators}
@@ -370,19 +481,38 @@ enum Color { Example: + + ```json { - "Enum": { - "Validators": [ + "enum": { + "validators": [ { - "Name": "EnumNameMustBeCapitalized", - "Expression": "{{eq .Name (.Name | capitalize)}}", - "Message": "enum name must be capitalized" + "name": "EnumNameMustBeCapitalized", + "expression": "{{eq .Name (.Name | capitalize)}}", + "message": "enum name must be capitalized" } ] } } ``` + + +```yaml +enum: + validators: + - name: EnumNameMustBeCapitalized + expression: "{{eq .Name (.Name | capitalize)}}" + message: enum name must be capitalized +``` + + ```next package demo; @@ -431,15 +561,33 @@ Currently, each type name can be one of the following types: Example: + + ```json { - "Enum": { - "Member": { - "Types": ["int"] + "enum": { + "member": { + "types": ["int"] } } } ``` + + +```yaml +enum: + member: + types: + - int +``` + + ```next package demo; @@ -471,21 +619,41 @@ It's used to validate the enum member name. You can access the enum member name Example: + + ```json { - "Enum": { - "Member": { - "Validators": [ + "enum": { + "member": { + "validators": [ { - "Name": "EnumMemberNameMustBeCapitalized", - "Expression": "{{eq .Name (.Name | capitalize)}}", - "Message": "enum member name must be capitalized" + "name": "EnumMemberNameMustBeCapitalized", + "expression": "{{eq .Name (.Name | capitalize)}}", + "message": "enum member name must be capitalized" } ] } } } ``` + + +```yaml +enum: + member: + validators: + - name: EnumMemberNameMustBeCapitalized + expression: "{{eq .Name (.Name | capitalize)}}" + message: enum member name must be capitalized +``` + + ```next package demo; @@ -508,15 +676,32 @@ enum Size { Example: + + ```json { - "Enum": { - "Member": { - "ValueRequired": true + "enum": { + "member": { + "value_required": true } } } ``` + + +```yaml +enum: + member: + value_required: true +``` + + ```next package demo; @@ -541,15 +726,32 @@ If the enum member zero value is required, the enum member zero value must be sp Example: + + ```json { - "Enum": { - "Member": { - "ZeroRequired": true + "enum": { + "member": { + "zero_required": true } } } ``` + + +```yaml +enum: + member: + zero_required: true +``` + + ```next package demo; @@ -574,20 +776,36 @@ enum Size { `Import` represents the grammar rules for the import declaration. -###### .Off {#user-content-Grammar_Import__Off} +###### .Disabled {#user-content-Grammar_Import__Disabled}
-`.Off` represents the import declaration is off or not. If the import declaration is off, the import declaration is not allowed in the next files. +`.Disabled` represents the import declaration is off or not. If the import declaration is off, the import declaration is not allowed in the next files. Example: + + ```json { - "Import": { - "Off": true + "import": { + "disabled": true } } ``` + + +```yaml +import: + disabled: true +``` + + ```next package demo; @@ -610,27 +828,36 @@ import "other.next";
-###### .Method {#user-content-Grammar_Interface__Method} -
- -`.Method` represents the [InterfaceMethod](#user-content-Grammar_InterfaceMethod) grammar rules for the interface declaration. - -
- -###### .Off {#user-content-Grammar_Interface__Off} +###### .Disabled {#user-content-Grammar_Interface__Disabled}
-`.Off` represents the interface declaration is off or not. If the interface declaration is off, the interface declaration is not allowed in the next files. +`.Disabled` represents the interface declaration is off or not. If the interface declaration is off, the interface declaration is not allowed in the next files. Example: + + ```json { - "Interface": { - "Off": true + "interface": { + "disabled": true } } ``` + + +```yaml +interface: + disabled: true +``` + + ```next package demo; @@ -644,6 +871,13 @@ interface User {
+###### .Method {#user-content-Grammar_Interface__Method} +
+ +`.Method` represents the [InterfaceMethod](#user-content-Grammar_InterfaceMethod) grammar rules for the interface declaration. + +
+ ###### .Validators {#user-content-Grammar_Interface__Validators}
@@ -651,19 +885,38 @@ interface User { Example: + + ```json { - "Interface": { - "Validators": [ + "interface": { + "validators": [ { - "Name": "InterfaceNameMustBeCapitalized", - "Expression": "{{eq .Name (.Name | capitalize)}}", - "Message": "interface name must be capitalized" + "name": "InterfaceNameMustBeCapitalized", + "expression": "{{eq .Name (.Name | capitalize)}}", + "message": "interface name must be capitalized" } ] } } ``` + + +```yaml +interface: + validators: + - name: InterfaceNameMustBeCapitalized + expression: "{{eq .Name (.Name | capitalize)}}" + message: interface name must be capitalized +``` + + ```next package demo; @@ -688,42 +941,74 @@ interface user { Example: + + ```json { - "Interface": { - "Method": { - "Annotations": [ + "interface": { + "method": { + "annotations": [ { - "Name": "http", - "Description": "Sets the method as an HTTP handler.", - "Parameters": [ + "name": "http", + "description": "Sets the method as an HTTP handler.", + "parameters": [ { - "Name": "method", - "Description": "Sets the HTTP method.", - "Type": "string", - "Required": true - "Validators": [ + "name": "method", + "description": "Sets the HTTP method.", + "type": "string", + "required": true + "validators": [ { - "Name": "HTTPMethodMustBeValid", - "Expression": "{{includes (list `GET` `POST` `PUT` `DELETE` `PATCH` `HEAD` `OPTIONS` `TRACE` `CONNECT`) .}}", - "Message": "http method must be valid" + "name": "HTTPMethodMustBeValid", + "expression": "{{includes (list `GET` `POST` `PUT` `DELETE` `PATCH` `HEAD` `OPTIONS` `TRACE` `CONNECT`) .}}", + "message": "http method must be valid" } ] } ] } ], - "Validators": [ + "validators": [ { - "Name": "MethodNameMustBeCapitalized", - "Expression": "{{eq .Name (.Name | capitalize)}}", - "Message": "method name must be capitalized" + "name": "MethodNameMustBeCapitalized", + "expression": "{{eq .Name (.Name | capitalize)}}", + "message": "method name must be capitalized" } ] } } } ``` + + +```yaml +interface: + method: + annotations: + - name: http + description: Sets the method as an HTTP handler. + parameters: + - name: method + description: Sets the HTTP method. + type: string + required: true + validators: + - name: HTTPMethodMustBeValid + expression: "{{includes (list `GET` `POST` `PUT` `DELETE` `PATCH` `HEAD` `OPTIONS` `TRACE` `CONNECT`) .}}" + message: http method must be valid + validators: + - name: MethodNameMustBeCapitalized + expression: "{{eq .Name (.Name | capitalize)}}" + message: method name must be capitalized +``` + + ###### .Annotations {#user-content-Grammar_InterfaceMethod__Annotations}
@@ -732,6 +1017,13 @@ Example:
+###### .Parameter {#user-content-Grammar_InterfaceMethod__Parameter} +
+ +`.Parameter` represents the [InterfaceMethodParameter](#user-content-Grammar_InterfaceMethodParameter) grammar rules for the interface method declaration. + +
+ ###### .Validators {#user-content-Grammar_InterfaceMethod__Validators}
@@ -739,18 +1031,18 @@ Example:
-### Parameter {#user-content-Grammar_InterfaceMethod_Parameter} +## InterfaceMethodParameter {#user-content-Grammar_InterfaceMethodParameter} -`Parameter` represents the grammar rules for the interface method parameter declaration. +`InterfaceMethodParameter` represents the grammar rules for the interface method parameter declaration. -###### .Annotations {#user-content-Grammar_InterfaceMethod_Parameter__Annotations} +###### .Annotations {#user-content-Grammar_InterfaceMethodParameter__Annotations}
`.Annotations` represents the [Annotation](#user-content-Grammar_Common_Annotation) grammar rules for the interface method parameter declaration.
-###### .Validators {#user-content-Grammar_InterfaceMethod_Parameter__Validators} +###### .Validators {#user-content-Grammar_InterfaceMethodParameter__Validators}
`.Validators` represents the [Validator](#user-content-Grammar_Common_Validator) for the interface method parameter declaration. @@ -775,19 +1067,38 @@ Example: Example: + + ```json { - "Package": { - "Validators": [ + "package": { + "validators": [ { - "Name": "PackageNameNotStartWithUnderscore", - "Expression": "{{not (hasPrefix `_` .Name)}}", - "Message": "package name must not start with an underscore" + "name": "PackageNameNotStartWithUnderscore", + "expression": "{{not (hasPrefix `_` .Name)}}", + "message": "package name must not start with an underscore" } ] } } ``` + + +```yaml +package: + validators: + - name: PackageNameNotStartWithUnderscore + expression: "{{not (hasPrefix `_` .Name)}}" + message: package name must not start with an underscore +``` + + ```next // This will error @@ -808,27 +1119,36 @@ package _test;
-###### .Field {#user-content-Grammar_Struct__Field} +###### .Disabled {#user-content-Grammar_Struct__Disabled}
-`.Field` represents the [StructField](#user-content-Grammar_StructField) grammar rules for the struct declaration. - -
- -###### .Off {#user-content-Grammar_Struct__Off} -
- -`.Off` represents the struct declaration is off or not. If the struct declaration is off, the struct declaration is not allowed in the next files. +`.Disabled` represents the struct declaration is off or not. If the struct declaration is off, the struct declaration is not allowed in the next files. Example: + + ```json { - "Struct": { - "Off": true + "struct": { + "disabled": true } } ``` + + +```yaml +struct: + disabled: true +``` + + ```next package demo; @@ -843,6 +1163,13 @@ struct User {
+###### .Field {#user-content-Grammar_Struct__Field} +
+ +`.Field` represents the [StructField](#user-content-Grammar_StructField) grammar rules for the struct declaration. + +
+ ###### .Validators {#user-content-Grammar_Struct__Validators}
@@ -850,19 +1177,38 @@ struct User { Example: + + ```json { - "Struct": { - "Validators": [ + "struct": { + "validators": [ { - "Name": "StructNameMustBeCapitalized", - "Expression": "{{eq .Name (.Name | capitalize)}}", - "Message": "struct name must be capitalized" + "name": "StructNameMustBeCapitalized", + "expression": "{{eq .Name (.Name | capitalize)}}", + "message": "struct name must be capitalized" } ] } } ``` + + +```yaml +struct: + validators: + - name: StructNameMustBeCapitalized + expression: "{{eq .Name (.Name | capitalize)}}" + message: struct name must be capitalized +``` + + ```next package demo; @@ -901,21 +1247,41 @@ struct point { Example: + + ```json { - "Struct": { - "Field": { - "Validators": [ + "struct": { + "field": { + "validators": [ { - "Name": "StructFieldNameMustNotBeCapitalized", - "Expression": "{{ne .Name (capitalize .Name)}}", - "Message": "struct field name must not be capitalized" + "name": "StructFieldNameMustNotBeCapitalized", + "expression": "{{ne .Name (capitalize .Name)}}", + "message": "struct field name must not be capitalized" } ] } } } ``` + + +```yaml +struct: + field: + validators: + - name: StructFieldNameMustNotBeCapitalized + expression: "{{ne .Name (capitalize .Name)}}" + message: struct field name must not be capitalized +``` + + ```next package demo; diff --git a/website/example/example.proj.yaml b/website/example/example.proj.yaml new file mode 100644 index 0000000..24ee833 --- /dev/null +++ b/website/example/example.proj.yaml @@ -0,0 +1,53 @@ +# This is a project configuration file. Usage: next run example.proj.yaml + +# Enable the strict mode, like the -s flag +strict: true + +# Verbose level, like the -v flag +# 0: quiet, 1: normal, 2: verbose +verbose: 0 + +# Grammar file, like the -g flag +grammar: grammar.yaml + +# Variables, like the -d flag +env: + PROJECT_NAME: demo + +# Output directories for target languages, like the -O flag +output: + go: gen/go + java: gen/java + cpp: gen/cpp + csharp: gen/csharp + c: gen/c + rust: gen/rust/src + protobuf: gen/protobuf + js: gen/js + ts: gen/ts + python: gen/python + php: gen/php + lua: gen/lua + +# Templates for target languages, like the -T flag +templates: + go: [templates/go] + java: [templates/java] + cpp: [templates/cpp] + csharp: [templates/csharp] + c: [templates/c] + rust: [templates/rust] + protobuf: [templates/protobuf] + js: [templates/js] + ts: [templates/ts] + python: [templates/python] + php: [templates/php] + lua: [templates/lua] + +# Features or types mapping, like the -M flag +mapping: + c.vector: void* + c.map: void* + +# Source directories or files, like the rest arguments in the command line +sources: [next] \ No newline at end of file diff --git a/website/example/grammar.json b/website/example/grammar.json index 254fc80..36644c8 100644 --- a/website/example/grammar.json +++ b/website/example/grammar.json @@ -1,207 +1,246 @@ { - "Context": { - "Annotations": { - "message": { - "Name": "message", - "Description": "Sets the declaration as a message.", - "Parameters": [ - { - "Name": "type", - "Description": "Sets the message type id", - "Type": "int", - "Validators": [ - { - "Name": "MessageTypeMustBePositive", - "Expression": "{{gt . 0}}", - "Message": "must be positive" - } - ] - }, - { - "Name": "req", - "Description": "Sets the message as a request.", - "Type": "bool" - } - ] - }, - "deprecated": { - "Name": "deprecated", - "Description": "Sets the declaration as deprecated.", - "Parameters": [ - { - "Name": "message", - "Description": "Sets the deprecation message.", - "Type": "string" - } - ] - }, - "next@const": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available" - ] - }, - "next@enum": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available", - "type" - ] - }, - "next@enum.member": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available" - ] - }, - "next@interface": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available", - "*_alias" - ] - }, - "next@interface.method": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available", - "mut", - "error" - ] - }, - "next@interface.method.parameter": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "mut", - "*_type" - ] - }, - "next@package": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available", - "*_package", - "*_imports" - ] - }, - "next@struct": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available", - "*_alias" - ] - }, - "next@struct.field": { - "Name": "next", - "Description": "Built-in annotation for the next compiler.", - "Parameters": [ - "available", - "*_type" - ] - }, - "optional": { - "Name": "optional", - "Description": "Sets the field as optional.", - "Parameters": [ - { - "Name": "default", - "Description": "Sets the default value for optional fields.", - "Type": "string" - } + "context": { + "annotations": { + "message": { + "name": "message", + "description": "Sets the declaration as a message.", + "parameters": [ + { + "name": "type", + "description": "Sets the message type id", + "type": "int", + "validators": [ + { + "name": "MessageTypeMustBePositive", + "expression": "{{gt . 0}}", + "message": "must be positive" + } + ] + }, + { + "name": "req", + "description": "Sets the message as a request.", + "type": "bool" + } + ] + }, + "deprecated": { + "name": "deprecated", + "description": "Sets the declaration as deprecated.", + "parameters": [ + { + "name": "message", + "description": "Sets the deprecation message.", + "type": "string" + } + ] + }, + "next@const": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available" + ] + }, + "next@enum": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available", + "type" + ] + }, + "next@enum.member": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available" + ] + }, + "next@interface": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available", + "*_alias" + ] + }, + "next@interface.method": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available", + "mut", + "error" + ] + }, + "next@interface.method.parameter": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "mut", + "*_type" + ] + }, + "next@package": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available", + "*_package", + "*_imports" + ] + }, + "next@struct": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available", + "*_alias" + ] + }, + "next@struct.field": { + "name": "next", + "description": "Built-in annotation for the next compiler.", + "parameters": [ + "available", + "*_type" + ] + }, + "optional": { + "name": "optional", + "description": "Sets the field as optional.", + "parameters": [ + { + "name": "default", + "description": "Sets the default value for optional fields.", + "type": "string" + } + ] + }, + "required": { + "name": "required", + "description": "Sets the field as required." + } + }, + "annotation_parameters": { + "*_alias": { + "name": ".+_alias", + "description": "Sets the alias name for target languages.", + "type": "string" + }, + "*_imports": { + "name": ".+_imports", + "description": "Sets the import declarations for target languages.", + "type": "string" + }, + "*_package": { + "name": ".+_package", + "description": "Sets the package name for target languages.", + "type": "string", + "validators": [ + { + "name": "LangPackageMustNotBeEmpty", + "expression": "{{ne . ``}}", + "message": "must not be empty" + } + ] + }, + "*_type": { + "name": ".+_type", + "description": "Sets the type name for target languages.", + "type": "string" + }, + "available": { + "name": "available", + "description": "Sets the available expression for the declaration.", + "type": "string" + }, + "error": { + "name": "error", + "description": "Indicates the method returns an error.", + "type": "bool" + }, + "mut": { + "name": "mut", + "description": "Sets object or parameter as mutable.", + "type": "bool" + }, + "type": { + "name": "type", + "description": "Sets the type for enum members.", + "type": "type" + } + }, + "validators": null + }, + "package": { + "annotations": [ + "next@package", + "deprecated" ] - }, - "required": { - "Name": "required", - "Description": "Sets the field as required." - } }, - "AnnotationParameters": { - "*_alias": { - "Name": ".+_alias", - "Description": "Sets the alias name for target languages.", - "Type": "string" - }, - "*_imports": { - "Name": ".+_imports", - "Description": "Sets the import declarations for target languages.", - "Type": "string" - }, - "*_package": { - "Name": ".+_package", - "Description": "Sets the package name for target languages.", - "Type": "string", - "Validators": [ - { - "Name": "LangPackageMustNotBeEmpty", - "Expression": "{{ne . ``}}", - "Message": "must not be empty" - } + "import": {}, + "const": { + "types": [ + "bool", + "int", + "float", + "string" + ], + "annotations": [ + "next@const", + "deprecated" ] - }, - "*_type": { - "Name": ".+_type", - "Description": "Sets the type name for target languages.", - "Type": "string" - }, - "available": { - "Name": "available", - "Description": "Sets the available expression for the declaration.", - "Type": "string" - }, - "error": { - "Name": "error", - "Description": "Indicates the method returns an error.", - "Type": "bool" - }, - "mut": { - "Name": "mut", - "Description": "Sets object or parameter as mutable.", - "Type": "bool" - }, - "type": { - "Name": "type", - "Description": "Sets the type for enum members.", - "Type": "type" - } }, - "Validators": null - }, - "Package": { - "Annotations": [ "next@package", "deprecated" ] - }, - "Import": {}, - "Const": { - "Types": [ "bool", "int", "float", "string" ], - "Annotations": [ "next@const", "deprecated" ] - }, - "Enum": { - "Annotations": [ "next@enum", "deprecated" ], - "Member": { - "Types": [ "int", "float", "string" ], - "Annotations": [ "next@enum.member", "deprecated" ] - } - }, - "Struct": { - "Annotations": [ "next@struct", "deprecated", "message" ], - "Field": { - "Annotations": [ "next@struct.field", "deprecated", "required", "optional" ] - } - }, - "Interface": { - "Annotations": [ "next@interface", "deprecated" ], - "Method": { - "Annotations": [ "next@interface.method", "deprecated" ], - "Parameter": { - "Annotations": [ "next@interface.method.parameter", "deprecated" ] - } + "enum": { + "annotations": [ + "next@enum", + "deprecated" + ], + "member": { + "types": [ + "int", + "float", + "string" + ], + "annotations": [ + "next@enum.member", + "deprecated" + ] + } + }, + "struct": { + "annotations": [ + "next@struct", + "deprecated", + "message" + ], + "field": { + "annotations": [ + "next@struct.field", + "deprecated", + "required", + "optional" + ] + } + }, + "interface": { + "annotations": [ + "next@interface", + "deprecated" + ], + "method": { + "annotations": [ + "next@interface.method", + "deprecated" + ], + "parameter": { + "annotations": [ + "next@interface.method.parameter", + "deprecated" + ] + } + } } - } -} \ No newline at end of file +} diff --git a/website/example/grammar.yaml b/website/example/grammar.yaml new file mode 100644 index 0000000..669dfd4 --- /dev/null +++ b/website/example/grammar.yaml @@ -0,0 +1,213 @@ +const: + annotations: + - next@const + - deprecated + disabled: false + types: + - bool + - int + - float + - string + validators: null +context: + annotation_parameters: + '*_alias': + description: Sets the alias name for target languages. + name: .+_alias + required: false + type: string + validators: null + '*_imports': + description: Sets the import declarations for target languages. + name: .+_imports + required: false + type: string + validators: null + '*_package': + description: Sets the package name for target languages. + name: .+_package + required: false + type: string + validators: + - expression: '{{ne . ``}}' + message: must not be empty + name: LangPackageMustNotBeEmpty + '*_type': + description: Sets the type name for target languages. + name: .+_type + required: false + type: string + validators: null + available: + description: Sets the available expression for the declaration. + name: available + required: false + type: string + validators: null + error: + description: Indicates the method returns an error. + name: error + required: false + type: bool + validators: null + mut: + description: Sets object or parameter as mutable. + name: mut + required: false + type: bool + validators: null + type: + description: Sets the type for enum members. + name: type + required: false + type: type + validators: null + annotations: + message: + description: Sets the declaration as a message. + name: message + parameters: + - description: Sets the message type id + name: type + required: false + type: int + validators: + - expression: '{{gt . 0}}' + message: must be positive + name: MessageTypeMustBePositive + - description: Sets the message as a request. + name: req + required: false + type: bool + validators: null + deprecated: + description: Sets the declaration as deprecated. + name: deprecated + parameters: + - description: Sets the deprecation message. + name: message + required: false + type: string + validators: null + next@const: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + next@enum: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + - type + next@enum.member: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + next@interface: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + - '*_alias' + next@interface.method: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + - mut + - error + next@interface.method.parameter: + description: Built-in annotation for the next compiler. + name: next + parameters: + - mut + - '*_type' + next@package: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + - '*_package' + - '*_imports' + next@struct: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + - '*_alias' + next@struct.field: + description: Built-in annotation for the next compiler. + name: next + parameters: + - available + - '*_type' + optional: + description: Sets the field as optional. + name: optional + parameters: + - description: Sets the default value for optional fields. + name: default + required: false + type: string + validators: null + required: + description: Sets the field as required. + name: required + parameters: null + validators: null +enum: + annotations: + - next@enum + - deprecated + disabled: false + member: + annotations: + - next@enum.member + - deprecated + types: + - int + - float + - string + validators: null + value_required: false + zero_required: false + validators: null +import: + disabled: false +interface: + annotations: + - next@interface + - deprecated + disabled: false + method: + annotations: + - next@interface.method + - deprecated + parameter: + annotations: + - next@interface.method.parameter + - deprecated + validators: null + validators: null + validators: null +package: + annotations: + - next@package + - deprecated + validators: null +struct: + annotations: + - message + - next@struct + - deprecated + disabled: false + field: + annotations: + - next@struct.field + - deprecated + - required + - optional + validators: null + validators: null