Files
alda-mirror/client/model/variable.go
Dave Yarwood e0d11b1ec2 include source context EVERYWHERE
This provides filename/line/column context for any error I could possibly find.
To achieve this, I made it so that all ScoreUpdates have source context, so when
updating the score results in an error, we can print out the line and column
number corresponding to the error.

It's a bit more complicated than that, because score updates are nestable (e.g.
event sequences can contain cram expressions can contain chords, etc.), which
essentially gives us a stacktrace whenever a score update results in an error. I
punted on giving the user the full stacktrace, in favor of doing something
simpler and just showing the bottom-most error, because that's probably going to
be the line and column number that's the most helpful.
2020-11-14 20:34:04 -05:00

175 lines
4.6 KiB
Go

package model
import (
"fmt"
"alda.io/client/json"
log "alda.io/client/logging"
"github.com/mohae/deepcopy"
)
// GetVariable returns the value of a variable, or an error if the variable in
// undefined.
func (score *Score) GetVariable(name string) ([]ScoreUpdate, error) {
events, hit := score.Variables[name]
if !hit {
return nil, fmt.Errorf("undefined variable: %s", name)
}
return events, nil
}
// SetVariable defines the value of a variable.
func (score *Score) SetVariable(name string, value []ScoreUpdate) {
log.Debug().
Str("name", name).
Interface("value", value).
Msg("Setting variable.")
score.Variables[name] = value
}
// A VariableDefinition stores a sequence of ScoreUpdates, using the provided
// variable name as a lookup key.
type VariableDefinition struct {
SourceContext AldaSourceContext
VariableName string
Events []ScoreUpdate
}
// GetSourceContext implements HasSourceContext.GetSourceContext.
func (vd VariableDefinition) GetSourceContext() AldaSourceContext {
return vd.SourceContext
}
// JSON implements RepresentableAsJSON.JSON.
func (vd VariableDefinition) JSON() *json.Container {
events := json.Array()
for _, event := range vd.Events {
events.ArrayAppend(event.JSON())
}
return json.Object(
"type", "variable-definition",
"value", json.Object("events", events),
)
}
// UpdateScore implements ScoreUpdate.UpdateScore by defining a variable.
func (vd VariableDefinition) UpdateScore(score *Score) error {
eventValues := []ScoreUpdate{}
for _, event := range vd.Events {
eventValue, err := event.VariableValue(score)
if err != nil {
return err
}
eventValues = append(eventValues, eventValue)
}
score.SetVariable(vd.VariableName, eventValues)
return nil
}
// DurationMs implements ScoreUpdate.DurationMs by returning 0, since a
// variable definition is conceptually instantaneous.
func (VariableDefinition) DurationMs(part *Part) float64 {
return 0
}
// VariableValue implements ScoreUpdate.VariableValue by capturing the current
// value of each event in the definition.
func (vd VariableDefinition) VariableValue(score *Score) (ScoreUpdate, error) {
result := deepcopy.Copy(vd).(VariableDefinition)
result.Events = []ScoreUpdate{}
for _, event := range vd.Events {
eventValue, err := event.VariableValue(score)
if err != nil {
return nil, err
}
result.Events = append(result.Events, eventValue)
}
return result, nil
}
// A VariableReference dereferences a stored variable. A variable with the
// provided name is looked up, and assuming that it was previously defined, the
// corresponding sequence of events defined is used to update the score.
type VariableReference struct {
SourceContext AldaSourceContext
VariableName string
}
// GetSourceContext implements HasSourceContext.GetSourceContext.
func (vr VariableReference) GetSourceContext() AldaSourceContext {
return vr.SourceContext
}
// JSON implements RepresentableAsJSON.JSON.
func (vr VariableReference) JSON() *json.Container {
return json.Object(
"type", "variable-reference",
"value", json.Object("name", vr.VariableName),
)
}
// UpdateScore implements ScoreUpdate.UpdateScore by looking up a variable and
// (assuming it was previously defined) using the corresponding sequence of
// events to update the score.
func (vr VariableReference) UpdateScore(score *Score) error {
events, err := score.GetVariable(vr.VariableName)
if err != nil {
return err
}
log.Debug().
Str("name", vr.VariableName).
Interface("events", events).
Msg("Dereferenced variable.")
for _, event := range events {
if err := event.UpdateScore(score); err != nil {
return err
}
}
return nil
}
// DurationMs implements ScoreUpdate.DurationMs by looking up the sequence of
// events corresponding to the given variable name, and returning the sum of
// the durations of the events.
func (vr VariableReference) DurationMs(part *Part) float64 {
events, err := part.score.GetVariable(vr.VariableName)
if err != nil {
// If the variable is undefined, an error will be thrown when we come back
// through and look it up again for UpdateScore. So, we can safely ignore
// the fact that the variable is undefined here and simply return 0.
return 0
}
durationMs := 0.0
for _, event := range events {
durationMs += event.DurationMs(part)
}
return durationMs
}
// VariableValue implements ScoreUpdate.VariableValue by capturing the current
// value of the referenced variable.
func (vr VariableReference) VariableValue(score *Score) (ScoreUpdate, error) {
events, err := score.GetVariable(vr.VariableName)
if err != nil {
return nil, err
}
return EventSequence{Events: events}, nil
}