Explorar o código

Shell: Track line numbers and the positions of some keywords

AnotherTest %!s(int64=4) %!d(string=hai) anos
pai
achega
a10cfee0d4
Modificáronse 4 ficheiros con 251 adicións e 107 borrados
  1. 17 6
      Shell/AST.cpp
  2. 104 40
      Shell/AST.h
  3. 101 59
      Shell/Parser.cpp
  4. 29 2
      Shell/Parser.h

+ 17 - 6
Shell/AST.cpp

@@ -121,7 +121,15 @@ Vector<Command> Node::to_lazy_evaluated_commands(RefPtr<Shell> shell)
 
 void Node::dump(int level) const
 {
-    print_indented(String::format("%s at %d:%d", class_name().characters(), m_position.start_offset, m_position.end_offset), level);
+    print_indented(String::format("%s at %d:%d (from %d.%d to %d.%d)",
+                       class_name().characters(),
+                       m_position.start_offset,
+                       m_position.end_offset,
+                       m_position.start_line.line_number,
+                       m_position.start_line.line_column,
+                       m_position.end_line.line_number,
+                       m_position.end_line.line_column),
+        level);
 }
 
 Node::Node(Position position)
@@ -226,10 +234,11 @@ HitTestResult And::hit_test_position(size_t offset)
     return result;
 }
 
-And::And(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right)
+And::And(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position and_position)
     : Node(move(position))
     , m_left(move(left))
     , m_right(move(right))
