package subex import ( "main/walk" ) func parseReplacement(l *RuneReader) (output []TransducerOutput) { // TODO escaping // TODO refactor all the terminator stuff @, #, ~ loop: for { r := l.next() switch r { case eof: panic("Missing closing \"") case '"': break loop case '$': slot := l.next() if slot == eof { panic("Missing slot character") } output = append(output, TransducerReplacementLoad{datum: slot}) case '@': terminal := l.next() if terminal == '(' { output = append(output, TransducerReplacementRune{datum: walk.ArrayBegin}) } else if terminal == ')' { output = append(output, TransducerReplacementRune{datum: walk.ArrayEnd}) } else { panic("Expected ( or ) after @") } case '~': terminal := l.next() if terminal == '(' { output = append(output, TransducerReplacementRune{datum: walk.StartString{}}) } else if terminal == ')' { output = append(output, TransducerReplacementRune{datum: walk.EndString{}}) } else { panic("Expected ( or ) after ~") } case '#': terminal := l.next() if terminal == '(' { output = append(output, TransducerReplacementRune{datum: walk.MapBegin}) } else if terminal == ')' { output = append(output, TransducerReplacementRune{datum: walk.MapEnd}) } else { panic("Expected ( or ) after #") } default: output = append(output, TransducerReplacementRune{datum: r}) } } return output } // Parse the contents of a range subex [] into a map func parseRangeSubex(l *RuneReader) map[walk.Datum]walk.Datum { // TODO escaping parts := make(map[walk.Datum]walk.Datum) var froms []walk.Datum var hasTo bool for { fromsStart := l.next() if fromsStart == ']' { hasTo = false break } else if fromsStart == '=' { hasTo = true break } else if fromsStart == '@' { terminal := l.next() if terminal == '(' { froms = append(froms, walk.ArrayBegin) continue } else if terminal == ')' { froms = append(froms, walk.ArrayEnd) continue } else { panic("Expected ( or ) after @") } } else if fromsStart == '#' { terminal := l.next() if terminal == '(' { froms = append(froms, walk.MapBegin) continue } else if terminal == ')' { froms = append(froms, walk.MapEnd) continue } else { panic("Expected ( or ) after #") } } else if fromsStart == '~' { terminal := l.next() if terminal == '(' { froms = append(froms, walk.StartString{}) continue } else if terminal == ')' { froms = append(froms, walk.EndString{}) continue } else { panic("Expected ( or ) after ~") } } if l.accept("-") { fromsEnd := l.next() if fromsEnd == ']' || fromsEnd == '=' { l.rewind() fromsEnd = fromsStart } for i := fromsStart; i <= fromsEnd; i += 1 { froms = append(froms, i) } } else { froms = append(froms, fromsStart) } } if len(froms) == 0 { panic("Missing from part of range expression") } var tos []walk.Datum if hasTo { for { tosStart := l.next() if tosStart == ']' { break } else if tosStart == '@' { terminal := l.next() if terminal == '(' { tos = append(tos, walk.ArrayBegin) continue } else if terminal == ')' { tos = append(tos, walk.ArrayEnd) continue } else { panic("Expected ( or ) after @") } } else if tosStart == '#' { terminal := l.next() if terminal == '(' { tos = append(tos, walk.MapBegin) continue } else if terminal == ')' { tos = append(tos, walk.MapEnd) continue } else { panic("Expected ( or ) after #") } } else if tosStart == '~' { terminal := l.next() if terminal == '(' { tos = append(tos, walk.StartString{}) continue } else if terminal == ')' { tos = append(tos, walk.EndString{}) continue } else { panic("Expected ( or ) after ~") } } if l.accept("-") { tosEnd := l.next() if tosEnd == ']' { l.rewind() tosEnd = tosStart } for i := tosStart; i <= tosEnd; i += 1 { tos = append(tos, i) } } else { tos = append(tos, tosStart) } } } else { tos = froms } if len(tos) == 0 { panic("Missing to part of range expression") } for i, from := range froms { parts[from] = tos[i % len(tos)] } return parts } func parseSubex(l *RuneReader, minPower int) SubexAST { var lhs SubexAST r := l.next() switch r { case eof: return nil case '(': lhs = parseSubex(l, 0) if !l.accept(")") { panic("Missing matching )") } case '[': rangeParts := parseRangeSubex(l) lhs = SubexASTRange {rangeParts} case ')', '*', '-', '|', '!', '?', ';': l.rewind() return nil case '$': slot := l.next() if slot == eof { panic("Missing slot character") } match := parseSubex(l, 100) if match == nil { panic("Missing regex for store") } lhs = SubexASTStore{ match: match, slot: slot, } case '"': replacement := parseReplacement(l) lhs = SubexASTOutput{replacement} case '.': lhs = SubexASTCopyAny{} case '@': terminal := l.next() if terminal == '(' { lhs = SubexASTCopyRune{datum: walk.ArrayBegin} } else if terminal == ')' { lhs = SubexASTCopyRune{datum: walk.ArrayEnd} } else { panic("Expected ( or ) after @") } case '~': terminal := l.next() if terminal == '(' { lhs = SubexASTCopyRune{datum: walk.StartString{}} } else if terminal == ')' { lhs = SubexASTCopyRune{datum: walk.EndString{}} } else { panic("Expected ( or ) after ~") } case '#': terminal := l.next() if terminal == '(' { lhs = SubexASTCopyRune{datum: walk.MapBegin} } else if terminal == ')' { lhs = SubexASTCopyRune{datum: walk.MapEnd} } else { panic("Expected ( or ) after #") } default: lhs = SubexASTCopyRune{datum: r} } loop: for { if minPower <= 0 { next := parseSubex(l, 1) if next != nil { lhs = SubexASTConcat{lhs, next} continue loop } } r := l.next() switch { case r == '*' && minPower <= 8: lhs = SubexASTMaximise{lhs} case r == '-' && minPower <= 8: lhs = SubexASTMinimise{lhs} case r == '!' && minPower <= 8: lhs = SubexASTTry{lhs} case r == '?' && minPower <= 8: lhs = SubexASTMaybe{lhs} case r == '|' && minPower <= 4: rhs := parseSubex(l, 5) if rhs == nil { panic("Missing subex after |") } lhs = SubexASTOr{lhs, rhs} case r == ';' && minPower <= 2: rhs := parseSubex(l, 3) if rhs == nil { panic("Missing subex after ;") } lhs = SubexASTJoin{ content: lhs, delimiter: rhs, } default: l.rewind() break loop } } return lhs } func Parse(input string) SubexAST { l := RuneReader { input: input, pos: 0, width: 0, } return parseSubex(&l, 0) }