diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go index 1ea2978..9900c5e 100644 --- a/cmd/lambda/lambda.go +++ b/cmd/lambda/lambda.go @@ -7,35 +7,37 @@ import ( "time" "git.maximhutz.com/max/lambda/internal/cli" + "git.maximhutz.com/max/lambda/internal/config" "git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/parser" "git.maximhutz.com/max/lambda/pkg/tokenizer" ) +// Run main application. func main() { - - options, err := cli.ParseOptions(os.Args[1:]) + options, err := config.FromArgs() cli.HandleError(err) - logger := cli.GetLogger(*options) + logger := options.GetLogger() logger.Info("Using program arguments.", "args", os.Args) logger.Info("Parsed CLI options.", "options", options) - if options.Input == "-" { - options.Input, err = cli.ReadInput() - cli.HandleError(err) - } + input, err := options.Source.Pull() + cli.HandleError(err) - tokens, fails := tokenizer.GetTokens([]rune(options.Input)) + // Parse tokens. + tokens, fails := tokenizer.GetTokens([]rune(input)) if len(fails) > 0 { cli.HandleError(errors.Join(fails...)) } logger.Info("Parsed tokens.", "tokens", tokens) + // Turn tokens into syntax tree. expression, err := parser.GetTree(tokens) cli.HandleError(err) logger.Info("Parsed syntax tree.", "tree", lambda.Stringify(expression)) + // Reduce expression. start := time.Now() if options.Explanation { diff --git a/internal/cli/arguments.go b/internal/cli/arguments.go index 56ed199..5249317 100644 --- a/internal/cli/arguments.go +++ b/internal/cli/arguments.go @@ -5,18 +5,24 @@ import ( "fmt" ) +// Arguments given to program. type CLIOptions struct { - Input string - Verbose bool + // The source code given to the program. + Input string + // Whether or not to print debug logs. + Verbose bool + // Whether or not to print an explanation of the reduction. Explanation bool } -func ParseOptions(args []string) (*CLIOptions, error) { - // Parse flags and arguments. +// Extract the program configuration from the command-line arguments. +func ParseOptions() (*CLIOptions, error) { + // Parse flags. verbose := flag.Bool("v", false, "Verbosity. If set, the program will print logs.") explanation := flag.Bool("x", false, "Explanation. Whether or not to show all reduction steps.") flag.Parse() + // Parse non-flag arguments. if flag.NArg() == 0 { return nil, fmt.Errorf("No input given.") } else if flag.NArg() > 1 { diff --git a/internal/cli/exit.go b/internal/cli/exit.go index 6ede677..32cfc5e 100644 --- a/internal/cli/exit.go +++ b/internal/cli/exit.go @@ -5,6 +5,8 @@ import ( "os" ) +// A helper function to handle errors in the program. If it is given an error, +// the program will exist, and print the error. func HandleError(err error) { if err == nil { return @@ -12,4 +14,4 @@ func HandleError(err error) { fmt.Fprintln(os.Stderr, err) os.Exit(1) -} \ No newline at end of file +} diff --git a/internal/cli/read.go b/internal/cli/read.go deleted file mode 100644 index 93b762c..0000000 --- a/internal/cli/read.go +++ /dev/null @@ -1,15 +0,0 @@ -package cli - -import ( - "io" - "os" -) - -func ReadInput() (string, error) { - data, err := io.ReadAll(os.Stdin) - if err != nil { - return "", err - } - - return string(data), nil -} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..cd74cef --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,11 @@ +package config + +// Arguments given to program. +type Config struct { + // The source code given to the program. + Source Source + // Whether or not to print debug logs. + Verbose bool + // Whether or not to print an explanation of the reduction. + Explanation bool +} diff --git a/internal/cli/logger.go b/internal/config/get_logger.go similarity index 62% rename from internal/cli/logger.go rename to internal/config/get_logger.go index 97437a4..ea7fb5e 100644 --- a/internal/cli/logger.go +++ b/internal/config/get_logger.go @@ -1,13 +1,14 @@ -package cli +package config import ( "log/slog" "os" ) -func GetLogger(o CLIOptions) *slog.Logger { +// Define the correct logger for the program to use. +func (this Config) GetLogger() *slog.Logger { var level slog.Level - if o.Verbose { + if this.Verbose { level = slog.LevelInfo } else { level = slog.LevelError diff --git a/internal/config/parse_from_args.go b/internal/config/parse_from_args.go new file mode 100644 index 0000000..f2bf8dd --- /dev/null +++ b/internal/config/parse_from_args.go @@ -0,0 +1,35 @@ +package config + +import ( + "flag" + "fmt" +) + +// Extract the program configuration from the command-line arguments. +func FromArgs() (*Config, error) { + // Parse flags. + verbose := flag.Bool("v", false, "Verbosity. If set, the program will print logs.") + explanation := flag.Bool("x", false, "Explanation. Whether or not to show all reduction steps.") + flag.Parse() + + // Parse non-flag arguments. + if flag.NArg() == 0 { + return nil, fmt.Errorf("No input given.") + } else if flag.NArg() > 1 { + return nil, fmt.Errorf("More than 1 command-line argument.") + } + + // Parse source type. + var source Source + if flag.Arg(0) == "-" { + source = StdinSource{} + } else { + source = StringSource{data: flag.Arg(0)} + } + + return &Config{ + Source: source, + Verbose: *verbose, + Explanation: *explanation, + }, nil +} diff --git a/internal/config/source.go b/internal/config/source.go new file mode 100644 index 0000000..f4b07b3 --- /dev/null +++ b/internal/config/source.go @@ -0,0 +1,29 @@ +package config + +import ( + "io" + "os" +) + +// Defines the consumption of different types of input sources. +type Source interface { + // Get the data. + Pull() (string, error) +} + +// A source coming from a string. +type StringSource struct{ data string } + +func (this StringSource) Pull() (string, error) { return this.data, nil } + +// A source coming from standard input. +type StdinSource struct{} + +func (this StdinSource) Pull() (string, error) { + data, err := io.ReadAll(os.Stdin) + if err != nil { + return "", err + } + + return string(data), nil +}