+    , m_and_position(and_position)
 {
     if (m_left->is_syntax_error())
         set_is_syntax_error(m_left->syntax_error_node());
@@ -913,7 +922,7 @@ void ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightM
 {
     editor.stylize({ m_position.start_offset, m_position.start_offset + 3 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
     if (m_in_kw_position.has_value())
-        editor.stylize({ m_in_kw_position.value(), m_in_kw_position.value() + 2 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
+        editor.stylize({ m_in_kw_position.value().start_offset, m_in_kw_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
 
     metadata.is_first_in_list = false;
     m_iterated_expression->highlight_in_editor(editor, shell, metadata);
@@ -937,7 +946,7 @@ HitTestResult ForLoop::hit_test_position(size_t offset)
     return m_block->hit_test_position(offset);
 }
 
-ForLoop::ForLoop(Position position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<size_t> in_kw_position)
+ForLoop::ForLoop(Position position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position)
     : Node(move(position))
     , m_variable_name(move(variable_name))
     , m_iterated_expression(move(iterated_expr))
@@ -1541,10 +1550,11 @@ HitTestResult Or::hit_test_position(size_t offset)
     return result;
 }
 
-Or::Or(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right)
+Or::Or(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position or_position)
     : Node(move(position))
     , m_left(move(left))
     , m_right(move(right))
+    , m_or_position(or_position)
 {
     if (m_left->is_syntax_error())
         set_is_syntax_error(m_left->syntax_error_node());
@@ -1796,10 +1806,11 @@ HitTestResult Sequence::hit_test_position(size_t offset)
     return m_right->hit_test_position(offset);
 }
 
-Sequence::Sequence(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right)
+Sequence::Sequence(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position separator_position)
     : Node(move(position))
     , m_left(move(left))
     , m_right(move(right))
+    , m_separator_position(separator_position)
 {
     if (m_left->is_syntax_error())
         set_is_syntax_error(m_left->syntax_error_node());

+ 104 - 40
Shell/AST.h

@@ -47,6 +47,16 @@ struct HighlightMetadata {
 struct Position {
     size_t start_offset { 0 };
     size_t end_offset { 0 };
+    struct Line {
+        size_t line_number { 0 };
+        size_t line_column { 0 };
+
+        bool operator==(const Line& other) const
+        {
+            return line_number == other.line_number && line_column == other.line_column;
+        }
+    } start_line, end_line;
+
     bool contains(size_t offset) const { return start_offset <= offset && offset <= end_offset; }
 };
 
@@ -418,12 +428,57 @@ public:
     virtual void visit(NodeVisitor&) { ASSERT_NOT_REACHED(); }
     virtual void visit(NodeVisitor& visitor) const { const_cast<Node*>(this)->visit(visitor); }
 
+    enum class Kind : u32 {
+        And,
+        ListConcatenate,
+        Background,
+        BarewordLiteral,
+        CastToCommand,
+        CastToList,
+        CloseFdRedirection,
+        CommandLiteral,
+        Comment,
+        DynamicEvaluate,
+        DoubleQuotedString,
+        Fd2FdRedirection,
+        FunctionDeclaration,
+        ForLoop,
+        Glob,
+        Execute,
+        IfCond,
+        Join,
+        MatchExpr,
+        Or,
+        Pipe,
+        ReadRedirection,
+        ReadWriteRedirection,
+        Sequence,
+        Subshell,
+        SimpleVariable,
+        SpecialVariable,
+        Juxtaposition,
+        StringLiteral,
+        StringPartCompose,
+        SyntaxError,
+        Tilde,
+        VariableDeclarations,
+        WriteAppendRedirection,
+        WriteRedirection,
+        __Count,
+    };
+
+    virtual Kind kind() const = 0;
+
 protected:
     Position m_position;
     bool m_is_syntax_error { false };
     RefPtr<const SyntaxError> m_syntax_error_node;
 };
 
+#define NODE(name)                                               \
+    virtual String class_name() const override { return #name; } \
+    virtual Kind kind() const override { return Kind::name; }
+
 class PathRedirectionNode : public Node {
 public:
     PathRedirectionNode(Position, int, NonnullRefPtr<Node>);
@@ -445,22 +500,24 @@ protected:
 
 class And final : public Node {
 public:
-    And(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
+    And(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>, Position and_position);
     virtual ~And();
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
     const NonnullRefPtr<Node>& left() const { return m_left; }
     const NonnullRefPtr<Node>& right() const { return m_right; }
+    const Position& and_position() const { return m_and_position; }
 
 private:
+    NODE(And);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "And"; }
 
     NonnullRefPtr<Node> m_left;
     NonnullRefPtr<Node> m_right;
+    Position m_and_position;
 };
 
 class ListConcatenate final : public Node {
@@ -471,11 +528,11 @@ public:
     const Vector<NonnullRefPtr<Node>> list() const { return m_list; }
 
 private:
+    NODE(ListConcatenate);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "ListConcatenate"; }
     virtual bool is_list() const override { return true; }
     virtual RefPtr<Node> leftmost_trivial_literal() const override;
 
@@ -491,11 +548,11 @@ public:
     const NonnullRefPtr<Node>& command() const { return m_command; }
 
 private:
+    NODE(Background);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Background"; }
 
     NonnullRefPtr<Node> m_command;
 };
@@ -509,10 +566,10 @@ public:
     const String& text() const { return m_text; }
 
 private:
+    NODE(BarewordLiteral);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual String class_name() const override { return "BarewordLiteral"; }
     virtual bool is_bareword() const override { return true; }
     virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; }
 
@@ -528,12 +585,12 @@ public:
     const NonnullRefPtr<Node>& inner() const { return m_inner; }
 
 private:
+    NODE(CastToCommand);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
-    virtual String class_name() const override { return "CastToCommand"; }
     virtual bool is_command() const override { return true; }
     virtual bool is_list() const override { return true; }
     virtual RefPtr<Node> leftmost_trivial_literal() const override;
@@ -550,11 +607,11 @@ public:
     const RefPtr<Node>& inner() const { return m_inner; }
 
 private:
+    NODE(CastToList);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "CastToList"; }
     virtual bool is_list() const override { return true; }
     virtual RefPtr<Node> leftmost_trivial_literal() const override;
 
@@ -570,10 +627,10 @@ public:
     int fd() const { return m_fd; }
 
 private:
+    NODE(CloseFdRedirection);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual String class_name() const override { return "CloseFdRedirection"; }
     virtual bool is_command() const override { return true; }
 
     int m_fd { -1 };
@@ -588,10 +645,10 @@ public:
     const Command& command() const { return m_command; }
 
 private:
+    NODE(CommandLiteral);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override { ASSERT_NOT_REACHED(); }
-    virtual String class_name() const override { return "CommandLiteral"; }
     virtual bool is_command() const override { return true; }
     virtual bool is_list() const override { return true; }
 
@@ -607,10 +664,10 @@ public:
     const String& text() const { return m_text; }
 
 private:
+    NODE(Comment);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual String class_name() const override { return "Comment"; }
 
     String m_text;
 };
@@ -624,11 +681,11 @@ public:
     const NonnullRefPtr<Node>& inner() const { return m_inner; }
 
 private:
+    NODE(DynamicEvaluate);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "DynamicEvaluate"; }
 
     virtual bool is_bareword() const override { return m_inner->is_bareword(); }
     virtual bool is_command() const override { return is_list(); }
@@ -651,11 +708,11 @@ public:
     const RefPtr<Node>& inner() const { return m_inner; }
 
 private:
+    NODE(DoubleQuotedString);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "DoubleQuotedString"; }
 
     RefPtr<Node> m_inner;
 };
@@ -670,10 +727,10 @@ public:
     int dest_fd() const { return m_dest_fd; }
 
 private:
+    NODE(Fd2FdRedirection);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual String class_name() const override { return "Fd2FdRedirection"; }
     virtual bool is_command() const override { return true; }
 
     int m_source_fd { -1 };
@@ -695,12 +752,12 @@ public:
     const RefPtr<Node>& block() const { return m_block; }
 
 private:
+    NODE(FunctionDeclaration);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
-    virtual String class_name() const override { return "FunctionDeclaration"; }
     virtual bool would_execute() const override { return true; }
 
     NameWithPosition m_name;
@@ -710,26 +767,27 @@ private:
 
 class ForLoop final : public Node {
 public:
-    ForLoop(Position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<size_t> in_kw_position = {});
+    ForLoop(Position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position = {});
     virtual ~ForLoop();
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
     const String& variable_name() const { return m_variable_name; }
     const NonnullRefPtr<Node>& iterated_expression() const { return m_iterated_expression; }
     const RefPtr<Node>& block() const { return m_block; }
+    const Optional<Position> in_keyword_position() const { return m_in_kw_position; }
 
 private:
+    NODE(ForLoop);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "ForLoop"; }
     virtual bool would_execute() const override { return true; }
 
     String m_variable_name;
     NonnullRefPtr<AST::Node> m_iterated_expression;
     RefPtr<AST::Node> m_block;
-    Optional<size_t> m_in_kw_position;
+    Optional<Position> m_in_kw_position;
 };
 
 class Glob final : public Node {
@@ -741,10 +799,10 @@ public:
     const String& text() const { return m_text; }
 
 private:
+    NODE(Glob);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual String class_name() const override { return "Glob"; }
     virtual bool is_glob() const override { return true; }
     virtual bool is_list() const override { return true; }
 
@@ -764,12 +822,12 @@ public:
     bool does_capture_stdout() const { return m_capture_stdout; }
 
 private:
+    NODE(Execute);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
-    virtual String class_name() const override { return "Execute"; }
     virtual bool is_execute() const override { return true; }
     virtual bool would_execute() const override { return true; }
 
@@ -789,11 +847,11 @@ public:
     const Optional<Position> else_position() const { return m_else_position; }
 
 private:
+    NODE(IfCond);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "IfCond"; }
     virtual bool would_execute() const override { return true; }
 
     NonnullRefPtr<AST::Node> m_condition;
@@ -813,11 +871,11 @@ public:
     const NonnullRefPtr<Node>& right() const { return m_right; }
 
 private:
+    NODE(Join);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Join"; }
     virtual bool is_command() const override { return true; }
     virtual bool is_list() const override { return true; }
     virtual RefPtr<Node> leftmost_trivial_literal() const override;
@@ -841,13 +899,14 @@ public:
     const NonnullRefPtr<Node>& matched_expr() const { return m_matched_expr; }
     const String& expr_name() const { return m_expr_name; }
     const Vector<MatchEntry>& entries() const { return m_entries; }
+    const Optional<Position>& as_position() const { return m_as_position; }
 
 private:
+    NODE(MatchExpr);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "MatchExpr"; }
     virtual bool would_execute() const override { return true; }
 
     NonnullRefPtr<Node> m_matched_expr;
