Files
alda-mirror/client/parser/ast.go
David Lu cea51273cb Introduce NoteLengthSeconds to distinguish between 's' and 'ms' note lengths
Add NoteLengthSeconds scanner token, ASTNode, and ScoreUpdate.
NoteLengthSeconds is the seconds equivalent of NoteLengthMs.
2023-04-02 22:26:39 -04:00

905 lines
20 KiB
Go

package parser
import (
"bytes"
"fmt"
"strings"
"alda.io/client/json"
"alda.io/client/model"
"alda.io/client/text"
)
// An ASTNodeType is a type of AST node output by the parser.
type ASTNodeType int
const (
AtMarkerNode ASTNodeType = iota
BarlineNode
ChordNode
CramNode
DenominatorNode
DotsNode
DurationNode
EventSequenceNode
FirstRepetitionNode
FlatNode
ImplicitPartNode
LastRepetitionNode
LispListNode
LispNumberNode
LispQuotedFormNode
LispStringNode
LispSymbolNode
MarkerNode
NaturalNode
NoteAccidentalsNode
NoteLengthMsNode
NoteLengthNode
NoteLengthSecondsNode
NoteLetterAndAccidentalsNode
NoteLetterNode
NoteNode
OctaveDownNode
OctaveSetNode
OctaveUpNode
OnRepetitionsNode
PartAliasNode
PartDeclarationNode
PartNameNode
PartNamesNode
PartNode
RepeatNode
RepetitionRangeNode
RepetitionsNode
RestNode
RootNode
SharpNode
TieNode
TimesNode
VariableDefinitionNode
VariableNameNode
VariableReferenceNode
VoiceNode
VoiceGroupEndMarkerNode
VoiceGroupNode
VoiceNumberNode
)
type ASTNode struct {
Type ASTNodeType
Literal interface{}
Children []ASTNode
SourceContext model.AldaSourceContext
}
func (nt ASTNodeType) String() string {
switch nt {
case AtMarkerNode:
return "AtMarkerNode"
case BarlineNode:
return "BarlineNode"
case ChordNode:
return "ChordNode"
case CramNode:
return "CramNode"
case DenominatorNode:
return "DenominatorNode"
case DotsNode:
return "DotsNode"
case DurationNode:
return "DurationNode"
case EventSequenceNode:
return "EventSequenceNode"
case FirstRepetitionNode:
return "FirstRepetitionNode"
case FlatNode:
return "FlatNode"
case ImplicitPartNode:
return "ImplicitPartNode"
case LastRepetitionNode:
return "LastRepetitionNode"
case LispListNode:
return "LispListNode"
case LispNumberNode:
return "LispNumberNode"
case LispQuotedFormNode:
return "LispQuotedFormNode"
case LispStringNode:
return "LispStringNode"
case LispSymbolNode:
return "LispSymbolNode"
case MarkerNode:
return "MarkerNode"
case NaturalNode:
return "NaturalNode"
case NoteAccidentalsNode:
return "NoteAccidentalsNode"
case NoteLengthMsNode:
return "NoteLengthMsNode"
case NoteLengthNode:
return "NoteLengthNode"
case NoteLengthSecondsNode:
return "NoteLengthSecondsNode"
case NoteLetterAndAccidentalsNode:
return "NoteLetterAndAccidentalsNode"
case NoteLetterNode:
return "NoteLetterNode"
case NoteNode:
return "NoteNode"
case OctaveDownNode:
return "OctaveDownNode"
case OctaveSetNode:
return "OctaveSetNode"
case OctaveUpNode:
return "OctaveUpNode"
case OnRepetitionsNode:
return "OnRepetitionsNode"
case PartAliasNode:
return "PartAliasNode"
case PartDeclarationNode:
return "PartDeclarationNode"
case PartNameNode:
return "PartNameNode"
case PartNamesNode:
return "PartNamesNode"
case PartNode:
return "PartNode"
case RepeatNode:
return "RepeatNode"
case RepetitionRangeNode:
return "RepetitionRangeNode"
case RepetitionsNode:
return "RepetitionsNode"
case RestNode:
return "RestNode"
case RootNode:
return "RootNode"
case SharpNode:
return "SharpNode"
case TieNode:
return "TieNode"
case TimesNode:
return "TimesNode"
case VariableDefinitionNode:
return "VariableDefinitionNode"
case VariableNameNode:
return "VariableNameNode"
case VariableReferenceNode:
return "VariableReferenceNode"
case VoiceNode:
return "VoiceNode"
case VoiceGroupEndMarkerNode:
return "VoiceGroupEndMarkerNode"
case VoiceGroupNode:
return "VoiceGroupNode"
case VoiceNumberNode:
return "VoiceNumberNode"
default:
return fmt.Sprintf("%d (String not implemented)", nt)
}
}
// JSON returns a JSON representation of an ASTNode.
func (node ASTNode) JSON() *json.Container {
nodeJSON := json.Object(
"type", node.Type.String(),
)
if len(node.Children) > 0 {
children := json.Array()
for _, child := range node.Children {
children.ArrayAppend(child.JSON())
}
nodeJSON.Set(children, "children")
}
if node.Literal != nil {
literal := node.Literal
switch node.Type {
case NoteLetterNode:
literal = fmt.Sprintf("%c", literal)
}
nodeJSON.Set(literal, "literal")
}
if node.SourceContext.Line > 0 {
nodeJSON.Set(
json.Object(
"line", node.SourceContext.Line,
"column", node.SourceContext.Column,
),
"source-context")
}
return nodeJSON
}
// HumanReadableAST returns a human-readable textual representation of an AST.
// It operates on the JSON output of ASTNode.JSON().
//
// NOTE: It kind of feels like a bad idea that this operates on the JSON output
// instead of the ASTNode structure itself, because JSON() is lossy, but it does
// make sense in the context of Alda REPL client/server interactions (the
// `:score ast` REPL command), where the server is sending the JSON
// representation of the AST over to the client, so the client doesn't have the
// original AST structure in hand. The AST is a really simple structure, and the
// JSON is a faithful representation of it, so I don't think the lossiness in
// the translation will effectively cause any problems.
func HumanReadableAST(ast *json.Container) string {
var buffer bytes.Buffer
var recursivelyWriteNodes func(*json.Container, int)
recursivelyWriteNodes = func(node *json.Container, indentAmount int) {
nodeType := node.Search("type").Data().(string)
maybeSourceContext := ""
sc, ok := node.Search("source-context").Data().(map[string]interface{})
if ok {
maybeSourceContext = fmt.Sprintf(
" [%d:%d]",
int(sc["line"].(float64)),
int(sc["column"].(float64)),
)
}
maybeLiteral := ""
if literal := node.Search("literal").Data(); literal != nil {
maybeLiteral = fmt.Sprintf(": %#v", literal)
}
nodeString := fmt.Sprintf(
"%s%s%s",
nodeType,
maybeSourceContext,
maybeLiteral,
)
buffer.WriteString(
text.Indent(indentAmount, nodeString) + "\n",
)
for _, child := range node.Search("children").Children() {
recursivelyWriteNodes(child, indentAmount+1)
}
}
recursivelyWriteNodes(ast, 0)
return strings.TrimRight(buffer.String(), "\n")
}
func (node ASTNode) expectChildren() error {
if len(node.Children) == 0 {
return fmt.Errorf("%s has no children", node.Type.String())
}
return nil
}
func (node ASTNode) expectNChildren(expectedChildren ...int) error {
actualChildren := len(node.Children)
for _, expected := range expectedChildren {
if actualChildren == expected {
return nil
}
}
return fmt.Errorf(
"expected %s to have %v children, but it has %d",
node.Type.String(),
expectedChildren,
actualChildren,
)
}
func (node ASTNode) expectNodeType(expectedType ASTNodeType) (ASTNode, error) {
if node.Type != expectedType {
return ASTNode{}, fmt.Errorf(
"expected %s node, but got %s node",
expectedType.String(),
node.Type.String(),
)
}
return node, nil
}
func errUnexpectedNodeChild(parentType ASTNodeType, childType ASTNodeType) error {
return fmt.Errorf(
"unexpected %s child: %s",
parentType.String(), childType.String(),
)
}
func duration(node ASTNode) (model.Duration, error) {
duration := model.Duration{}
for _, componentNode := range node.Children {
switch componentNode.Type {
default:
return model.Duration{}, errUnexpectedNodeChild(node.Type, componentNode.Type)
case BarlineNode:
barline := model.Barline{SourceContext: componentNode.SourceContext}
duration.Components = append(duration.Components, barline)
case NoteLengthNode:
if err := componentNode.expectChildren(); err != nil {
return model.Duration{}, err
}
denom, err := componentNode.Children[0].expectNodeType(DenominatorNode)
if err != nil {
return model.Duration{}, err
}
dots := int32(0)
if len(componentNode.Children) > 1 {
dotsNode, err := componentNode.Children[1].expectNodeType(DotsNode)
if err != nil {
return model.Duration{}, err
}
dots = dotsNode.Literal.(int32)
}
noteLength := model.NoteLength{
Denominator: denom.Literal.(float64),
Dots: dots,
}
duration.Components = append(duration.Components, noteLength)
case NoteLengthMsNode:
literal := componentNode.Literal.(float64)
noteLengthMs := model.NoteLengthMs{Quantity: literal}
duration.Components = append(duration.Components, noteLengthMs)
case NoteLengthSecondsNode:
literal := componentNode.Literal.(float64)
noteLengthSeconds := model.NoteLengthSeconds{Quantity: literal}
duration.Components = append(duration.Components, noteLengthSeconds)
}
}
return duration, nil
}
func (node ASTNode) Updates() ([]model.ScoreUpdate, error) {
concatChildUpdates := func(node ASTNode) ([]model.ScoreUpdate, error) {
updates := []model.ScoreUpdate{}
for _, child := range node.Children {
childUpdates, err := child.Updates()
if err != nil {
return nil, err
}
updates = append(updates, childUpdates...)
}
return updates, nil
}
switch node.Type {
case AtMarkerNode:
return []model.ScoreUpdate{
model.AtMarker{
SourceContext: node.SourceContext,
Name: node.Literal.(string),
},
}, nil
case BarlineNode:
return []model.ScoreUpdate{
model.Barline{SourceContext: node.SourceContext},
}, nil
case ChordNode:
if err := node.expectChildren(); err != nil {
return nil, err
}
updates, err := concatChildUpdates(node)
if err != nil {
return nil, err
}
return []model.ScoreUpdate{
model.Chord{
SourceContext: node.SourceContext,
Events: updates,
},
}, nil
case CramNode:
if err := node.expectNChildren(1, 2); err != nil {
return nil, err
}
eventsNode, err := node.Children[0].expectNodeType(EventSequenceNode)
if err != nil {
return nil, err
}
events, err := concatChildUpdates(eventsNode)
if err != nil {
return nil, err
}
cram := model.Cram{
SourceContext: node.SourceContext,
Events: events,
}
if len(node.Children) > 1 {
durationNode, err := node.Children[1].expectNodeType(DurationNode)
if err != nil {
return nil, err
}
dur, err := duration(durationNode)
if err != nil {
return nil, err
}
cram.Duration = dur
}
return []model.ScoreUpdate{cram}, nil
case EventSequenceNode:
updates, err := concatChildUpdates(node)
if err != nil {
return nil, err
}
return []model.ScoreUpdate{
model.EventSequence{
SourceContext: node.SourceContext,
Events: updates,
},
}, nil
case ImplicitPartNode:
if err := node.expectNChildren(1); err != nil {
return nil, err
}
events, err := node.Children[0].expectNodeType(EventSequenceNode)
if err != nil {
return nil, err
}
return concatChildUpdates(events)
case LispListNode:
var lispForm func(ASTNode) (model.LispForm, error)
lispForm = func(node ASTNode) (model.LispForm, error) {
switch node.Type {
case LispListNode:
list := model.LispList{SourceContext: node.SourceContext}
for _, child := range node.Children {
form, err := lispForm(child)
if err != nil {
return nil, err
}
list.Elements = append(list.Elements, form)
}
return list, nil
case LispNumberNode:
return model.LispNumber{
SourceContext: node.SourceContext,
Value: node.Literal.(float64),
}, nil
case LispQuotedFormNode:
if err := node.expectNChildren(1); err != nil {
return nil, err
}
form, err := lispForm(node.Children[0])
if err != nil {
return nil, err
}
return model.LispQuotedForm{
SourceContext: node.SourceContext,
Form: form,
}, nil
case LispStringNode:
return model.LispString{
SourceContext: node.SourceContext,
Value: node.Literal.(string),
}, nil
case LispSymbolNode:
return model.LispSymbol{
SourceContext: node.SourceContext,
Name: node.Literal.(string),
}, nil
}
return nil, fmt.Errorf(
"unexpected %s node inside of Lisp form", node.Type.String(),
)
}
list, err := lispForm(node)
if err != nil {
return nil, err
}
return []model.ScoreUpdate{list.(model.LispList)}, nil
case MarkerNode:
return []model.ScoreUpdate{
model.Marker{
SourceContext: node.SourceContext,
Name: node.Literal.(string),
},
}, nil
case NoteNode:
if err := node.expectChildren(); err != nil {
return nil, err
}
laaNode, err := node.Children[0].expectNodeType(NoteLetterAndAccidentalsNode)
if err != nil {
return nil, err
}
letterNode, err := laaNode.Children[0].expectNodeType(NoteLetterNode)
if err != nil {
return nil, err
}
noteLetter, err := model.NewNoteLetter(letterNode.Literal.(rune))
if err != nil {
return nil, err
}
laa := model.LetterAndAccidentals{NoteLetter: noteLetter}
if len(laaNode.Children) > 1 {
accidentalsNode, err := laaNode.Children[1].expectNodeType(
NoteAccidentalsNode,
)
if err != nil {
return nil, err
}
for _, child := range accidentalsNode.Children {
switch child.Type {
default:
return nil, errUnexpectedNodeChild(accidentalsNode.Type, child.Type)
case FlatNode:
laa.Accidentals = append(laa.Accidentals, model.Flat)
case NaturalNode:
laa.Accidentals = append(laa.Accidentals, model.Natural)
case SharpNode:
laa.Accidentals = append(laa.Accidentals, model.Sharp)
}
}
}
note := model.Note{
SourceContext: node.SourceContext,
Pitch: laa,
}
if len(node.Children) > 1 {
for _, child := range node.Children[1:] {
switch child.Type {
default:
return nil, errUnexpectedNodeChild(node.Type, child.Type)
case DurationNode:
dur, err := duration(child)
if err != nil {
return nil, err
}
note.Duration = dur
case TieNode:
note.Slurred = true
}
}
}
return []model.ScoreUpdate{note}, nil
case OctaveDownNode:
return []model.ScoreUpdate{
model.AttributeUpdate{
SourceContext: node.SourceContext,
PartUpdate: model.OctaveDown{},
},
}, nil
case OctaveSetNode:
return []model.ScoreUpdate{
model.AttributeUpdate{
SourceContext: node.SourceContext,
PartUpdate: model.OctaveSet{OctaveNumber: node.Literal.(int32)},
},
}, nil
case OctaveUpNode:
return []model.ScoreUpdate{
model.AttributeUpdate{
SourceContext: node.SourceContext,
PartUpdate: model.OctaveUp{},
},
}, nil
case OnRepetitionsNode:
if err := node.expectNChildren(2); err != nil {
return nil, err
}
eventNode := node.Children[0]
eventUpdates, err := eventNode.Updates()
if err != nil {
return nil, err
}
var event model.ScoreUpdate
// I don't _think_ `eventUpdates` will ever contain more than one update,
// but just in case...
if len(eventUpdates) == 1 {
event = eventUpdates[0]
} else {
event = model.EventSequence{
SourceContext: eventNode.SourceContext,
Events: eventUpdates,
}
}
repetitions, err := node.Children[1].expectNodeType(RepetitionsNode)
if err != nil {
return nil, err
}
if err := repetitions.expectChildren(); err != nil {
return nil, err
}
repetitionRanges := []model.RepetitionRange{}
for _, rrNode := range repetitions.Children {
if err := rrNode.expectNChildren(2); err != nil {
return nil, err
}
frNode, err := rrNode.Children[0].expectNodeType(FirstRepetitionNode)
if err != nil {
return nil, err
}
lrNode, err := rrNode.Children[1].expectNodeType(LastRepetitionNode)
if err != nil {
return nil, err
}
repetitionRanges = append(repetitionRanges, model.RepetitionRange{
First: frNode.Literal.(int32),
Last: lrNode.Literal.(int32),
})
}
return []model.ScoreUpdate{
model.OnRepetitions{
SourceContext: node.SourceContext,
Event: event,
Repetitions: repetitionRanges,
},
}, nil
case PartNode:
if err := node.expectNChildren(2); err != nil {
return nil, err
}
partDeclNode, err := node.Children[0].expectNodeType(PartDeclarationNode)
if err != nil {
return nil, err
}
partDecl := model.PartDeclaration{SourceContext: node.SourceContext}
partNames, err := partDeclNode.Children[0].expectNodeType(PartNamesNode)
if err != nil {
return nil, err
}
if err := partNames.expectChildren(); err != nil {
return nil, err
}
for _, child := range partNames.Children {
partName, err := child.expectNodeType(PartNameNode)
if err != nil {
return nil, err
}
partDecl.Names = append(partDecl.Names, partName.Literal.(string))
}
if len(partDeclNode.Children) > 1 {
partAlias, err := partDeclNode.Children[1].expectNodeType(PartAliasNode)
if err != nil {
return nil, err
}
partDecl.Alias = partAlias.Literal.(string)
}
events, err := node.Children[1].expectNodeType(EventSequenceNode)
if err != nil {
return nil, err
}
eventUpdates, err := concatChildUpdates(events)
if err != nil {
return nil, err
}
return append([]model.ScoreUpdate{partDecl}, eventUpdates...), nil
case RepeatNode:
if err := node.expectNChildren(2); err != nil {
return nil, err
}
eventNode := node.Children[0]
times, err := node.Children[1].expectNodeType(TimesNode)
if err != nil {
return nil, err
}
eventUpdates, err := eventNode.Updates()
if err != nil {
return nil, err
}
var event model.ScoreUpdate
// I don't _think_ `eventUpdates` will ever contain more than one update,
// but just in case...
if len(eventUpdates) == 1 {
event = eventUpdates[0]
} else {
event = model.EventSequence{
SourceContext: eventNode.SourceContext,
Events: eventUpdates,
}
}
return []model.ScoreUpdate{
model.Repeat{
SourceContext: eventNode.SourceContext,
Event: event,
Times: times.Literal.(int32),
},
}, nil
case RestNode:
rest := model.Rest{SourceContext: node.SourceContext}
switch len(node.Children) {
case 0:
break
case 1:
durationNode, err := node.Children[0].expectNodeType(DurationNode)
if err != nil {
return nil, err
}
dur, err := duration(durationNode)
if err != nil {
return nil, err
}
rest.Duration = dur
default:
return nil, fmt.Errorf("rest node must have either 0 or 1 children")
}
return []model.ScoreUpdate{rest}, nil
case RootNode:
return concatChildUpdates(node)
case VariableDefinitionNode:
if err := node.expectNChildren(2); err != nil {
return nil, err
}
variableName, err := node.Children[0].expectNodeType(VariableNameNode)
if err != nil {
return nil, err
}
definition := model.VariableDefinition{
SourceContext: node.SourceContext,
VariableName: variableName.Literal.(string),
}
eventsNode, err := node.Children[1].expectNodeType(EventSequenceNode)
if err != nil {
return nil, err
}
events, err := concatChildUpdates(eventsNode)
if err != nil {
return nil, err
}
definition.Events = events
return []model.ScoreUpdate{definition}, nil
case VariableReferenceNode:
return []model.ScoreUpdate{
model.VariableReference{
SourceContext: node.SourceContext,
VariableName: node.Literal.(string),
},
}, nil
case VoiceGroupNode:
return concatChildUpdates(node)
case VoiceGroupEndMarkerNode:
return []model.ScoreUpdate{
model.VoiceGroupEndMarker{
SourceContext: node.SourceContext,
},
}, nil
case VoiceNode:
if err := node.expectNChildren(2); err != nil {
return nil, err
}
voiceNumber, err := node.Children[0].expectNodeType(VoiceNumberNode)
if err != nil {
return nil, err
}
voiceEvents, err := node.Children[1].expectNodeType(EventSequenceNode)
if err != nil {
return nil, err
}
voiceEventUpdates, err := concatChildUpdates(voiceEvents)
if err != nil {
return nil, err
}
return append(
[]model.ScoreUpdate{
model.VoiceMarker{
SourceContext: voiceNumber.SourceContext,
VoiceNumber: voiceNumber.Literal.(int32),
},
},
voiceEventUpdates...,
), nil
}
return nil, fmt.Errorf(
"Updates() not implemented for AST node type '%s'",
node.Type,
)
}