Files
alda-mirror/client/cmd/parse.go
2022-02-22 23:06:28 -05:00

224 lines
5.2 KiB
Go

package cmd
import (
"fmt"
"time"
"alda.io/client/color"
"alda.io/client/help"
"alda.io/client/json"
log "alda.io/client/logging"
"alda.io/client/model"
"alda.io/client/parser"
"alda.io/client/system"
"github.com/spf13/cobra"
)
var outputType string
func init() {
parseCmd.Flags().StringVarP(
&file, "file", "f", "", "Read Alda source code from a file",
)
parseCmd.Flags().StringVarP(
&code, "code", "c", "", "Supply Alda source code as a string",
)
parseCmd.Flags().StringVarP(
&outputType, "output", "o", "data", "The desired parse output",
)
}
var parseCmd = &cobra.Command{
Use: "parse",
Short: "Display the result of parsing Alda source code",
Long: fmt.Sprintf(`Display the result of parsing Alda source code
---
%s
---
The -o / --output parameter determines what output is displayed.
Options include:
ast:
The AST that results from parsing the source code, represented as a JSON
object.
ast-human:
The AST that results from parsing the source code, displayed in a more
human-readable way.
events:
A JSON array of objects, each of which represents an "event" parsed from the
source code.
data (default):
A JSON object representing the score that is constructed after parsing the
source code into events and evaluating them in order within the context of a
new score.
---`,
sourceCodeInputOptions("parse", false),
),
RunE: func(_ *cobra.Command, args []string) error {
switch outputType {
case "ast", "ast-human", "events", "data": // OK to proceed
default:
return help.UserFacingErrorf(
`%s is not a supported output type.
Please choose one of:
%s
The AST that results from parsing the source code, represented as a JSON
object.
%s
The AST that results from parsing the source code, displayed in a more
human-readable way.
%s
A JSON array of objects, each of which represents an "event" parsed from the
source code.
%s (default)
A JSON object representing the score that is constructed after parsing the
source code into events and evaluating them in order within the context of a
new score.`,
color.Aurora.BrightYellow(outputType),
color.Aurora.BrightYellow("ast"),
color.Aurora.BrightYellow("ast-human"),
color.Aurora.BrightYellow("events"),
color.Aurora.BrightYellow("data"),
)
}
var ast parser.ASTNode
var scoreUpdates []model.ScoreUpdate
var err error
switch {
case file != "":
ast, err = parser.ParseFile(file)
case code != "":
ast, err = parser.ParseString(code)
default:
ast, err = parseStdin()
}
if err == system.ErrNoInputSupplied {
return userFacingNoInputSuppliedError("parse")
}
// Errors with source context are presented to the user as-is.
//
// TODO: Consider writing better user-facing error messages with suggestions
// for the users to try, etc. The Elm compiler is a good source of
// inspiration.
//
// TODO: If we do the above, we should also consider providing a flag that
// keeps the error messages terse/parseable, the way they are now. That way,
// tooling can be built around the parseable error messages with source
// context, e.g. an editor plugin can easily parse out the line and column
// number and display the error message at the relevant position in the
// file.
switch err.(type) {
case *model.AldaSourceError:
err = &help.UserFacingError{Err: err}
}
if err != nil {
return err
}
if outputType == "ast" {
fmt.Println(ast.JSON().String())
return nil
}
if outputType == "ast-human" {
// HACK: Instead of the code below, we should really just be able to have
// this one line:
//
// fmt.Println(parser.HumanReadableAST(ast.JSON()))
//
// However, for some bizarre reason, it only seems to work if we serialize
// the JSON directly emitted by ast.JSON() as a string, then parse the
// string to get a new object. I guess something about ast.JSON() is doing
// something weird with nested gabs.Containers, or something like that.
jsonObj := ast.JSON()
jsonStr := jsonObj.String()
parsedJsonObj, err := json.ParseJSON([]byte(jsonStr))
if err != nil {
return err
}
fmt.Println(parser.HumanReadableAST(parsedJsonObj))
return nil
}
scoreUpdates, err = ast.Updates()
// Errors with source context are presented to the user as-is.
//
// See TODO notes above.
switch err.(type) {
case *model.AldaSourceError:
err = &help.UserFacingError{Err: err}
}
if err != nil {
return err
}
if outputType == "events" {
updates := json.Array()
for _, update := range scoreUpdates {
updates.ArrayAppend(update.JSON())
}
fmt.Println(updates.String())
return nil
}
score := model.NewScore()
start := time.Now()
err = score.Update(scoreUpdates...)
// Errors with source context are presented to the user as-is.
//
// TODO: See TODO comment above about writing better user-facing error
// messages.
switch err.(type) {
case *model.AldaSourceError:
err = &help.UserFacingError{Err: err}
}
if err != nil {
return err
}
log.Info().
Int("updates", len(scoreUpdates)).
Str("took", time.Since(start).String()).
Msg("Constructed score.")
fmt.Println(score.JSON().String())
return nil
},
}