@@ -858,22 +917,24 @@ private:
 
 class Or final : public Node {
 public:
-    Or(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
+    Or(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>, Position or_position);
     virtual ~Or();
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
     const NonnullRefPtr<Node>& left() const { return m_left; }
     const NonnullRefPtr<Node>& right() const { return m_right; }
+    const Position& or_position() const { return m_or_position; }
 
 private:
+    NODE(Or);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Or"; }
 
     NonnullRefPtr<Node> m_left;
     NonnullRefPtr<Node> m_right;
+    Position m_or_position;
 };
 
 class Pipe final : public Node {
@@ -886,11 +947,11 @@ public:
     const NonnullRefPtr<Node>& right() const { return m_right; }
 
 private:
+    NODE(Pipe);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Pipe"; }
     virtual bool is_command() const override { return true; }
 
     NonnullRefPtr<Node> m_left;
@@ -904,9 +965,9 @@ public:
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
 private:
+    NODE(ReadRedirection);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
-    virtual String class_name() const override { return "ReadRedirection"; }
 };
 
 class ReadWriteRedirection final : public PathRedirectionNode {
@@ -916,31 +977,34 @@ public:
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
 private:
+    NODE(ReadWriteRedirection);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
-    virtual String class_name() const override { return "ReadWriteRedirection"; }
 };
 
 class Sequence final : public Node {
 public:
-    Sequence(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
+    Sequence(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>, Position separator_position);
     virtual ~Sequence();
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
     const NonnullRefPtr<Node>& left() const { return m_left; }
     const NonnullRefPtr<Node>& right() const { return m_right; }
 
+    const Position& separator_position() const { return m_separator_position; }
+
 private:
+    NODE(Sequence);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Sequence"; }
     virtual bool is_list() const override { return true; }
     virtual bool would_execute() const override { return m_left->would_execute() || m_right->would_execute(); }
 
     NonnullRefPtr<Node> m_left;
     NonnullRefPtr<Node> m_right;
+    Position m_separator_position;
 };
 
 class Subshell final : public Node {
@@ -952,11 +1016,11 @@ public:
     const RefPtr<Node>& block() const { return m_block; }
 
 private:
+    NODE(Subshell);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Subshell"; }
     virtual bool would_execute() const override { return true; }
 
     RefPtr<AST::Node> m_block;
@@ -971,12 +1035,12 @@ public:
     const String& name() const { return m_name; }
 
 private:
+    NODE(SimpleVariable);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "SimpleVariable"; }
     virtual bool is_simple_variable() const override { return true; }
 
     String m_name;
@@ -991,12 +1055,12 @@ public:
     char name() const { return m_name; }
 
 private:
+    NODE(SpecialVariable);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "SpecialVariable"; }
 
     char m_name { -1 };
 };
