123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- package text_template_parse
- import "core:fmt"
- import "core:mem"
- import "core:mem/virtual"
- import "core:strconv"
- import "../scan"
- Error :: enum {
- None,
- Unexpected_Token,
- Unexpected_EOF,
- Expected_End,
- Invalid_Node,
- Invalid_Character,
- Invalid_Number,
- Invalid_String,
- Empty_Command,
- Missing_Value,
- Non_Executable_Command,
- Undefined_Variable,
- Unexpected_Operand,
- Invalid_For_Initialization,
- Too_Many_Declarations,
- }
- Tree :: struct {
- general_allocator: mem.Allocator,
- arena: virtual.Growing_Arena,
- name: string,
- tokens: []Token, // general_allocator
- root: ^Node_List,
- input: string,
- offset: uint,
- for_loop_depth: uint,
- vars: [dynamic]string,
- }
- @(require_results)
- errorf :: proc(t: ^Tree, err: Error, format: string, args: ..any) -> Error {
- if err != nil {
- fmt.eprintf(format, ..args)
- fmt.eprintln()
- }
- return err
- }
- @(require_results)
- unexpected_token :: proc(t: ^Tree, token: Token) -> Error {
- return errorf(t, .Unexpected_Token, "unexpected token: %s", token.value)
- }
- peek :: proc(t: ^Tree, n: uint = 0) -> Token {
- if t.offset+n < len(t.tokens) {
- return t.tokens[t.offset+n]
- }
- return Token{.EOF, "", Pos(len(t.input)), 0}
- }
- next :: proc(t: ^Tree) -> (token: Token) {
- if t.offset < len(t.tokens) {
- token = t.tokens[t.offset]
- t.offset += 1
- return
- }
- return Token{.EOF, "", Pos(len(t.input)), 0}
- }
- backup :: proc(t: ^Tree, n: uint = 1) {
- if n > t.offset {
- t.offset = 0
- } else {
- t.offset -= n
- }
- }
- next_non_space :: proc(t: ^Tree) -> (token: Token) {
- for {
- token = next(t)
- if token.kind != .Space {
- break
- }
- }
- return
- }
- peek_non_space :: proc(t: ^Tree, offset: uint = 0) -> (token: Token) {
- i := offset
- for {
- if t.offset+i < len(t.tokens) {
- token = t.tokens[t.offset+i]
- } else {
- token = Token{.EOF, "", Pos(len(t.input)), 0}
- }
- if token.kind != .Space {
- break
- }
- i += 1
- }
- return
- }
- peek_after_non_space :: proc(t: ^Tree) -> (token: Token) {
- return peek_non_space(t, 1)
- }
- expect :: proc(t: ^Tree, expected: Token_Kind, ctx: string) -> (token: Token, err: Error) {
- token = next_non_space(t)
- if token.kind != expected {
- err = errorf(t, .Unexpected_Token, "unexpected token, expected %s, got %s", expected, token.value)
- }
- return
- }
- parse :: proc(input: string, left_delim, right_delim: string, emit_comments: bool = false, general_allocator := context.allocator) -> (t: ^Tree, err: Error) {
- t = new(Tree, general_allocator)
- t.general_allocator = general_allocator
- t.vars.allocator = general_allocator
- t.input = input
- s := scan.init(&scan.Scanner{}, t.name, input, left_delim, right_delim, emit_comments)
- s.tokens.allocator = t.general_allocator
- scan.run(s)
- t.tokens = s.tokens[:] // general_allocator
- context.allocator = virtual.arena_allocator(&t.arena)
- t.root = new_node(Node_List)
- for peek(t).kind != .EOF {
- if peek(t).kind == .Left_Delim && peek_after_non_space(t).kind == .Declare {
- // TODO
- continue
- }
- node := text_or_action(t) or_return
- if node != nil {
- append(&t.root.nodes, node)
- } else {
- break
- }
- }
- return
- }
- destroy_tree :: proc(t: ^Tree) {
- if t != nil {
- virtual.arena_destroy(&t.arena)
- ga := t.general_allocator
- delete(t.tokens, ga)
- delete(t.vars)
- free(t, ga)
- }
- }
- text_or_action :: proc(t: ^Tree) -> (node: ^Node, err: Error) {
- #partial switch token := next_non_space(t); token.kind {
- case .Text:
- n := new_node(Node_Text, token.pos)
- n.text = token.value
- return n, nil
- case .Left_Delim:
- return action(t)
- case .Comment:
- n := new_node(Node_Comment, token.pos)
- n.text = token.value
- return n, nil
- case:
- return nil, unexpected_token(t, token)
- }
- return nil, nil
- }
- parse_list :: proc(t: ^Tree) -> (list: ^Node_List, next: ^Node, err: Error) {
- list = new_node(Node_List, peek_non_space(t).pos)
- for peek_non_space(t).kind != .EOF {
- node := text_or_action(t) or_return
- #partial switch n in node.variant {
- case ^Node_Else:
- next = n
- return
- case ^Node_End:
- next = n
- return
- }
- append(&list.nodes, node)
- }
- err = errorf(t, .Unexpected_EOF, "unexpected EOF")
- return
- }
- parse_control :: proc(t: ^Tree, allow_else_if: bool, ctx: string) -> (pipe: ^Node_Pipeline, list, else_list: ^Node_List, err: Error) {
- pipe = pipeline(t, ctx, .Right_Delim) or_return
- if ctx == "for" {
- t.for_loop_depth += 1
- }
- next_node: ^Node
- list, next_node = parse_list(t) or_return
- if ctx == "for" {
- t.for_loop_depth -= 1
- }
- #partial switch n in next_node.variant {
- case ^Node_End:
- // We are done
- case ^Node_Else:
- if allow_else_if && peek(t).kind == .If {
- // {{if a}}...{{else if b}}...{{end}}
- // is translated into
- // {{if a}}...{{else}}{{if b}}...{{end}}{{end}}
- next(t)
- else_list = new_node(Node_List, next_node.pos)
- append(&else_list.nodes, parse_if(t) or_return)
- break
- }
- else_list, next_node = parse_list(t) or_return
- if _, ok := next_node.variant.(^Node_End); !ok {
- errorf(t, .Expected_End, "expected end") or_return
- }
- }
- return
- }
- // {{if pipeline}} list {{end}}
- // {{if pipeline}} list {{else}} list {{end}}
- // {{if pipeline}} list {{else if pipeline}} list {{end}}
- parse_if :: proc(t: ^Tree) -> (node: ^Node_If, err: Error) {
- pipe, list, else_list := parse_control(t, true, "if") or_return
- node = new_node(Node_If, pipe.pos)
- node.pipe = pipe
- node.list = list
- node.else_list = else_list
- return
- }
- // {{for pipeline}} list {{end}}
- // {{for pipeline}} list {{else}} list {{end}}
- parse_for :: proc(t: ^Tree) -> (node: ^Node_For, err: Error) {
- pipe, list, else_list := parse_control(t, false, "for") or_return
- node = new_node(Node_For, pipe.pos)
- node.pipe = pipe
- node.list = list
- node.else_list = else_list
- return
- }
- // {{with pipeline}} list {{end}}
- // {{with pipeline}} list {{else}} list {{end}}
- parse_with :: proc(t: ^Tree) -> (node: ^Node_With, err: Error) {
- pipe, list, else_list := parse_control(t, false, "with") or_return
- node = new_node(Node_With, pipe.pos)
- node.pipe = pipe
- node.list = list
- node.else_list = else_list
- return
- }
- // {{else}}
- parse_else :: proc(t: ^Tree) -> (node: ^Node_Else, err: Error) {
- p := peek_non_space(t)
- if p.kind == .If {
- node = new_node(Node_Else, p.pos)
- return
- }
- token := expect(t, .Right_Delim, "else") or_return
- node = new_node(Node_Else, token.pos)
- return
- }
- // {{end}}
- parse_end :: proc(t: ^Tree) -> (node: ^Node_End, err: Error) {
- token := expect(t, .Right_Delim, "end") or_return
- node = new_node(Node_End, token.pos)
- return
- }
- action :: proc(t: ^Tree) -> (^Node, Error) {
- // TODO actions
- #partial switch token := next_non_space(t); token.kind {
- case .If: return parse_if(t)
- case .For: return parse_for(t)
- case .With: return parse_with(t)
- case .Else: return parse_else(t)
- case .End: return parse_end(t)
- case .Block:
- return nil, .Invalid_Node
- case .Break:
- return nil, .Invalid_Node
- case .Continue:
- return nil, .Invalid_Node
- case .Include:
- return nil, .Invalid_Node
- }
- backup(t)
- return pipeline(t, "command", .Right_Delim)
- }
- pipeline :: proc(t: ^Tree, ctx: string, end: Token_Kind) -> (pipe: ^Node_Pipeline, err: Error) {
- pipe = new_node(Node_Pipeline, peek_non_space(t).pos)
- decls: for v := peek_non_space(t); v.kind == .Variable; /**/ {
- next_non_space(t)
- token_after_variable := peek(t) // could be space
- next := peek_non_space(t)
- switch {
- case next.kind == .Assign, next.kind == .Declare:
- pipe.is_assign = next.kind == .Assign
- next_non_space(t)
- append(&t.vars, v.value)
- append(&pipe.decl, parse_variable(t, v) or_return)
- case next.kind == .Char && next.value == ",":
- next_non_space(t)
- append(&t.vars, v.value)
- append(&pipe.decl, parse_variable(t, v) or_return)
- if ctx == "for" && len(pipe.decl) < 2 {
- #partial switch peek_non_space(t).kind {
- case .Variable, .Right_Delim, .Right_Paren:
- v = peek_non_space(t)
- continue decls
- }
- errorf(t, .Invalid_For_Initialization, "for can only initialize variables") or_return
- }
- errorf(t, .Too_Many_Declarations, "too many declarations in %s", ctx) or_return
- case token_after_variable.kind == .Space:
- backup(t, 2)
- case:
- backup(t, 1)
- }
- break decls
- }
- for {
- #partial switch tok := next_non_space(t); tok.kind {
- case end:
- if len(pipe.cmds) == 0 {
- errorf(t, .Missing_Value, "missing value for %s", ctx) or_return
- }
- for c, i in pipe.cmds[1:] {
- #partial switch n in c.variant {
- case ^Node_Bool, ^Node_Dot, ^Node_Nil, ^Node_Number, ^Node_String:
- errorf(t, .Non_Executable_Command, "non executable command in pipeline stage for %d", i+2) or_return
- }
- }
- return
- case .Bool, .Char, .Dot, .Field, .Identifier, .Operator, .Number, .Nil, .Raw_String, .String, .Variable, .Left_Paren:
- backup(t)
- append(&pipe.cmds, command(t) or_return)
- case:
- err = unexpected_token(t, tok)
- return
- }
- }
- }
- command :: proc(t: ^Tree) -> (cmd: ^Node_Command, err: Error) {
- cmd = new_node(Node_Command, peek_non_space(t).pos)
- loop: for {
- op := operand(t) or_return
- if op != nil {
- append(&cmd.args, op)
- }
- #partial switch token := next(t); token.kind {
- case .Space:
- continue loop
- case .Right_Delim, .Right_Paren:
- backup(t)
- case .Pipe:
- break loop
- case:
- errorf(t, .Unexpected_Operand, "unexpected operand %s", token.value) or_return
- }
- break loop
- }
- if len(cmd.args) == 0 {
- err = errorf(t, .Empty_Command, "empty command")
- }
- return
- }
- operand :: proc(t: ^Tree) -> (node: ^Node, err: Error) {
- node = term(t) or_return
- if node == nil {
- return
- }
- if p := peek(t); p.kind == .Field {
- chain := new_node(Node_Chain, p.pos)
- chain.node = node
- for peek(t).kind == .Field {
- chain_add(chain, next(t).value)
- }
- #partial switch n in node.variant {
- case ^Node_Field:
- f := new_node(Node_Field, chain.pos)
- resize(&chain.fields, len(chain.fields)+len(n.idents))
- copy(chain.fields[len(n.idents):], chain.fields[:])
- copy(chain.fields[:], n.idents)
- f.idents = chain.fields[:]
- node = f
- case:
- node = chain
- }
- }
- return
- }
- // literal (number, string, nil, boolean)
- // function (identifier)
- // operator (function-like thing)
- // .
- // .field
- // $
- // $
- // '(' pipeline ')'
- term :: proc(t: ^Tree) -> (^Node, Error) {
- #partial switch token := next_non_space(t); token.kind {
- case .Identifier:
- n := new_node(Node_Identifier, token.pos)
- n.ident = token.value
- return n, nil
- case .Operator:
- n := new_node(Node_Operator, token.pos)
- n.value = token.value
- return n, nil
- case .Dot: return new_node(Node_Dot, token.pos), nil
- case .Nil: return new_node(Node_Nil, token.pos), nil
- case .Variable:
- return parse_variable(t, token)
- case .Field:
- f := new_node(Node_Field, token.pos)
- f.idents = make([]string, 1)
- f.idents[0] = token.value[1:]
- return f, nil
- case .Bool:
- b := new_node(Node_Bool, token.pos)
- b.ok = token.value == "true"
- return b, nil
- case .Char, .Number:
- return parse_number(t, token)
- case .String, .Raw_String:
- text, _, ok := strconv.unquote_string(token.value)
- if !ok {
- return nil, errorf(t, .Invalid_String, "invalid string literal: %s", token.value)
- }
- n := new_node(Node_String, token.pos)
- n.quoted = token.value
- n.text = text
- return n, nil
- case .Left_Paren:
- return pipeline(t, "parenthesized pipeline", .Right_Paren)
- }
- backup(t)
- return nil, nil
- }
- parse_number :: proc(t: ^Tree, token: Token) -> (^Node_Number, Error) {
- text := token.value
- n := new_node(Node_Number, token.pos)
- n.text = text
- if token.kind == .Char {
- r, _, tail, ok := strconv.unquote_char(text[:], text[0])
- if !ok || tail != "" {
- return nil, errorf(t, .Invalid_Character, "invalid character literal: %s", text)
- }
- n.i = i64(r)
- n.u = u64(r)
- n.f = f64(r)
- return n, nil
- }
- if u, ok := strconv.parse_u64(text); ok {
- n.u = u
- }
- if i, ok := strconv.parse_i64(text); ok {
- n.i = i
- if i == 0 {
- n.u = 0
- }
- }
- if n.u == nil && n.i == nil {
- if f, ok := strconv.parse_f64(text); ok {
- n.f = f
- }
- }
- if n.u == nil && n.i == nil && n.f == nil {
- return nil, errorf(t, .Invalid_Number, "invalid number syntax: %q", text)
- }
- return n, nil
- }
- parse_variable :: proc(t: ^Tree, token: Token) -> (^Node_Variable, Error) {
- v := new_node(Node_Variable, token.pos)
- v.name = token.value
- for var in t.vars {
- if var == v.name {
- return v, nil
- }
- }
- return nil, errorf(t, .Undefined_Variable, "undefined variable %q", v.name)
- }
|