foxtail-tools 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/exe/foxtail +12 -0
- data/lib/foxtail/cli/commands/check.rb +60 -0
- data/lib/foxtail/cli/commands/dump.rb +43 -0
- data/lib/foxtail/cli/commands/ids.rb +73 -0
- data/lib/foxtail/cli/commands/tidy.rb +107 -0
- data/lib/foxtail/cli.rb +59 -0
- data/lib/foxtail/syntax/error.rb +8 -0
- data/lib/foxtail/syntax/parser/ast/annotation.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/attribute.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/base_comment.rb +19 -0
- data/lib/foxtail/syntax/parser/ast/base_literal.rb +24 -0
- data/lib/foxtail/syntax/parser/ast/base_node.rb +89 -0
- data/lib/foxtail/syntax/parser/ast/call_arguments.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/comment.rb +13 -0
- data/lib/foxtail/syntax/parser/ast/function_reference.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/group_comment.rb +13 -0
- data/lib/foxtail/syntax/parser/ast/identifier.rb +19 -0
- data/lib/foxtail/syntax/parser/ast/junk.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/message.rb +28 -0
- data/lib/foxtail/syntax/parser/ast/message_reference.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/named_argument.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/number_literal.rb +24 -0
- data/lib/foxtail/syntax/parser/ast/pattern.rb +22 -0
- data/lib/foxtail/syntax/parser/ast/placeable.rb +21 -0
- data/lib/foxtail/syntax/parser/ast/resource.rb +55 -0
- data/lib/foxtail/syntax/parser/ast/resource_comment.rb +13 -0
- data/lib/foxtail/syntax/parser/ast/select_expression.rb +23 -0
- data/lib/foxtail/syntax/parser/ast/span.rb +22 -0
- data/lib/foxtail/syntax/parser/ast/string_literal.rb +45 -0
- data/lib/foxtail/syntax/parser/ast/syntax_node.rb +22 -0
- data/lib/foxtail/syntax/parser/ast/term.rb +28 -0
- data/lib/foxtail/syntax/parser/ast/term_reference.rb +25 -0
- data/lib/foxtail/syntax/parser/ast/text_element.rb +19 -0
- data/lib/foxtail/syntax/parser/ast/variable_reference.rb +21 -0
- data/lib/foxtail/syntax/parser/ast/variant.rb +25 -0
- data/lib/foxtail/syntax/parser/ast.rb +12 -0
- data/lib/foxtail/syntax/parser/parse_error.rb +94 -0
- data/lib/foxtail/syntax/parser/stream.rb +338 -0
- data/lib/foxtail/syntax/parser.rb +797 -0
- data/lib/foxtail/syntax/serializer.rb +242 -0
- data/lib/foxtail/syntax/visitor.rb +61 -0
- data/lib/foxtail/syntax.rb +12 -0
- data/lib/foxtail/tools/error.rb +8 -0
- data/lib/foxtail/tools/version.rb +9 -0
- data/lib/foxtail-tools.rb +22 -0
- metadata +141 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents function calls with optional arguments
|
|
8
|
+
class FunctionReference < SyntaxNode
|
|
9
|
+
attr_accessor :id
|
|
10
|
+
attr_accessor :arguments
|
|
11
|
+
|
|
12
|
+
def initialize(id, arguments=nil)
|
|
13
|
+
super()
|
|
14
|
+
@id = id
|
|
15
|
+
@arguments = arguments
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def children = [id, arguments].compact
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents identifiers used for messages, terms, attributes, and function names
|
|
8
|
+
class Identifier < SyntaxNode
|
|
9
|
+
attr_accessor :name
|
|
10
|
+
|
|
11
|
+
def initialize(name)
|
|
12
|
+
super()
|
|
13
|
+
@name = name
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents unparseable content with associated error annotations
|
|
8
|
+
class Junk < SyntaxNode
|
|
9
|
+
attr_accessor :content
|
|
10
|
+
attr_accessor :annotations
|
|
11
|
+
|
|
12
|
+
def initialize(content, annotations=[])
|
|
13
|
+
super()
|
|
14
|
+
@content = content
|
|
15
|
+
@annotations = annotations
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def children = annotations
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents a Fluent message with an identifier, optional value pattern,
|
|
8
|
+
# attributes, and an optional comment
|
|
9
|
+
class Message < SyntaxNode
|
|
10
|
+
attr_accessor :id
|
|
11
|
+
attr_accessor :value
|
|
12
|
+
attr_accessor :attributes
|
|
13
|
+
attr_accessor :comment
|
|
14
|
+
|
|
15
|
+
def initialize(id, value=nil, attributes=[], comment=nil)
|
|
16
|
+
super()
|
|
17
|
+
@id = id
|
|
18
|
+
@value = value
|
|
19
|
+
@attributes = attributes
|
|
20
|
+
@comment = comment
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def children = [id, value, *attributes, comment].compact
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents references to messages with optional attribute access
|
|
8
|
+
class MessageReference < SyntaxNode
|
|
9
|
+
attr_accessor :id
|
|
10
|
+
attr_accessor :attribute
|
|
11
|
+
|
|
12
|
+
def initialize(id, attribute=nil)
|
|
13
|
+
super()
|
|
14
|
+
@id = id
|
|
15
|
+
@attribute = attribute
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def children = [id, attribute].compact
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents named arguments in function calls (e.g., arg: value)
|
|
8
|
+
class NamedArgument < SyntaxNode
|
|
9
|
+
attr_accessor :name
|
|
10
|
+
attr_accessor :value
|
|
11
|
+
|
|
12
|
+
def initialize(name, value)
|
|
13
|
+
super()
|
|
14
|
+
@name = name
|
|
15
|
+
@value = value
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def children = [name, value]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents numeric literals (integers and floats)
|
|
8
|
+
class NumberLiteral < BaseLiteral
|
|
9
|
+
# Parse the number literal value and return as a Hash
|
|
10
|
+
# @return [Hash] Hash containing the parsed numeric value
|
|
11
|
+
def parse
|
|
12
|
+
value_str = @value
|
|
13
|
+
|
|
14
|
+
if value_str.include?(".")
|
|
15
|
+
{value: Float(value_str)}
|
|
16
|
+
else
|
|
17
|
+
{value: Integer(value_str, 10)}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents a message or term value pattern consisting of text elements
|
|
8
|
+
# and placeables (expressions within braces)
|
|
9
|
+
class Pattern < SyntaxNode
|
|
10
|
+
attr_accessor :elements
|
|
11
|
+
|
|
12
|
+
def initialize(elements)
|
|
13
|
+
super()
|
|
14
|
+
@elements = elements
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def children = elements
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents expressions within braces {} in a pattern that are evaluated at runtime
|
|
8
|
+
class Placeable < SyntaxNode
|
|
9
|
+
attr_accessor :expression
|
|
10
|
+
|
|
11
|
+
def initialize(expression)
|
|
12
|
+
super()
|
|
13
|
+
@expression = expression
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def children = [expression]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents a Fluent resource containing messages, terms, and comments
|
|
8
|
+
# This is the root node of a parsed Fluent file
|
|
9
|
+
class Resource < SyntaxNode
|
|
10
|
+
attr_accessor :body
|
|
11
|
+
|
|
12
|
+
def initialize(body=[])
|
|
13
|
+
super()
|
|
14
|
+
@body = body
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def children = body
|
|
18
|
+
|
|
19
|
+
# Iterate over Message entries only
|
|
20
|
+
# @yield [Message] each message entry
|
|
21
|
+
# @return [Enumerator] if no block given
|
|
22
|
+
# @return [self] if block given
|
|
23
|
+
def each_message(&block)
|
|
24
|
+
return enum_for(__method__) unless block
|
|
25
|
+
|
|
26
|
+
body.each {|entry| yield(entry) if entry.is_a?(Message) }
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Iterate over Term entries only
|
|
31
|
+
# @yield [Term] each term entry
|
|
32
|
+
# @return [Enumerator] if no block given
|
|
33
|
+
# @return [self] if block given
|
|
34
|
+
def each_term(&block)
|
|
35
|
+
return enum_for(__method__) unless block
|
|
36
|
+
|
|
37
|
+
body.each {|entry| yield(entry) if entry.is_a?(Term) }
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Iterate over Message and Term entries (excludes comments and junk)
|
|
42
|
+
# @yield [Message, Term] each message or term entry
|
|
43
|
+
# @return [Enumerator] if no block given
|
|
44
|
+
# @return [self] if block given
|
|
45
|
+
def each_entry(&block)
|
|
46
|
+
return enum_for(__method__) unless block
|
|
47
|
+
|
|
48
|
+
body.each {|entry| yield(entry) if entry.is_a?(Message) || entry.is_a?(Term) }
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents select expressions for conditional message variants
|
|
8
|
+
class SelectExpression < SyntaxNode
|
|
9
|
+
attr_accessor :selector
|
|
10
|
+
attr_accessor :variants
|
|
11
|
+
|
|
12
|
+
def initialize(selector, variants)
|
|
13
|
+
super()
|
|
14
|
+
@selector = selector
|
|
15
|
+
@variants = variants
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def children = [selector, *variants]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents a source code span with start and end positions
|
|
8
|
+
# Used to track the location of AST nodes in the original source text
|
|
9
|
+
class Span < BaseNode
|
|
10
|
+
attr_accessor :start
|
|
11
|
+
attr_accessor :end
|
|
12
|
+
|
|
13
|
+
def initialize(start_pos, end_pos)
|
|
14
|
+
super()
|
|
15
|
+
@start = start_pos
|
|
16
|
+
@end = end_pos
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents quoted string literals with escape sequence processing
|
|
8
|
+
class StringLiteral < BaseLiteral
|
|
9
|
+
# Parse the string literal value, processing escape sequences
|
|
10
|
+
# Handles backslash escapes, Unicode escapes (uHHHH, UHHHHHH), and validates Unicode scalar values
|
|
11
|
+
# @return [Hash] Hash containing the parsed string value
|
|
12
|
+
def parse
|
|
13
|
+
# Backslash backslash, backslash double quote, uHHHH, UHHHHHH.
|
|
14
|
+
known_escapes = /(?:\\\\|\\"|\\u(\h{4})|\\U(\h{6}))/
|
|
15
|
+
|
|
16
|
+
escaped_value = @value.gsub(known_escapes) {|match|
|
|
17
|
+
codepoint4 = $1
|
|
18
|
+
codepoint6 = $2
|
|
19
|
+
|
|
20
|
+
case match
|
|
21
|
+
when "\\\\"
|
|
22
|
+
"\\"
|
|
23
|
+
when '\\"'
|
|
24
|
+
'"'
|
|
25
|
+
else
|
|
26
|
+
codepoint = (codepoint4 || codepoint6).to_i(16)
|
|
27
|
+
if codepoint <= 0xd7ff || 0xe000 <= codepoint
|
|
28
|
+
# It's a Unicode scalar value.
|
|
29
|
+
codepoint.chr(Encoding::UTF_8)
|
|
30
|
+
else
|
|
31
|
+
# Escape sequences representing surrogate code points are
|
|
32
|
+
# well-formed but invalid in Fluent. Replace them with U+FFFD
|
|
33
|
+
# REPLACEMENT CHARACTER.
|
|
34
|
+
"\uFFFD"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
{value: escaped_value}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Base class for AST nodes that can have span information
|
|
8
|
+
# Extends BaseNode with source position tracking capabilities
|
|
9
|
+
class SyntaxNode < BaseNode
|
|
10
|
+
attr_accessor :span
|
|
11
|
+
|
|
12
|
+
# Add span information to this syntax node
|
|
13
|
+
# @param start_pos [Integer] Starting position in the source
|
|
14
|
+
# @param end_pos [Integer] Ending position in the source
|
|
15
|
+
def add_span(start_pos, end_pos)
|
|
16
|
+
@span = Span.new(start_pos, end_pos)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents a Fluent term with an identifier, value pattern,
|
|
8
|
+
# optional attributes, and an optional comment
|
|
9
|
+
class Term < SyntaxNode
|
|
10
|
+
attr_accessor :id
|
|
11
|
+
attr_accessor :value
|
|
12
|
+
attr_accessor :attributes
|
|
13
|
+
attr_accessor :comment
|
|
14
|
+
|
|
15
|
+
def initialize(id, value, attributes=[], comment=nil)
|
|
16
|
+
super()
|
|
17
|
+
@id = id
|
|
18
|
+
@value = value
|
|
19
|
+
@attributes = attributes
|
|
20
|
+
@comment = comment
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def children = [id, value, *attributes, comment].compact
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents references to terms with optional attribute access and arguments
|
|
8
|
+
class TermReference < SyntaxNode
|
|
9
|
+
attr_accessor :id
|
|
10
|
+
attr_accessor :attribute
|
|
11
|
+
attr_accessor :arguments
|
|
12
|
+
|
|
13
|
+
def initialize(id, attribute=nil, arguments=nil)
|
|
14
|
+
super()
|
|
15
|
+
@id = id
|
|
16
|
+
@attribute = attribute
|
|
17
|
+
@arguments = arguments
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def children = [id, attribute, arguments].compact
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents plain text content within a pattern
|
|
8
|
+
class TextElement < SyntaxNode
|
|
9
|
+
attr_accessor :value
|
|
10
|
+
|
|
11
|
+
def initialize(value)
|
|
12
|
+
super()
|
|
13
|
+
@value = value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents references to variables passed as arguments (e.g., $variable)
|
|
8
|
+
class VariableReference < SyntaxNode
|
|
9
|
+
attr_accessor :id
|
|
10
|
+
|
|
11
|
+
def initialize(id)
|
|
12
|
+
super()
|
|
13
|
+
@id = id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def children = [id]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
module AST
|
|
7
|
+
# Represents individual variants within select expressions
|
|
8
|
+
class Variant < SyntaxNode
|
|
9
|
+
attr_accessor :key
|
|
10
|
+
attr_accessor :value
|
|
11
|
+
attr_accessor :default
|
|
12
|
+
|
|
13
|
+
def initialize(key, value, default: false)
|
|
14
|
+
super()
|
|
15
|
+
@key = key
|
|
16
|
+
@value = value
|
|
17
|
+
@default = default
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def children = [key, value]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
# Namespace module for abstract syntax tree node classes
|
|
7
|
+
# With Zeitwerk, individual node classes are automatically loaded when referenced
|
|
8
|
+
module AST
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foxtail
|
|
4
|
+
module Syntax
|
|
5
|
+
class Parser
|
|
6
|
+
# Parse error with detailed error codes and messages
|
|
7
|
+
class ParseError < Foxtail::Syntax::Error
|
|
8
|
+
# @return [String] Error code (e.g., "E0001", "E0002")
|
|
9
|
+
attr_reader :code
|
|
10
|
+
# @return [Array] Additional arguments for error message formatting
|
|
11
|
+
attr_reader :args
|
|
12
|
+
|
|
13
|
+
# @param code [String] Error code
|
|
14
|
+
# @param args [Array] Additional arguments for error message formatting
|
|
15
|
+
def initialize(code, *args)
|
|
16
|
+
@code = code
|
|
17
|
+
@args = args
|
|
18
|
+
super(error_message(code, args))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private def error_message(code, args)
|
|
22
|
+
case code
|
|
23
|
+
when "E0001"
|
|
24
|
+
"Generic error"
|
|
25
|
+
when "E0002"
|
|
26
|
+
"Expected an entry start"
|
|
27
|
+
when "E0003"
|
|
28
|
+
token = args[0]
|
|
29
|
+
"Expected token: \"#{token}\""
|
|
30
|
+
when "E0004"
|
|
31
|
+
range = args[0]
|
|
32
|
+
"Expected a character from range: \"#{range}\""
|
|
33
|
+
when "E0005"
|
|
34
|
+
id = args[0]
|
|
35
|
+
"Expected message \"#{id}\" to have a value or attributes"
|
|
36
|
+
when "E0006"
|
|
37
|
+
id = args[0]
|
|
38
|
+
"Expected term \"-#{id}\" to have a value"
|
|
39
|
+
when "E0007"
|
|
40
|
+
"Expected a keyword"
|
|
41
|
+
when "E0008"
|
|
42
|
+
"The callee has to be an upper-case identifier or a term"
|
|
43
|
+
when "E0009"
|
|
44
|
+
"The argument name has to be a simple identifier"
|
|
45
|
+
when "E0010"
|
|
46
|
+
"Expected one of the variants to be marked as default (*)"
|
|
47
|
+
when "E0011"
|
|
48
|
+
"Expected at least one variant after \"->\""
|
|
49
|
+
when "E0012"
|
|
50
|
+
"Expected value"
|
|
51
|
+
when "E0013"
|
|
52
|
+
"Expected a variant key"
|
|
53
|
+
when "E0014"
|
|
54
|
+
"Expected literal"
|
|
55
|
+
when "E0015"
|
|
56
|
+
"Only one variant can be marked as default (*)"
|
|
57
|
+
when "E0016"
|
|
58
|
+
"Message references cannot be used as selectors"
|
|
59
|
+
when "E0017"
|
|
60
|
+
"Terms cannot be used as selectors"
|
|
61
|
+
when "E0018"
|
|
62
|
+
"Attributes of messages cannot be used as selectors"
|
|
63
|
+
when "E0019"
|
|
64
|
+
"Attributes of terms cannot be used as placeables"
|
|
65
|
+
when "E0020"
|
|
66
|
+
"Unterminated string expression"
|
|
67
|
+
when "E0021"
|
|
68
|
+
"Positional arguments must not follow named arguments"
|
|
69
|
+
when "E0022"
|
|
70
|
+
"Named arguments must be unique"
|
|
71
|
+
when "E0023"
|
|
72
|
+
"Expected an option list"
|
|
73
|
+
when "E0024"
|
|
74
|
+
"Expected a keyword argument"
|
|
75
|
+
when "E0025"
|
|
76
|
+
arg = args[0]
|
|
77
|
+
"Unknown escape sequence: \\#{arg}."
|
|
78
|
+
when "E0026"
|
|
79
|
+
sequence = args[0]
|
|
80
|
+
"Invalid Unicode escape sequence: #{sequence}."
|
|
81
|
+
when "E0027"
|
|
82
|
+
"Unbalanced closing brace in TextElement."
|
|
83
|
+
when "E0028"
|
|
84
|
+
"Expected an inline expression"
|
|
85
|
+
when "E0029"
|
|
86
|
+
"Nested placeables are not allowed"
|
|
87
|
+
else
|
|
88
|
+
"Unknown error: #{code}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|