@@ -1011,12 +1075,12 @@ public:
     const NonnullRefPtr<Node>& right() const { return m_right; }
 
 private:
+    NODE(Juxtaposition);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
-    virtual String class_name() const override { return "Juxtaposition"; }
 
     NonnullRefPtr<Node> m_left;
     NonnullRefPtr<Node> m_right;
@@ -1031,10 +1095,10 @@ public:
     const String& text() const { return m_text; }
 
 private:
+    NODE(StringLiteral);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual String class_name() const override { return "StringLiteral"; }
     virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; };
 
     String m_text;
@@ -1050,11 +1114,11 @@ public:
     const NonnullRefPtr<Node>& right() const { return m_right; }
 
 private:
+    NODE(StringPartCompose);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "StringPartCompose"; }
 
     NonnullRefPtr<Node> m_left;
     NonnullRefPtr<Node> m_right;
@@ -1069,11 +1133,11 @@ public:
     const String& error_text() const { return m_syntax_error_text; }
 
 private:
+    NODE(SyntaxError);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override { return { nullptr, nullptr, nullptr }; }
-    virtual String class_name() const override { return "SyntaxError"; }
     virtual bool is_syntax_error() const override { return true; }
     virtual const SyntaxError& syntax_error_node() const override;
 
@@ -1089,12 +1153,12 @@ public:
     String text() const;
 
 private:
+    NODE(Tilde);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "Tilde"; }
     virtual bool is_tilde() const override { return true; }
 
     String m_username;
@@ -1113,11 +1177,11 @@ public:
     const Vector<Variable>& variables() const { return m_variables; }
 
 private:
+    NODE(VariableDeclarations);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual String class_name() const override { return "VariableDeclarations"; }
     virtual bool is_variable_decls() const override { return true; }
 
     Vector<Variable> m_variables;
@@ -1130,9 +1194,9 @@ public:
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
 private:
+    NODE(WriteAppendRedirection);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
-    virtual String class_name() const override { return "WriteAppendRedirection"; }
 };
 
 class WriteRedirection final : public PathRedirectionNode {
@@ -1142,9 +1206,9 @@ public:
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
 private:
+    NODE(WriteRedirection);
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
-    virtual String class_name() const override { return "WriteRedirection"; }
 };
 
 }

+ 101 - 59
Shell/Parser.cpp

@@ -29,6 +29,11 @@
 #include <stdio.h>
 #include <unistd.h>
 
+Parser::SavedOffset Parser::save_offset() const
+{
+    return { m_offset, m_line };
+}
+
 char Parser::peek()
 {
     if (m_offset == m_input.length())
@@ -39,6 +44,8 @@ char Parser::peek()
     auto ch = m_input[m_offset];
     if (ch == '\\' && m_input.length() > m_offset + 1 && m_input[m_offset + 1] == '\n') {
         m_offset += 2;
+        ++m_line.line_number;
+        m_line.line_column = 0;
         return peek();
     }
 
@@ -50,13 +57,14 @@ char Parser::consume()
     auto ch = peek();
     ++m_offset;
 
-    return ch;
-}
+    if (ch == '\n') {
+        ++m_line.line_number;
+        m_line.line_column = 0;
+    } else {
+        ++m_line.line_column;
+    }
 
-void Parser::putback()
-{
-    ASSERT(m_offset > 0);
-    --m_offset;
+    return ch;
 }
 
 bool Parser::expect(char ch)
