1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#pragma once
6
7#include <stdint.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <fstream>
13#include <string>
14#include <vector>
15
16std::vector<std::string>& operator+=(std::vector<std::string>& v1,
17                                     const std::vector<std::string>& v2);
18std::vector<std::string> tokenize_string(const std::string& str);
19
20struct FileCtx {
21    const char* file;
22    const char* last_token;
23    int line_start;
24    int line_end;
25    bool verbose;
26
27    FileCtx(const char* file, bool verbose)
28        : file(file), last_token(nullptr),
29          line_start(0), line_end(0),
30          verbose(verbose) {}
31
32    FileCtx(const FileCtx& src, int start)
33        : file(src.file), last_token(src.last_token),
34          line_start(start), line_end(src.line_start),
35          verbose(src.verbose) {}
36
37    void print_error(const char* what, const std::string& extra) const;
38    void print_info(const char* what) const;
39};
40
41class TokenStream {
42public:
43    TokenStream(const std::vector<std::string>& tokens, const FileCtx& fc)
44        : fc_(fc), ix_(0u), tokens_(tokens) {}
45
46    const std::string& curr();
47    const std::string& next();
48    const std::string& peek_next() const;
49    const FileCtx& filectx();
50
51private:
52    FileCtx fc_;
53    size_t ix_;
54    const std::vector<std::string>& tokens_;
55};
56
57// ======================= generic parsing machinery =============================================
58template <typename P>
59using ProcFn = bool (*)(P* parser, TokenStream& ts);
60
61template <typename P>
62struct Dispatch {
63    const char* first_token;
64    const char* last_token;
65    ProcFn<P> fn;
66};
67
68template <typename P>
69bool process_line(P* parser, const Dispatch<P>* table,
70                  const std::vector<std::string>& tokens,
71                  const FileCtx& fc) {
72    static std::vector<std::string> acc;
73    static int start = 0;
74
75    auto& first = acc.empty() ? tokens[0] : acc[0];
76    auto& last = tokens.back();
77
78    start = acc.empty() ? fc.line_start : start;
79
80    size_t ix = 0;
81    while (table[ix].fn) {
82        auto& d = table[ix++];
83        if (first == d.first_token) {
84
85            TokenStream ts(tokens, fc);
86            if (!d.last_token)
87                return d.fn(parser, ts);
88
89            if (last == d.last_token) {
90                if (acc.empty()) {
91                    // single line case.
92                    return d.fn(parser, ts);
93                } else {
94                    // multiline case.
95                    std::vector<std::string> t(std::move(acc));
96                    t += tokens;
97                    TokenStream mts(t, FileCtx(fc, start));
98                    return d.fn(parser, mts);
99                }
100            } else {
101                // more input is needed.
102                acc += tokens;
103                return true;
104            }
105        }
106    }
107
108    if (!acc.empty())
109        fc.print_error("missing terminator", tokens[0]);
110    else
111        fc.print_error("unknown token", tokens[0]);
112    return false;
113}
114
115template <typename P>
116bool run_parser(P* parser, const Dispatch<P>* table, const char* input, bool verbose) {
117    std::ifstream infile;
118    infile.open(input, std::ifstream::in);
119
120    if (!infile.good()) {
121        fprintf(stderr, "error: unable to open %s\n", input);
122        return false;
123    }
124
125    if (verbose)
126        fprintf(stderr, "abigen: processing file %s\n", input);
127
128    bool error = false;
129    FileCtx fc(input, verbose);
130    std::string line;
131
132    while (!infile.eof()) {
133        getline(infile, line);
134        ++fc.line_start;
135        auto tokens = tokenize_string(line);
136        if (tokens.empty())
137            continue;
138
139        if (!process_line(parser, table, tokens, fc)) {
140            error = true;
141            break;
142        }
143    }
144
145    if (error) {
146        fprintf(stderr, "** stopping at line %d. parsing %s failed.\n", fc.line_start, input);
147        return false;
148    }
149
150    return true;
151}
152