antlers 0.0.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1af03cc260e8dd49409f43d325fbf63271128bc55dcb9f9d8fa87151b2e4f3c9
4
- data.tar.gz: f60f7b0ef2eef4c809a90d434b38e3dc20e0a7e2665633909b403433aa1a6726
3
+ metadata.gz: 7aef5ead7628092869dbae7f4ae7fee7bea88a69e1c3ee2e9ebd5fd6ace600ea
4
+ data.tar.gz: a96ba7818b542305105305c9c7cbc73a6c4962b118e6f0686f1eb73cfda59a56
5
5
  SHA512:
6
- metadata.gz: 1f7d7f8a0a141c2cb9bb8ccd4907b64d69a9d75cbf9fe98dfb8ddc4f3c37a175f38ae3e7dfb7798a9b2bcb07dd9abb7d537e472b62e534daeb7635a70bdcabc9
7
- data.tar.gz: d333bc441ccf5531e22325c94ccffe0820215ce68be6bd3a4c512fe8e28c5fd5ff711e18fc81ec0a2336fff131cc281b2b348d6f893ce6f6407c5cb4e75ba424
6
+ metadata.gz: ff1db9dd204501ae40f3ccc43ebf56b236aa1776ef233edd7a982d34ca5ad5d5486aaad29de46f6f382adc092cb54d5169a6a84443faae5799eb26cb7a9c96ee
7
+ data.tar.gz: 5637d1f3cb287c70b7f47d0cb7405c594b98fc3858a624e10302ef935e88872f07fa5a67403bbb091a7ec29af758161f8effa9c2055ddd3a12ec2aa32d1d470a
data/lib/antlers.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antlers
4
+ class << self
5
+ def parse(template)
6
+ return template unless template.include?('<{') || template.include?('{')
7
+
8
+ lexemes = Lexer.new.parse(template)
9
+ Parser.parse(lexemes)
10
+ end
11
+
12
+ # TODO: Render the AST.
13
+ def render(antler_node); end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../nodes/prop_node'
4
+ require_relative '../nodes/slot_node'
5
+ require_relative '../nodes/var_node'
6
+
7
+ module Antlers
8
+ class NodeFactory
9
+ class << self
10
+ def var_node(segment:)
11
+ VarNode.new(name: segment[:ivar])
12
+ end
13
+
14
+ def prop_node(segment:)
15
+ PropNode.new(name: segment[:prop], props: segment[:props])
16
+ end
17
+
18
+ def slot_node(segment:)
19
+ SlotNode.new(name: segment[:slot_def], props: segment[:props])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antlers
4
+ class AntlerNode
5
+ attr_reader :name
6
+
7
+ def initialize(name:)
8
+ @name = name
9
+ end
10
+
11
+ # Consider this a value object on comparison.
12
+ def ==(other) = other.class == self.class
13
+ def eql?(other) = self == other
14
+ def hash = [self.class].hash
15
+ end
16
+ end
data/lib/lexer.rb ADDED
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antlers
4
+ class LexerParseError < StandardError; end
5
+
6
+ class Lexer
7
+ def initialize
8
+ @delimiters = ['<{', '}>', '{', '}']
9
+ @keywords = ['if:', 'for:', 'in:']
10
+ @cursor = 0
11
+ end
12
+
13
+ def parse(template)
14
+ @cursor = 0
15
+ sequence = []
16
+
17
+ # Split on delimiters and retain capture groups.
18
+ segments = template.split(/(#{Regexp.union(@delimiters)})/).map(&:strip)
19
+
20
+ until segments[@cursor].nil?
21
+ if (antlers_segment = antlers_segment(segments:))
22
+ sequence << antlers_lexeme(antlers_segment:, segments:)
23
+ # Skipping: ['{', '@ivar', '}']
24
+ # Skipping: ['<{', 'name + props + keywords', '}>']
25
+ @cursor += 3
26
+ else
27
+ segment = segments[@cursor]
28
+ sequence << segment unless segment.empty?
29
+ @cursor += 1
30
+ end
31
+ end
32
+
33
+ sequence
34
+ end
35
+
36
+ private
37
+
38
+ def antlers_segment(segments:)
39
+ next_segment = segments[@cursor + 1]
40
+ return nil unless next_segment && (segments[@cursor] == '<{' || ivar?(segments:))
41
+
42
+ next_segment
43
+ end
44
+
45
+ def antlers_lexeme(antlers_segment:, segments:)
46
+ return ivar(antlers_segment:) if ivar?(segments:)
47
+
48
+ name, props, _keywords = parse_segment(antlers_segment:)
49
+
50
+ return slot(name:, props:) if slot?(name)
51
+ return prop(name:, props:) if prop?(name)
52
+
53
+ raise LexerParseError, "Couldn't parse antlers syntax: '#{antlers_segment}'"
54
+ end
55
+
56
+ def parse_segment(antlers_segment:)
57
+ name_and_props, *keywords = antlers_segment.split(/(#{Regexp.union(@keywords)})/)
58
+ name, *props = name_and_props.split(' ')
59
+
60
+ [name, props, keywords]
61
+ end
62
+
63
+ def ivar?(segments:)
64
+ first, second, third = segments[@cursor..@cursor + 3].map(&:strip)
65
+ first == '{' && second&.start_with?('@') && third == '}'
66
+ end
67
+
68
+ def slot?(name)
69
+ name.start_with?(':') || name.end_with?(':')
70
+ end
71
+
72
+ def prop?(name)
73
+ [*'A'..'Z'].include?(name[0])
74
+ end
75
+
76
+ def ivar(antlers_segment:)
77
+ { ivar: antlers_segment.delete_prefix('@') }
78
+ end
79
+
80
+ def slot(name:, props:)
81
+ if name.end_with?(':')
82
+ slot_def = { slot_def: name.delete_suffix(':') }
83
+ slot_def[:props] = props(props) unless props.empty?
84
+ return slot_def
85
+ end
86
+
87
+ { slot_end: name.delete_prefix(':') }
88
+ end
89
+
90
+ def prop(name:, props:)
91
+ prop = { prop: name }
92
+ prop[:props] = props(props) unless props.empty?
93
+ prop
94
+ end
95
+
96
+ def props(props)
97
+ odd_props = props.join(' ').split(/(=)|\s/)
98
+
99
+ return {} unless odd_props.any?
100
+
101
+ props = {}
102
+ until odd_props.empty?
103
+ prop = odd_props.shift
104
+ value = nil
105
+
106
+ if odd_props.first == '='
107
+ odd_props.shift
108
+ value = odd_props.shift
109
+ end
110
+
111
+ props[prop] = value
112
+ end
113
+
114
+ props
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/antler_node'
4
+
5
+ module Antlers
6
+ attr_accessor :props
7
+
8
+ class PropNode < AntlerNode
9
+ def initialize(name:, props: [])
10
+ super(name:)
11
+
12
+ @props = props
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/antler_node'
4
+
5
+ module Antlers
6
+ class RootNode < AntlerNode
7
+ attr_accessor :children
8
+
9
+ def initialize(name: :root_node, children: [])
10
+ super(name:)
11
+
12
+ @children = children
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../nodes/prop_node'
4
+
5
+ module Antlers
6
+ class SlotNode < PropNode
7
+ attr_accessor :children
8
+
9
+ def initialize(name:, props: [], children: [])
10
+ super(name:, props:)
11
+
12
+ @children = children
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/antler_node'
4
+
5
+ module Antlers
6
+ class VarNode < AntlerNode
7
+ end
8
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'factories/node_factory'
4
+ require_relative 'nodes/root_node'
5
+
6
+ module Antlers
7
+ module Parser
8
+ class << self
9
+ def parse(sequence, id: :root_node)
10
+ branch(branch_node: RootNode.new(name: id), sequence:)
11
+ end
12
+
13
+ def branch(branch_node:, sequence:) # rubocop:disable Metrics/AbcSize
14
+ until sequence.empty?
15
+ segment = sequence.shift
16
+
17
+ if segment.is_a?(String)
18
+ branch_node.children << segment
19
+ elsif segment[:ivar]
20
+ branch_node.children << NodeFactory.var_node(segment:)
21
+ elsif segment[:prop]
22
+ branch_node.children << NodeFactory.prop_node(segment:)
23
+ elsif segment[:slot_def]
24
+ slot_node = NodeFactory.slot_node(segment:)
25
+ branch_node.children << slot_node
26
+
27
+ sub_sequence = []
28
+ sub_sequence << sequence.shift until sequence.first[:slot_end] == slot_node.name
29
+
30
+ branch(branch_node: slot_node, sequence: sub_sequence)
31
+ end
32
+ end
33
+
34
+ branch_node
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antlers
4
- VERSION = '0.0.0'
4
+ VERSION = '0.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: antlers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -30,6 +30,15 @@ executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - lib/antlers.rb
34
+ - lib/factories/node_factory.rb
35
+ - lib/interfaces/antler_node.rb
36
+ - lib/lexer.rb
37
+ - lib/nodes/prop_node.rb
38
+ - lib/nodes/root_node.rb
39
+ - lib/nodes/slot_node.rb
40
+ - lib/nodes/var_node.rb
41
+ - lib/parser.rb
33
42
  - lib/version.rb
34
43
  homepage: https://codeberg.org/raindeer/antlers
35
44
  licenses: []