package parser import ( "errors" "regexp" "strconv" "strings" "github.com/hellerve/argos/ast" ) func withoutEmpty(input []string) []string { var r []string for _, str := range input { if str != "" { r = append(r, str) } } return r } var comments = regexp.MustCompile(";.*") var whitespace = regexp.MustCompile("\\s") func explode(s string, start string, end string) string { return strings.Replace(strings.Replace(s, start, " "+start+" ", -1), end, " "+end+" ", -1) } func tokenize(input string) []string { withoutComments := string(comments.ReplaceAll([]byte(input), []byte(""))) explodedParens := explode(withoutComments, "(", ")") explodedBrackets := explode(explodedParens, "[", "]") // TODO this consumes multiple spaces return withoutEmpty(whitespace.Split(explodedBrackets, -1)) } func withEscape(input string) rune { switch input[1:] { case "\\a": return '\a' case "\\b": return '\b' case "newline": return '\n' case "tab": return '\t' case "\\f": return '\f' case "\\r": return '\r' case "\\v": return '\v' case "\\\\": return '\\' case "\\'": return '\'' case "\\\"": return '"' } return []rune(input)[1] } func parseValue(input []string) (*ast.AST, error, []string) { var res ast.AST f, err := strconv.ParseFloat(input[0], 64) if err == nil { res = ast.AST{ast.Num, f} return &res, nil, input[1:] } if input[0][0] == '#' { res := ast.AST{ast.Char, withEscape(input[0])} return &res, nil, input[1:] } if input[0][0] == '"' { start := input[0] agg := []string{start} input = input[1:] if start[len(start)-1] != '"' { for { if len(input) == 0 { return nil, errors.New("Unmatched \""), input } token := input[0] input = input[1:] agg = append(agg, token) if token[len(token)-1] == '"' { break } } } joined := strings.Join(agg, " ") res = ast.AST{ast.String, joined[1 : len(joined)-1]} return &res, nil, input } if input[0][0] == '~' { fn := ast.AST{ast.Symbol, input[0][1:]} complement := ast.AST{ast.Symbol, "complement"} res = ast.AST{ast.List, []*ast.AST{&complement, &fn}} return &res, nil, input[1:] } if strings.Contains(input[0], ":") { comp := ast.AST{ast.Symbol, "compose"} fns := []*ast.AST{&comp} for _, fn := range strings.Split(input[0], ":") { astfn := ast.AST{ast.Symbol, fn} fns = append(fns, &astfn) } res = ast.AST{ast.List, fns} return &res, nil, input[1:] } res = ast.AST{ast.Symbol, input[0]} return &res, nil, input[1:] } func makeFn(bodyStatements []*ast.AST) ast.AST { body := ast.AST{ast.List, bodyStatements} underscore := ast.AST{ast.Symbol, "_"} args := ast.AST{ast.List, []*ast.AST{&underscore}} fn := ast.AST{ast.Symbol, "fn"} return ast.AST{ast.List, []*ast.AST{&fn, &args, &body}} } func parseToken(input []string) (*ast.AST, error, []string) { if len(input) == 0 { return nil, errors.New("Unmatched '(' or '['"), input } if input[0][0] == '\'' { if input[0] == "'" { input = input[1:] } else { input = append([]string{input[0][1:]}, input[1:]...) } tmp, err, input := parseToken(input) if err != nil { return nil, err, input } quote := ast.AST{ast.Symbol, "quote"} res := ast.AST{ast.List, []*ast.AST{"e, tmp}} return &res, nil, input } if input[0] == "`" { input = input[1:] tmp, err, input := parseToken(input) if err != nil { return nil, err, input } quote := ast.AST{ast.Symbol, "quasiquote"} res := ast.AST{ast.List, []*ast.AST{"e, tmp}} return &res, nil, input } if input[0][0] == '@' { if input[0] == "@" { input = input[1:] } else { input = append([]string{input[0][1:]}, input[1:]...) } tmp, err, input := parseToken(input) if err != nil { return nil, err, input } unquote := ast.AST{ast.Symbol, "unqote"} res := ast.AST{ast.List, []*ast.AST{&unquote, tmp}} return &res, nil, input } switch input[0] { case "(": { var l []*ast.AST input = input[1:] for input[0] != ")" { elem, err, newInput := parseToken(input) if err != nil { return nil, err, input } if len(newInput) == 0 { return nil, errors.New("Unmatched '('"), input } l = append(l, elem) input = newInput } res := ast.AST{ast.List, l} return &res, nil, input[1:] } case ")": { return nil, errors.New("Unmatched ')'"), input } case "[": { var l []*ast.AST input = input[1:] for input[0] != "]" { elem, err, newInput := parseToken(input) if err != nil { return nil, err, input } if len(newInput) == 0 { return nil, errors.New("Unmatched '['"), input } l = append(l, elem) input = newInput } res := makeFn(l) return &res, nil, input[1:] } case "]": { return nil, errors.New("Unmatched ']'"), input } } return parseValue(input) } func Parse(input string) (*ast.AST, error, []string) { return parseToken(tokenize(input)) } func ParseAll(input string) ([]*ast.AST, error) { tokens := tokenize(input) var res []*ast.AST for { if len(tokens) == 0 { break } parsed, err, unconsumed := parseToken(tokens) if err != nil { return res, err } res = append(res, parsed) tokens = unconsumed } return res, nil }