luoma 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 +7 -0
- checksums.yaml.gz.sig +2 -0
- data/.rdoc_options +16 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +69 -0
- data/Steepfile +41 -0
- data/certs/jgrp.pem +27 -0
- data/docs/configuration.md +180 -0
- data/docs/custom_filters.md +3 -0
- data/docs/custom_tags.md +3 -0
- data/docs/expressions.md +3 -0
- data/docs/extension_types.md +41 -0
- data/docs/filter_reference.md +1702 -0
- data/docs/index.md +73 -0
- data/docs/luoma_for_template_authors.md +3 -0
- data/docs/markdown.md +111 -0
- data/docs/predicate_reference.md +3 -0
- data/docs/static_analysis.md +3 -0
- data/docs/tag_reference.md +352 -0
- data/docs/template_loaders.md +177 -0
- data/docs/undefined_variables.md +3 -0
- data/docs-requirements.txt +11 -0
- data/docs_/cycle.md +17 -0
- data/docs_/font_rendering_example.md +157 -0
- data/docs_/header_block_example.md +51 -0
- data/docs_/increment_and_decrement.md +49 -0
- data/docs_/logo_style_example.md +40 -0
- data/docs_/migration.md +11 -0
- data/docs_/notes.md +19 -0
- data/lib/luoma/cache.rb +80 -0
- data/lib/luoma/chain_hash.rb +54 -0
- data/lib/luoma/context.rb +227 -0
- data/lib/luoma/drop.rb +84 -0
- data/lib/luoma/drops/block.rb +33 -0
- data/lib/luoma/drops/expression.rb +29 -0
- data/lib/luoma/drops/html_safe.rb +36 -0
- data/lib/luoma/drops/range.rb +69 -0
- data/lib/luoma/drops/undefined.rb +119 -0
- data/lib/luoma/environment.rb +479 -0
- data/lib/luoma/errors.rb +79 -0
- data/lib/luoma/escape.rb +35 -0
- data/lib/luoma/expression.rb +1271 -0
- data/lib/luoma/filter.rb +97 -0
- data/lib/luoma/filters/array.rb +372 -0
- data/lib/luoma/filters/date.rb +19 -0
- data/lib/luoma/filters/default.rb +16 -0
- data/lib/luoma/filters/json.rb +14 -0
- data/lib/luoma/filters/math.rb +84 -0
- data/lib/luoma/filters/size.rb +13 -0
- data/lib/luoma/filters/slice.rb +52 -0
- data/lib/luoma/filters/sort.rb +113 -0
- data/lib/luoma/filters/string.rb +186 -0
- data/lib/luoma/fnv.rb +15 -0
- data/lib/luoma/lexer.rb +106 -0
- data/lib/luoma/lexer_legacy.rb +396 -0
- data/lib/luoma/lexer_unified.rb +279 -0
- data/lib/luoma/loader.rb +50 -0
- data/lib/luoma/loaders/choice_loader.rb +20 -0
- data/lib/luoma/loaders/file_system_loader.rb +75 -0
- data/lib/luoma/loaders/mixins.rb +52 -0
- data/lib/luoma/markup.rb +109 -0
- data/lib/luoma/parser.rb +252 -0
- data/lib/luoma/parser_unified.rb +979 -0
- data/lib/luoma/predicates/blank.rb +19 -0
- data/lib/luoma/predicates/defined.rb +10 -0
- data/lib/luoma/predicates/empty.rb +15 -0
- data/lib/luoma/predicates/type.rb +35 -0
- data/lib/luoma/static_analysis.rb +427 -0
- data/lib/luoma/tags/assign.rb +63 -0
- data/lib/luoma/tags/break.rb +23 -0
- data/lib/luoma/tags/capture.rb +43 -0
- data/lib/luoma/tags/case.rb +137 -0
- data/lib/luoma/tags/comment.rb +17 -0
- data/lib/luoma/tags/continue.rb +23 -0
- data/lib/luoma/tags/define.rb +42 -0
- data/lib/luoma/tags/else.rb +30 -0
- data/lib/luoma/tags/for.rb +108 -0
- data/lib/luoma/tags/if.rb +109 -0
- data/lib/luoma/tags/import.rb +91 -0
- data/lib/luoma/tags/include.rb +77 -0
- data/lib/luoma/tags/output.rb +20 -0
- data/lib/luoma/tags/raw.rb +26 -0
- data/lib/luoma/tags/render.rb +92 -0
- data/lib/luoma/tags/with.rb +55 -0
- data/lib/luoma/template.rb +178 -0
- data/lib/luoma/token.rb +84 -0
- data/lib/luoma/version.rb +5 -0
- data/lib/luoma.rb +75 -0
- data/sig/luoma/cache.rbs +44 -0
- data/sig/luoma/chain_hash.rbs +26 -0
- data/sig/luoma/context.rbs +115 -0
- data/sig/luoma/drop.rbs +44 -0
- data/sig/luoma/drops/block.rbs +10 -0
- data/sig/luoma/drops/expression.rbs +10 -0
- data/sig/luoma/drops/html_safe.rbs +14 -0
- data/sig/luoma/drops/range.rbs +15 -0
- data/sig/luoma/drops/undefined.rbs +27 -0
- data/sig/luoma/environment.rbs +212 -0
- data/sig/luoma/errors.rbs +67 -0
- data/sig/luoma/escape.rbs +15 -0
- data/sig/luoma/expression.rbs +514 -0
- data/sig/luoma/filter.rbs +66 -0
- data/sig/luoma/filters/array.rbs +74 -0
- data/sig/luoma/filters/date.rbs +9 -0
- data/sig/luoma/filters/default.rbs +8 -0
- data/sig/luoma/filters/json.rbs +7 -0
- data/sig/luoma/filters/math.rbs +37 -0
- data/sig/luoma/filters/size.rbs +6 -0
- data/sig/luoma/filters/slice.rbs +10 -0
- data/sig/luoma/filters/sort.rbs +17 -0
- data/sig/luoma/filters/string.rbs +103 -0
- data/sig/luoma/fnv.rbs +3 -0
- data/sig/luoma/lexer.rbs +42 -0
- data/sig/luoma/lexer_legacy.rbs +81 -0
- data/sig/luoma/lexer_unified.rbs +49 -0
- data/sig/luoma/loader.rbs +40 -0
- data/sig/luoma/loaders/choice_loader.rbs +9 -0
- data/sig/luoma/loaders/file_system_loader.rbs +19 -0
- data/sig/luoma/loaders/mixins.rbs +16 -0
- data/sig/luoma/markup.rbs +68 -0
- data/sig/luoma/parser.rbs +105 -0
- data/sig/luoma/parser_unified.rbs +150 -0
- data/sig/luoma/predicates/blank.rbs +6 -0
- data/sig/luoma/predicates/defined.rbs +6 -0
- data/sig/luoma/predicates/empty.rbs +6 -0
- data/sig/luoma/predicates/type.rbs +21 -0
- data/sig/luoma/static_analysis.rbs +141 -0
- data/sig/luoma/tags/assign.rbs +12 -0
- data/sig/luoma/tags/break.rbs +11 -0
- data/sig/luoma/tags/capture.rbs +14 -0
- data/sig/luoma/tags/case.rbs +36 -0
- data/sig/luoma/tags/comment.rbs +11 -0
- data/sig/luoma/tags/continue.rbs +11 -0
- data/sig/luoma/tags/define.rbs +16 -0
- data/sig/luoma/tags/else.rbs +17 -0
- data/sig/luoma/tags/for.rbs +26 -0
- data/sig/luoma/tags/if.rbs +45 -0
- data/sig/luoma/tags/import.rbs +13 -0
- data/sig/luoma/tags/include.rbs +13 -0
- data/sig/luoma/tags/output.rbs +13 -0
- data/sig/luoma/tags/raw.rbs +11 -0
- data/sig/luoma/tags/render.rbs +13 -0
- data/sig/luoma/tags/with.rbs +15 -0
- data/sig/luoma/template.rbs +117 -0
- data/sig/luoma/token.rbs +81 -0
- data/sig/luoma.rbs +11 -0
- data/zensical.toml +363 -0
- data.tar.gz.sig +0 -0
- metadata +233 -0
- metadata.gz.sig +0 -0
data/lib/luoma/loader.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
# The base class for all template loaders.
|
|
5
|
+
class TemplateLoader
|
|
6
|
+
# Load and return template source text and any associated data.
|
|
7
|
+
#: (Environment, String, ?context: RenderContext?, **untyped) -> TemplateSource
|
|
8
|
+
def get_source(env, name, context: nil, **kwargs)
|
|
9
|
+
raise "not implemented"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#: (Environment, String, ?globals: t_namespace?, ?context: RenderContext?, **kwargs) -> Template
|
|
13
|
+
def load(env, name, globals: nil, context: nil, **kwargs)
|
|
14
|
+
data = get_source(env, name, context: context, **kwargs)
|
|
15
|
+
env.parse(data.source,
|
|
16
|
+
name: data.name,
|
|
17
|
+
globals: globals,
|
|
18
|
+
up_to_date: data.up_to_date,
|
|
19
|
+
overlay: data.matter)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Template source text and meta data.
|
|
24
|
+
class TemplateSource
|
|
25
|
+
attr_accessor :source, :name, :up_to_date, :matter
|
|
26
|
+
|
|
27
|
+
def initialize(source:, name:, up_to_date: nil, matter: nil)
|
|
28
|
+
@source = source
|
|
29
|
+
@name = name
|
|
30
|
+
@up_to_date = up_to_date
|
|
31
|
+
@matter = matter
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# A template loader that reads templates from a hash.
|
|
36
|
+
class HashLoader < TemplateLoader
|
|
37
|
+
#: (Hash<String, String>) -> void
|
|
38
|
+
def initialize(templates)
|
|
39
|
+
super()
|
|
40
|
+
@templates = templates
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_source(env, name, context: nil, **kwargs)
|
|
44
|
+
source = @templates[name]
|
|
45
|
+
raise TemplateNotFoundError.new("template not found #{name}") unless source
|
|
46
|
+
|
|
47
|
+
TemplateSource.new(source: source, name: name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class ChoiceLoader < TemplateLoader
|
|
5
|
+
def initialize(*loaders)
|
|
6
|
+
super
|
|
7
|
+
@loaders = loaders
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def load(env, name, **kwargs)
|
|
11
|
+
@loaders.each do |loader|
|
|
12
|
+
return loader.load(env, name, **kwargs)
|
|
13
|
+
rescue TemplateNotFoundError
|
|
14
|
+
next
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
raise TemplateNotFoundError.new("template not found #{name.inspect}")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Luoma
|
|
6
|
+
# A template loader that reads template from a file system.
|
|
7
|
+
class FileSystemLoader < TemplateLoader
|
|
8
|
+
def initialize(search_path, default_extension: nil)
|
|
9
|
+
super()
|
|
10
|
+
@search_path = if search_path.is_a?(Array)
|
|
11
|
+
search_path.map { |p| Pathname.new(p) }
|
|
12
|
+
else
|
|
13
|
+
[Pathname.new(search_path)]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@default_extension = default_extension
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_source(_env, name, context: nil, **_kwargs)
|
|
20
|
+
path = resolve_path(name)
|
|
21
|
+
mtime = path.mtime
|
|
22
|
+
up_to_date = -> { path.mtime == mtime }
|
|
23
|
+
TemplateSource.new(source: path.read, name: path.basename.to_s, up_to_date: up_to_date)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def resolve_path(template_name)
|
|
27
|
+
template_path = Pathname.new(template_name)
|
|
28
|
+
|
|
29
|
+
# Append the default file extension if needed.
|
|
30
|
+
if @default_extension && template_path.extname.empty?
|
|
31
|
+
template_path = template_path.sub_ext(@default_extension || raise)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# TODO: better relative path check
|
|
35
|
+
|
|
36
|
+
# Don't alow template names to escape the search path with "../".
|
|
37
|
+
template_path.each_filename do |part|
|
|
38
|
+
next unless part == ".."
|
|
39
|
+
|
|
40
|
+
raise TemplateNotFoundError.new("template not found #{template_name}")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Search each path in turn.
|
|
44
|
+
@search_path.each do |path|
|
|
45
|
+
source_path = path.join(template_path)
|
|
46
|
+
return source_path if source_path.file?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
raise TemplateNotFoundError.new("template not found #{template_name}")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A file system template loader that caches parsed templates.
|
|
54
|
+
class CachingFileSystemLoader < FileSystemLoader
|
|
55
|
+
include CachingLoaderMixin
|
|
56
|
+
|
|
57
|
+
def initialize(
|
|
58
|
+
search_path,
|
|
59
|
+
default_extension: nil,
|
|
60
|
+
auto_reload: true,
|
|
61
|
+
namespace_key: "",
|
|
62
|
+
capacity: 300,
|
|
63
|
+
thread_safe: false
|
|
64
|
+
)
|
|
65
|
+
super(search_path, default_extension: default_extension)
|
|
66
|
+
|
|
67
|
+
initialize_cache(
|
|
68
|
+
auto_reload: auto_reload,
|
|
69
|
+
namespace_key: namespace_key,
|
|
70
|
+
capacity: capacity,
|
|
71
|
+
thread_safe: thread_safe
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../cache"
|
|
4
|
+
|
|
5
|
+
module Luoma
|
|
6
|
+
# A mixin that adds caching to a template loader.
|
|
7
|
+
module CachingLoaderMixin
|
|
8
|
+
def initialize_cache(auto_reload: true, namespace_key: "", capacity: 300, thread_safe: false)
|
|
9
|
+
@auto_reload = auto_reload
|
|
10
|
+
@namespace_key = namespace_key
|
|
11
|
+
@cache = if thread_safe
|
|
12
|
+
ThreadSafeLRUCache.new(capacity)
|
|
13
|
+
else
|
|
14
|
+
LRUCache.new(capacity)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load(env, name, globals: nil, context: nil, **)
|
|
19
|
+
key = cache_key(name, context: context, **)
|
|
20
|
+
|
|
21
|
+
# @type var template: Template
|
|
22
|
+
# @type var cached_template: Template
|
|
23
|
+
if (cached_template = @cache[key])
|
|
24
|
+
if @auto_reload && cached_template.up_to_date? == false
|
|
25
|
+
template = super
|
|
26
|
+
@cache[key] = template
|
|
27
|
+
template
|
|
28
|
+
else
|
|
29
|
+
cached_template
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
template = super
|
|
33
|
+
@cache[key] = template
|
|
34
|
+
template
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def cache_key(name, context: nil, **kwargs)
|
|
39
|
+
return name unless @namespace_key
|
|
40
|
+
|
|
41
|
+
key = (@namespace_key || raise).to_sym
|
|
42
|
+
return "#{kwargs[key]}/#{name}" if kwargs.include?(key)
|
|
43
|
+
return name unless context
|
|
44
|
+
|
|
45
|
+
if (namespace = context.globals[@namespace_key || raise])
|
|
46
|
+
"#{namespace}/#{name}"
|
|
47
|
+
else
|
|
48
|
+
name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/luoma/markup.rb
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
# The base class for all tag nodes and the output statement.
|
|
5
|
+
class Markup
|
|
6
|
+
attr_reader :token, :blank, :tag_name
|
|
7
|
+
|
|
8
|
+
#: (t_token) -> void
|
|
9
|
+
def initialize(token)
|
|
10
|
+
@token = token
|
|
11
|
+
@blank = false
|
|
12
|
+
@tag_name = ""
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: (RenderContext, String) -> void
|
|
16
|
+
def render(context, buffer)
|
|
17
|
+
raise "not implemented"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#: (RenderContext) -> Array[Markup]
|
|
21
|
+
def children(static_context)
|
|
22
|
+
[]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: () -> Array[Expression]
|
|
26
|
+
def expressions
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: () -> Array[Name]
|
|
31
|
+
def block_scope
|
|
32
|
+
[]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#: () -> Array[Name]
|
|
36
|
+
def template_scope
|
|
37
|
+
[]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#: (RenderContext) -> Partial?
|
|
41
|
+
def partial(static_context)
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Partial
|
|
47
|
+
attr_reader :template, :scope_kind, :in_scope, :key
|
|
48
|
+
|
|
49
|
+
#: (Template, :shared | :isolated | :inherited, Array[Name], Integer) -> void
|
|
50
|
+
def initialize(template, scope_kind, in_scope, key)
|
|
51
|
+
@template = template
|
|
52
|
+
@scope_kind = scope_kind
|
|
53
|
+
@in_scope = in_scope
|
|
54
|
+
@key = key
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#: (t_block, RenderContext, String, ?root: false) -> void
|
|
59
|
+
def self.render_block(block, context, buffer, root: false)
|
|
60
|
+
if context.env.max_render_score || context.env.max_render_score_cumulative
|
|
61
|
+
context.render_score += block.length
|
|
62
|
+
context.render_score_cumulative += block.length
|
|
63
|
+
|
|
64
|
+
if (context.env.max_render_score && context.render_score > context.env.max_render_score) ||
|
|
65
|
+
(context.env.max_render_score_cumulative &&
|
|
66
|
+
context.render_score_cumulative > context.env.max_render_score_cumulative)
|
|
67
|
+
raise ResourceLimitError.new("memory limits reached")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
block.each do |node|
|
|
72
|
+
if node.is_a?(String)
|
|
73
|
+
buffer << node
|
|
74
|
+
else
|
|
75
|
+
if context.disabled_tags&.include?(node.tag_name)
|
|
76
|
+
raise DisabledTagError.new(
|
|
77
|
+
"#{node.tag_name.inspect} is not allowed in this context",
|
|
78
|
+
node.token,
|
|
79
|
+
context.template.source,
|
|
80
|
+
context.template.name
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
node.render(context, buffer)
|
|
85
|
+
|
|
86
|
+
# TODO: disable these when strict is false?
|
|
87
|
+
if root && !context.interrupts.empty?
|
|
88
|
+
raise TemplateSyntaxError.new(
|
|
89
|
+
"unexpected #{context.interrupts.last}",
|
|
90
|
+
node.token,
|
|
91
|
+
context.template.source,
|
|
92
|
+
context.template.name
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if context.env.max_render_size && buffer.bytesize > context.env.max_render_size
|
|
98
|
+
raise ResourceLimitError.new("memory limits exceeded")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
break unless context.interrupts.empty?
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#: (t_block) -> bool
|
|
106
|
+
def self.blank_block?(block)
|
|
107
|
+
block.all? { |node| node.is_a?(String) ? node.match?(/\A\s*\Z/) : node.blank }
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/luoma/parser.rb
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class Parser
|
|
5
|
+
attr_reader :source, :template_name, :env
|
|
6
|
+
|
|
7
|
+
TERMINATE_EXPRESSION = Set.new(%i[
|
|
8
|
+
token_wc
|
|
9
|
+
token_out_end
|
|
10
|
+
token_tag_end
|
|
11
|
+
token_text
|
|
12
|
+
token_rparen
|
|
13
|
+
token_eoi
|
|
14
|
+
token_if
|
|
15
|
+
token_else
|
|
16
|
+
token_interpolation_end
|
|
17
|
+
]).freeze #: Set[t_token_kind]
|
|
18
|
+
|
|
19
|
+
#: (Environment, String, String, Array[t_token]) -> t_block
|
|
20
|
+
def self.parse(env, source, template_name, tokens)
|
|
21
|
+
new(env, source, template_name, tokens).parse_block
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#: (Environment, String, String, Array[t_token]) -> void
|
|
25
|
+
def initialize(env, source, template_name, tokens)
|
|
26
|
+
@env = env
|
|
27
|
+
@source = source
|
|
28
|
+
@template_name = template_name
|
|
29
|
+
@tokens = tokens
|
|
30
|
+
|
|
31
|
+
@pos = 0
|
|
32
|
+
@eoi = [:token_eoi, source.length, source.length]
|
|
33
|
+
@whitespace_control_carry = nil #: "-" | "+" | "~" | nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#: () -> void
|
|
37
|
+
def carry_whitespace_control
|
|
38
|
+
@whitespace_control_carry = (Luoma.get_token_value(self.next, @source) if kind == :token_wc)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#: () -> t_token
|
|
42
|
+
def current
|
|
43
|
+
@tokens[@pos] || @eoi
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#: () -> t_token_kind
|
|
47
|
+
def kind
|
|
48
|
+
(@tokens[@pos] || @eoi).first #: t_token_kind
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#: () -> String
|
|
52
|
+
def current_value
|
|
53
|
+
Luoma.get_token_value(@tokens[@pos] || @eoi, @source)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#: () -> t_token
|
|
57
|
+
def next
|
|
58
|
+
token = @tokens[@pos] || @eoi
|
|
59
|
+
@pos += 1
|
|
60
|
+
token
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def peek(offset = 1)
|
|
64
|
+
@tokens[@pos + offset] || @eoi
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#: (t_token_kind, ?message: String?) -> t_token
|
|
68
|
+
def eat(kind, message: nil)
|
|
69
|
+
token = @tokens[@pos] || @eoi
|
|
70
|
+
unless token.first == kind
|
|
71
|
+
kind_ = Luoma::TOKEN_KIND_MAP[token.first]
|
|
72
|
+
value = Luoma.get_token_value(token, @source).inspect
|
|
73
|
+
raise TemplateSyntaxError.new(
|
|
74
|
+
message || "unexpected #{kind_} (#{value})",
|
|
75
|
+
token,
|
|
76
|
+
@source,
|
|
77
|
+
@template_name
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@pos += 1
|
|
82
|
+
token
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#: (String) -> t_token
|
|
86
|
+
def eat_empty_tag(name)
|
|
87
|
+
eat(:token_tag_start, message: "expected tag #{name}")
|
|
88
|
+
@pos += 1 if kind == :token_wc # steep:ignore
|
|
89
|
+
token = eat(:token_tag_name, message: "expected tag #{name}")
|
|
90
|
+
got = Luoma.get_token_value(token, @source)
|
|
91
|
+
|
|
92
|
+
unless got == name
|
|
93
|
+
raise TemplateSyntaxError.new(
|
|
94
|
+
"unexpected tag #{got.inspect}",
|
|
95
|
+
token,
|
|
96
|
+
@source,
|
|
97
|
+
@template_name
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
carry_whitespace_control
|
|
102
|
+
eat(:token_tag_end, message: "expected a closing tag delimiter")
|
|
103
|
+
token
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
#: (Set[t_token_kind]) -> t_token
|
|
107
|
+
def eat_one_of(kinds)
|
|
108
|
+
token = @tokens[@pos] || @eoi
|
|
109
|
+
unless kinds.include?(token.first)
|
|
110
|
+
kind_ = Luoma::TOKEN_KIND_MAP[token.first]
|
|
111
|
+
value = Luoma.get_token_value(token, @source).inspect
|
|
112
|
+
raise TemplateSyntaxError.new(
|
|
113
|
+
"unexpected #{kind_} (#{value})",
|
|
114
|
+
token,
|
|
115
|
+
@source,
|
|
116
|
+
@template_name
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@pos += 1
|
|
121
|
+
token
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#: (String) -> t_token
|
|
125
|
+
def eat_tag(name)
|
|
126
|
+
eat(:token_tag_start, message: "expected tag #{name}")
|
|
127
|
+
@pos += 1 if kind == :token_wc # steep:ignore
|
|
128
|
+
token = eat(:token_tag_name, message: "expected tag #{name}")
|
|
129
|
+
got = Luoma.get_token_value(token, @source)
|
|
130
|
+
|
|
131
|
+
unless got == name
|
|
132
|
+
raise TemplateSyntaxError.new(
|
|
133
|
+
"unexpected tag #{got.inspect}",
|
|
134
|
+
token,
|
|
135
|
+
@source,
|
|
136
|
+
@template_name
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Ignore everything between the tag name and the closing tag delimiter.
|
|
141
|
+
@pos += 1 until TERMINATE_EXPRESSION.include?(kind)
|
|
142
|
+
|
|
143
|
+
carry_whitespace_control
|
|
144
|
+
eat(:token_tag_end, message: "expected a closing tag delimiter")
|
|
145
|
+
token
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Raise an error if we're not at the start of an expression.
|
|
149
|
+
#: () -> void
|
|
150
|
+
def expect_expression
|
|
151
|
+
if TERMINATE_EXPRESSION.include?(kind)
|
|
152
|
+
raise TemplateSyntaxError.new(
|
|
153
|
+
"missing expression",
|
|
154
|
+
current,
|
|
155
|
+
@source,
|
|
156
|
+
@template_name
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#: (?require_commas: bool?) -> Array[Expression | KeywordArgument]
|
|
162
|
+
def parse_arguments(require_commas: nil)
|
|
163
|
+
raise "not implemented"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#: (?stop: Set[String]?) -> t_block
|
|
167
|
+
def parse_block(stop: nil)
|
|
168
|
+
raise "not implemented"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
#: (?precedence: Integer) -> Expression
|
|
172
|
+
def parse_expression(precedence: 1)
|
|
173
|
+
raise "not implemented"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
#: () -> Name
|
|
177
|
+
def parse_ident
|
|
178
|
+
raise "not implemented"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
#: (?require_commas: bool?) -> Array[KeywordArgument]
|
|
182
|
+
def parse_keyword_arguments(require_commas: nil)
|
|
183
|
+
raise "not implemented"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Parse an identifier, possibly surrounded by quotes.
|
|
187
|
+
#: () -> Name
|
|
188
|
+
def parse_name
|
|
189
|
+
raise "not implemented"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
#: (?require_commas: bool?) -> Array[Expression]
|
|
193
|
+
def parse_positional_arguments(require_commas: nil)
|
|
194
|
+
raise "not implemented"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
#: () -> StringLiteral
|
|
198
|
+
def parse_string_literal
|
|
199
|
+
raise "not implemented"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
#: () -> String
|
|
203
|
+
def peek_tag_name
|
|
204
|
+
token = current
|
|
205
|
+
token = peek if token.first == :token_wc
|
|
206
|
+
|
|
207
|
+
unless token.first == :token_tag_name
|
|
208
|
+
raise TemplateSyntaxError.new(
|
|
209
|
+
"missing tag name",
|
|
210
|
+
token,
|
|
211
|
+
@source,
|
|
212
|
+
@template_name
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
Luoma.get_token_value(token, @source)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
#: () -> String?
|
|
220
|
+
def peek_whitespace_control
|
|
221
|
+
token = peek
|
|
222
|
+
Luoma.get_token_value(token, @source) if token.first == :token_wc
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
#: () -> void
|
|
226
|
+
def skip_whitespace_control
|
|
227
|
+
@pos += 1 if kind == :token_wc
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Return `true` if we're at the start of a tag named `name`.
|
|
231
|
+
#: (String) -> bool
|
|
232
|
+
def tag?(name)
|
|
233
|
+
token = peek
|
|
234
|
+
token = peek(2) if token.first == :token_wc
|
|
235
|
+
|
|
236
|
+
token.first == :token_tag_name && Luoma.get_token_value(token, @source) == name
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Return a tag name if we're at the start of a tag and that tag's name is
|
|
240
|
+
# in `names`.
|
|
241
|
+
#: (Set[String]) -> String?
|
|
242
|
+
def tags(names)
|
|
243
|
+
token = peek
|
|
244
|
+
token = peek(2) if token.first == :token_wc
|
|
245
|
+
|
|
246
|
+
return unless token.first == :token_tag_name
|
|
247
|
+
|
|
248
|
+
name = Luoma.get_token_value(token, @source)
|
|
249
|
+
name if names.include?(name)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|