@@ -67,13 +75,14 @@ bool Parser::expect(char ch)
 bool Parser::expect(const StringView& expected)
 {
     auto offset_at_start = m_offset;
+    auto line_at_start = line();
 
     if (expected.length() + m_offset > m_input.length())
         return false;
 
     for (size_t i = 0; i < expected.length(); ++i) {
         if (peek() != expected[i]) {
-            m_offset = offset_at_start;
+            restore_to(offset_at_start, line_at_start);
             return false;
         }
 
@@ -86,12 +95,12 @@ bool Parser::expect(const StringView& expected)
 template<typename A, typename... Args>
 NonnullRefPtr<A> Parser::create(Args... args)
 {
-    return adopt(*new A(AST::Position { m_rule_start_offsets.last(), m_offset }, args...));
+    return adopt(*new A(AST::Position { m_rule_start_offsets.last(), m_offset, m_rule_start_lines.last(), line() }, args...));
 }
 
 [[nodiscard]] OwnPtr<Parser::ScopedOffset> Parser::push_start()
 {
-    return make<ScopedOffset>(m_rule_start_offsets, m_offset);
+    return make<ScopedOffset>(m_rule_start_offsets, m_rule_start_lines, m_offset, m_line.line_number, m_line.line_column);
 }
 
 static constexpr bool is_whitespace(char c)
@@ -128,13 +137,14 @@ static inline char to_byte(char a, char b)
 RefPtr<AST::Node> Parser::parse()
 {
     m_offset = 0;
+    m_line = { 0, 0 };
 
     auto toplevel = parse_toplevel();
 
     if (m_offset < m_input.length()) {
         // Parsing stopped midway, this is a syntax error.
         auto error_start = push_start();
-        m_offset = m_input.length();
+        consume_while([](auto) { return true; });
         auto syntax_error_node = create<AST::SyntaxError>("Unexpected tokens past the end");
         if (!toplevel)
             toplevel = move(syntax_error_node);
@@ -162,6 +172,8 @@ RefPtr<AST::Node> Parser::parse_sequence()
     auto rule_start = push_start();
     auto var_decls = parse_variable_decls();
 
+    auto pos_before_seps = save_offset();
+
     switch (peek()) {
     case '}':
         return var_decls;
@@ -171,9 +183,15 @@ RefPtr<AST::Node> Parser::parse_sequence()
             break;
 
         consume_while(is_any_of("\n;"));
+
+        auto pos_after_seps = save_offset();
+
         auto rest = parse_sequence();
         if (rest)
-            return create<AST::Sequence>(var_decls.release_nonnull(), rest.release_nonnull());
+            return create<AST::Sequence>(
+                var_decls.release_nonnull(),
+                rest.release_nonnull(),
+                AST::Position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line });
         return var_decls;
     }
     default:
@@ -189,24 +207,38 @@ RefPtr<AST::Node> Parser::parse_sequence()
         return var_decls;
 
     if (var_decls)
-        first = create<AST::Sequence>(var_decls.release_nonnull(), first.release_nonnull());
+        first = create<AST::Sequence>(
+            var_decls.release_nonnull(),
+            first.release_nonnull(),
+            AST::Position { pos_before_seps.offset, pos_before_seps.offset, pos_before_seps.line, pos_before_seps.line });
 
     consume_while(is_whitespace);
 
+    pos_before_seps = save_offset();
     switch (peek()) {
     case ';':
-    case '\n':
+    case '\n': {
         consume_while(is_any_of("\n;"));
+        auto pos_after_seps = save_offset();
+
         if (auto expr = parse_sequence()) {
-            return create<AST::Sequence>(first.release_nonnull(), expr.release_nonnull()); // Sequence
+            return create<AST::Sequence>(
+                first.release_nonnull(),
+                expr.release_nonnull(),
+                AST::Position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line }); // Sequence
         }
         return first;
+    }
     case '&': {
         auto execute_pipe_seq = first->would_execute() ? first.release_nonnull() : static_cast<NonnullRefPtr<AST::Node>>(create<AST::Execute>(first.release_nonnull()));
         consume();
+        auto pos_after_seps = save_offset();
         auto bg = create<AST::Background>(execute_pipe_seq); // Execute Background
         if (auto rest = parse_sequence())
-            return create<AST::Sequence>(move(bg), rest.release_nonnull()); // Sequence Background Sequence
+            return create<AST::Sequence>(
+                move(bg),
+                rest.release_nonnull(),
+                AST::Position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_before_seps.line }); // Sequence Background Sequence
 
         return bg;
     }
@@ -221,13 +253,13 @@ RefPtr<AST::Node> Parser::parse_variable_decls()
 
     consume_while(is_whitespace);
 
-    auto offset_before_name = m_offset;
+    auto pos_before_name = save_offset();
     auto var_name = consume_while(is_word_character);
     if (var_name.is_empty())
         return nullptr;
 
     if (!expect('=')) {
-        m_offset = offset_before_name;
+        restore_to(pos_before_name.offset, pos_before_name.line);
         return nullptr;
     }
 
@@ -236,12 +268,12 @@ RefPtr<AST::Node> Parser::parse_variable_decls()
     auto start = push_start();
     auto expression = parse_expression();
     if (!expression || expression->is_syntax_error()) {
-        m_offset = start->offset;
+        restore_to(*start);
         if (peek() == '(') {
             consume();
             auto command = parse_pipe_sequence();
             if (!command)
-                m_offset = start->offset;
+                restore_to(*start);
             else if (!expect(')'))
                 command->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren"));
             expression = command;
@@ -252,7 +284,7 @@ RefPtr<AST::Node> Parser::parse_variable_decls()
             auto string_start = push_start();
             expression = create<AST::StringLiteral>("");
         } else {
-            m_offset = offset_before_name;
+            restore_to(pos_before_name.offset, pos_before_name.line);
             return nullptr;
         }
     }
@@ -280,14 +312,14 @@ RefPtr<AST::Node> Parser::parse_function_decl()
     auto rule_start = push_start();
 
     auto restore = [&] {
-        m_offset = rule_start->offset;
+        restore_to(*rule_start);
         return nullptr;
     };
 
     consume_while(is_whitespace);
-    auto offset_before_name = m_offset;
+    auto pos_before_name = save_offset();
     auto function_name = consume_while(is_word_character);
-    auto offset_after_name = m_offset;
+    auto pos_after_name = save_offset();
     if (function_name.is_empty())
         return restore();
 
@@ -302,12 +334,13 @@ RefPtr<AST::Node> Parser::parse_function_decl()
             break;
 
         auto name_offset = m_offset;
+        auto start_line = line();
         auto arg_name = consume_while(is_word_character);
         if (arg_name.is_empty()) {
             // FIXME: Should this be a syntax error, or just return?
             return restore();
         }
-        arguments.append({ arg_name, { name_offset, m_offset } });
+        arguments.append({ arg_name, { name_offset, m_offset, start_line, line() } });
     }
 
     consume_while(is_whitespace);
@@ -322,7 +355,7 @@ RefPtr<AST::Node> Parser::parse_function_decl()
             return create<AST::FunctionDeclaration>(
                 AST::FunctionDeclaration::NameWithPosition {
                     move(function_name),
-                    { offset_before_name, offset_after_name } },
+                    { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } },
                 move(arguments),
                 move(syntax_error));
         }
