Created a new argument parsing system

This commit is contained in:
Sasha Koshka 2024-02-11 03:33:06 -05:00
parent 2bc5eb1f49
commit b4a886b126
3 changed files with 228 additions and 19 deletions

200
cmd/fsplc/cli.go Normal file
View File

@ -0,0 +1,200 @@
package main
import "os"
import "fmt"
import "errors"
import "strings"
import "strconv"
type Cli struct {
// Description describes the application.
Description string
// The syntax description of the command.
Syntax string
// Flag is a set of flags to parse.
Flags []*Flag
// Args is a list of leftover arguments that were not parsed.
Args []string
}
func NewCli (description string, flags ...*Flag) *Cli {
return &Cli {
Description: description,
Flags: flags,
}
}
func (this *Cli) Parse (args []string) error {
// strip out program name
if len(args) > 0 { args = args[1:] }
next := func () string {
arg := args[0]
args = args[1:]
return arg
}
processFlag := func (flag *Flag, allowInput bool) error {
if flag.Validate == nil {
flag.Value = "true"
} else {
if len(args) < 1 || !allowInput {
return errors.New(
flag.String() +
" requires a value")
}
flag.Value = next()
}
return nil
}
// process args one by one
for ; len(args) > 0; next() {
arg := args[0]
switch {
case arg == "--":
// halt parsing
this.Args = append(this.Args, args[1:]...)
return nil
case strings.HasPrefix(arg, "--"):
// long flag
flag, ok := this.LongFlag(arg[2:])
if !ok {
return errors.New("unknown flag: " + arg)
}
err := processFlag(flag, true)
if err != nil { return err }
case strings.HasPrefix(arg, "-") && len(arg) > 1:
// one or more short flags
arg := arg[1:]
for _, part := range arg {
flag, ok := this.ShortFlag(part)
if !ok {
return errors.New (
"unknown flag: -" +
string(part))
}
err := processFlag(flag, len(arg) == 1)
if err != nil { return err }
}
default:
// not a flag
this.Args = append(this.Args, arg)
}
}
return nil
}
func (this *Cli) ParseOrExit (args []string) {
err := this.Parse(args)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n\n", os.Args[0], err)
this.Usage()
os.Exit(2)
}
}
func (this *Cli) ShortFlag (short rune) (*Flag, bool) {
if short == 0 { return nil, false }
for _, flag := range this.Flags {
if flag.Short == short { return flag, true }
}
return nil, false
}
func (this *Cli) LongFlag (long string) (*Flag, bool) {
if long == "" { return nil, false }
for _, flag := range this.Flags {
if flag.Long == long { return flag, true }
}
return nil, false
}
func (this *Cli) Usage () {
fmt.Fprintf(os.Stderr, "Usage: %s ", os.Args[0])
if this.Syntax == "" {
fmt.Fprint(os.Stderr, "[OPTION]...")
} else {
fmt.Fprint(os.Stderr, this.Syntax)
}
fmt.Fprint(os.Stderr, "\n\n")
if this.Description != "" {
fmt.Fprintf(os.Stderr, "%s\n\n", this.Description)
}
longest := 0
for _, flag := range this.Flags {
longest = max(longest, len(flag.Long))
}
format := fmt.Sprint("\t%-", longest + 8, "s%s\n")
for _, flag := range this.Flags {
shortLong := "-" + string(flag.Short)
if flag.Short != 0 && flag.Long != "" {
shortLong += ", "
}
shortLong += "--" + flag.Long
fmt.Fprintf(os.Stderr, format, shortLong, flag.Help)
}
}
type Flag struct {
Short rune // The short form of the flag (-l)
Long string // Long form of the flag (--long-form)
Help string // Help text to display by the flag
// Validate is an optional function that is called when an input is
// passed to the flag. It checks whether or not the input is valid, and
// returns an error if it isn't. If this function is nil, the flag is
// assumed to have no input.
Validate func (string) error
// Value contains the input given to the flag, and is filled out when
// the flags are parsed. If this is a non-input flag (if Validate is
// nil), this will be set to true. If the flag was not specified, Value
// will be unchanged.
Value string
}
func (this *Flag) String () string {
if this.Long == "" {
return fmt.Sprintf("-%c", this.Short)
} else {
return fmt.Sprintf("--%s", this.Long)
}
}
func NewFlag(short rune, long string, help string) *Flag {
return &Flag {
Short: short,
Long: long,
Help: help,
}
}
func NewInputFlag(short rune, long string, help string, defaul string, validate func (string) error) *Flag {
return &Flag {
Short: short,
Long: long,
Help: help,
Value: defaul,
Validate: validate,
}
}
func ValString (value string) error {
return nil
}
func ValInt (value string) error {
_, err := strconv.Atoi(value)
return err
}

View File

@ -14,7 +14,7 @@ import "git.tebibyte.media/sashakoshka/fspl/generator/native"
type Compiler struct {
Output string
Optimization int
Optimization string
}
func (this *Compiler) Compile (inputs []string) error {
@ -73,7 +73,7 @@ func (this *Compiler) CompileModule (module *llvm.Module, filetype string) error
"-",
fmt.Sprintf("-filetype=%s", filetype),
"-o", this.Output,
fmt.Sprintf("-O=%d", this.Optimization),
fmt.Sprintf("-O=%s", this.Optimization),
}
} else {
commandName = "clang"

View File

@ -2,31 +2,40 @@ package main
import "os"
import "fmt"
import "flag"
import "errors"
import "strings"
import "os/exec"
import "path/filepath"
import "git.tebibyte.media/sashakoshka/fspl/llvm"
import "git.tebibyte.media/sashakoshka/fspl/lexer"
import "git.tebibyte.media/sashakoshka/fspl/parser"
import "git.tebibyte.media/sashakoshka/fspl/analyzer"
import ferrors "git.tebibyte.media/sashakoshka/fspl/errors"
import "git.tebibyte.media/sashakoshka/fspl/generator/native"
func main () {
compiler := new(Compiler)
help := NewFlag (
'h', "help",
"Display usage information and exit")
output := NewInputFlag (
'o', "output",
"Output filename", "",
ValString)
optimization := NewInputFlag (
'O', "optimization",
"Optimization level (0-3)", "0",
func (value string) error {
switch value {
case "0", "1", "2", "3": return nil
default: return errors.New("optimization level must be 0-3")
}
})
flag.StringVar(&compiler.Output, "o", "", "Output filename")
flag.IntVar(&compiler.Optimization, "O", 0, "Optimization level (0-3)")
flag.Parse()
if compiler.Optimization < 0 || compiler.Optimization > 3 {
flag.Usage()
cli := NewCli("Compile FSPL source files", help, output, optimization)
cli.Syntax = "[OPTION]... [FILE]..."
cli.ParseOrExit(os.Args)
if help.Value != "" {
cli.Usage()
os.Exit(2)
}
compiler := new(Compiler)
compiler.Output = output.Value
compiler.Optimization = optimization.Value
err := compiler.Compile(flag.Args())
err := compiler.Compile(cli.Args)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], ferrors.Format(err))
os.Exit(1)