@@ -345,7 +378,7 @@ RefPtr<AST::Node> Parser::parse_function_decl()
             return create<AST::FunctionDeclaration>(
                 AST::FunctionDeclaration::NameWithPosition {
                     move(function_name),
-                    { offset_before_name, offset_after_name } },
+                    { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } },
                 move(arguments),
                 move(body));
         }
@@ -354,7 +387,7 @@ RefPtr<AST::Node> Parser::parse_function_decl()
     return create<AST::FunctionDeclaration>(
         AST::FunctionDeclaration::NameWithPosition {
             move(function_name),
-            { offset_before_name, offset_after_name } },
+            { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } },
         move(arguments),
         move(body));
 }
@@ -368,17 +401,19 @@ RefPtr<AST::Node> Parser::parse_or_logical_sequence()
         return nullptr;
 
     consume_while(is_whitespace);
-    auto saved_offset = m_offset;
-    if (!expect("||")) {
-        m_offset = saved_offset;
+    auto pos_before_or = save_offset();
+    if (!expect("||"))
         return and_sequence;
-    }
+    auto pos_after_or = save_offset();
 
     auto right_and_sequence = parse_and_logical_sequence();
     if (!right_and_sequence)
         right_and_sequence = create<AST::SyntaxError>("Expected an expression after '||'");
 
-    return create<AST::Or>(and_sequence.release_nonnull(), right_and_sequence.release_nonnull());
+    return create<AST::Or>(
+        and_sequence.release_nonnull(),
+        right_and_sequence.release_nonnull(),
+        AST::Position { pos_before_or.offset, pos_after_or.offset, pos_before_or.line, pos_after_or.line });
 }
 
 RefPtr<AST::Node> Parser::parse_and_logical_sequence()
@@ -390,17 +425,19 @@ RefPtr<AST::Node> Parser::parse_and_logical_sequence()
         return nullptr;
 
     consume_while(is_whitespace);
-    auto saved_offset = m_offset;
-    if (!expect("&&")) {
-        m_offset = saved_offset;
+    auto pos_before_and = save_offset();
+    if (!expect("&&"))
         return pipe_sequence;
-    }
+    auto pos_after_end = save_offset();
 
     auto right_and_sequence = parse_and_logical_sequence();
     if (!right_and_sequence)
         right_and_sequence = create<AST::SyntaxError>("Expected an expression after '&&'");
 
-    return create<AST::And>(pipe_sequence.release_nonnull(), right_and_sequence.release_nonnull());
+    return create<AST::And>(
+        pipe_sequence.release_nonnull(),
+        right_and_sequence.release_nonnull(),
+        AST::Position { pos_before_and.offset, pos_after_end.offset, pos_before_and.line, pos_after_end.line });
 }
 
 RefPtr<AST::Node> Parser::parse_pipe_sequence()
@@ -419,13 +456,14 @@ RefPtr<AST::Node> Parser::parse_pipe_sequence()
     if (peek() != '|')
         return left;
 
+    auto before_pipe = save_offset();
     consume();
 
     if (auto pipe_seq = parse_pipe_sequence()) {
         return create<AST::Pipe>(left.release_nonnull(), pipe_seq.release_nonnull()); // Pipe
     }
 
-    putback();
+    restore_to(before_pipe.offset, before_pipe.line);
     return left;
 }
 
@@ -478,28 +516,26 @@ RefPtr<AST::Node> Parser::parse_control_structure()
 RefPtr<AST::Node> Parser::parse_for_loop()
 {
     auto rule_start = push_start();
-    if (!expect("for")) {
-        m_offset = rule_start->offset;
+    if (!expect("for"))
         return nullptr;
-    }
 
     if (consume_while(is_any_of(" \t\n")).is_empty()) {
-        m_offset = rule_start->offset;
+        restore_to(*rule_start);
         return nullptr;
     }
 
     auto variable_name = consume_while(is_word_character);
-    Optional<size_t> in_start_position;
+    Optional<AST::Position> in_start_position;
     if (variable_name.is_empty()) {
         variable_name = "it";
     } else {
         consume_while(is_whitespace);
         auto in_error_start = push_start();
-        in_start_position = in_error_start->offset;
         if (!expect("in")) {
             auto syntax_error = create<AST::SyntaxError>("Expected 'in' after a variable name in a 'for' loop");
             return create<AST::ForLoop>(move(variable_name), move(syntax_error), nullptr); // ForLoop Var Iterated Block
         }
+        in_start_position = AST::Position { in_error_start->offset, m_offset, in_error_start->line, line() };
     }
 
     consume_while(is_whitespace);
@@ -542,13 +578,11 @@ RefPtr<AST::Node> Parser::parse_for_loop()
 RefPtr<AST::Node> Parser::parse_if_expr()
 {
     auto rule_start = push_start();
-    if (!expect("if")) {
-        m_offset = rule_start->offset;
+    if (!expect("if"))
         return nullptr;
-    }
 
     if (consume_while(is_any_of(" \t\n")).is_empty()) {
-        m_offset = rule_start->offset;
+        restore_to(*rule_start);
         return nullptr;
     }
 
@@ -595,7 +629,7 @@ RefPtr<AST::Node> Parser::parse_if_expr()
     {
         auto else_start = push_start();
         if (expect("else"))
-            else_position = AST::Position { else_start->offset, m_offset };
+            else_position = AST::Position { else_start->offset, m_offset, else_start->line, line() };
     }
 
     if (else_position.has_value()) {
@@ -642,7 +676,7 @@ RefPtr<AST::Node> Parser::parse_match_expr()
         return nullptr;
 
     if (consume_while(is_whitespace).is_empty()) {
-        m_offset = rule_start->offset;
+        restore_to(*rule_start);
         return nullptr;
     }
 
@@ -658,8 +692,9 @@ RefPtr<AST::Node> Parser::parse_match_expr()
     String match_name;
     Optional<AST::Position> as_position;
     auto as_start = m_offset;
+    auto as_line = line();
     if (expect("as")) {
-        as_position = AST::Position { as_start, m_offset };
+        as_position = AST::Position { as_start, m_offset, as_line, line() };
 
         if (consume_while(is_any_of(" \t\n")).is_empty()) {
             auto node = create<AST::MatchExpr>(
@@ -730,9 +765,10 @@ AST::MatchEntry Parser::parse_match_entry()
     consume_while(is_any_of(" \t\n"));
 
     auto previous_pipe_start_position = m_offset;
+    auto previous_pipe_start_line = line();
     RefPtr<AST::SyntaxError> error;
     while (expect('|')) {
-        pipe_positions.append({ previous_pipe_start_position, m_offset });
+        pipe_positions.append({ previous_pipe_start_position, m_offset, previous_pipe_start_line, line() });
         consume_while(is_any_of(" \t\n"));
         auto pattern = parse_match_pattern();
         if (!pattern) {
@@ -742,6 +778,9 @@ AST::MatchEntry Parser::parse_match_entry()
         consume_while(is_any_of(" \t\n"));
 
         patterns.append(pattern.release_nonnull());
+
+        previous_pipe_start_line = line();
+        previous_pipe_start_position = m_offset;
     }
 
     consume_while(is_any_of(" \t\n"));
@@ -864,7 +903,7 @@ RefPtr<AST::Node> Parser::parse_redirection()
         return create<AST::ReadWriteRedirection>(pipe_fd, path.release_nonnull()); // Redirection ReadWrite
     }
     default:
-        m_offset = rule_start->offset;
+        restore_to(*rule_start);
         return nullptr;
     }
 }
@@ -930,7 +969,7 @@ RefPtr<AST::Node> Parser::parse_expression()
         consume();
         auto list = parse_list_expression();
         if (!expect(')')) {
-            m_offset = rule_start->offset;
+            restore_to(*rule_start);
             return nullptr;
         }
         return read_concat(create<AST::CastToList>(move(list))); // Cast To List
@@ -1126,7 +1165,7 @@ RefPtr<AST::Node> Parser::parse_variable()
     auto name = consume_while(is_word_character);
 
     if (name.length() == 0) {
-        putback();
+        restore_to(rule_start->offset, rule_start->line);
         return nullptr;
     }
 
@@ -1215,6 +1254,7 @@ RefPtr<AST::Node> Parser::parse_bareword()
         return nullptr;
 
     auto current_end = m_offset;
+    auto current_line = line();
     auto string = builder.to_string();
     if (string.starts_with('~')) {
         String username;
@@ -1231,7 +1271,9 @@ RefPtr<AST::Node> Parser::parse_bareword()
 
         // Synthesize a Tilde Node with the correct positioning information.
         {
-            m_offset -= string.length();
+            restore_to(rule_start->offset, rule_start->line);
+            auto ch = consume();
+            ASSERT(ch == '~');
             tilde = create<AST::Tilde>(move(username));
         }
 
@@ -1240,9 +1282,8 @@ RefPtr<AST::Node> Parser::parse_bareword()
 
         // Synthesize a BarewordLiteral Node with the correct positioning information.
         {
-            m_offset = tilde->position().end_offset;
             auto text_start = push_start();
-            m_offset = current_end;
+            restore_to(current_end, current_line);
             text = create<AST::BarewordLiteral>(move(string));
         }
 
@@ -1267,6 +1308,7 @@ RefPtr<AST::Node> Parser::parse_glob()
 
     char ch = peek();
     if (ch == '*' || ch == '?') {
+        auto saved_offset = save_offset();
         consume();
         StringBuilder textbuilder;
         if (bareword_part) {
@@ -1276,7 +1318,7 @@ RefPtr<AST::Node> Parser::parse_glob()
                 text = bareword->text();
             } else {
                 // FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*'
-                putback();
+                restore_to(saved_offset.offset, saved_offset.line);
                 bareword_part->set_is_syntax_error(*create<AST::SyntaxError>(String::format("Unexpected %s inside a glob", bareword_part->class_name().characters())));
                 return bareword_part;
             }

+ 29 - 2
Shell/Parser.h

@@ -42,6 +42,12 @@ public:
 
     RefPtr<AST::Node> parse();
 
+    struct SavedOffset {
+        size_t offset;
+        AST::Position::Line line;
+    };
+    SavedOffset save_offset() const;
+
 private:
     RefPtr<AST::Node> parse_toplevel();
     RefPtr<AST::Node> parse_sequence();
@@ -76,34 +82,55 @@ private:
     bool at_end() const { return m_input.length() <= m_offset; }
     char peek();
     char consume();
-    void putback();
     bool expect(char);
     bool expect(const StringView&);
 
+    void restore_to(size_t offset, AST::Position::Line line)
+    {
+        m_offset = offset;
+        m_line = move(line);
+    }
+
+    AST::Position::Line line() const { return m_line; }
+
     StringView consume_while(Function<bool(char)>);
 
     struct ScopedOffset {
-        ScopedOffset(Vector<size_t>& offsets, size_t offset)
+        ScopedOffset(Vector<size_t>& offsets, Vector<AST::Position::Line>& lines, size_t offset, size_t lineno, size_t linecol)
             : offsets(offsets)
+            , lines(lines)
             , offset(offset)
+            , line({ lineno, linecol })
         {
             offsets.append(offset);
+            lines.append(line);
         }
         ~ScopedOffset()
         {
             auto last = offsets.take_last();
             ASSERT(last == offset);
+
+            auto last_line = lines.take_last();
+            ASSERT(last_line == line);
         }
 
         Vector<size_t>& offsets;
+        Vector<AST::Position::Line>& lines;
         size_t offset;
+        AST::Position::Line line;
     };
 
+    void restore_to(const ScopedOffset& offset) { restore_to(offset.offset, offset.line); }
+
     OwnPtr<ScopedOffset> push_start();
 
     StringView m_input;
     size_t m_offset { 0 };
+
+    AST::Position::Line m_line { 0, 0 };
+
     Vector<size_t> m_rule_start_offsets;
+    Vector<AST::Position::Line> m_rule_start_lines;
 };
 
 #if 0