cadenza 0.7.0.rc1
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.
- data/lib/cadenza.rb +57 -0
- data/lib/cadenza/base_renderer.rb +24 -0
- data/lib/cadenza/blocks/standard_blocks.rb +9 -0
- data/lib/cadenza/context.rb +192 -0
- data/lib/cadenza/filesystem_loader.rb +28 -0
- data/lib/cadenza/filters/standard_filters.rb +97 -0
- data/lib/cadenza/functional_variables/standard_functional_variables.rb +14 -0
- data/lib/cadenza/lexer.rb +191 -0
- data/lib/cadenza/nodes/block_node.rb +20 -0
- data/lib/cadenza/nodes/boolean_inverse_node.rb +17 -0
- data/lib/cadenza/nodes/constant_node.rb +21 -0
- data/lib/cadenza/nodes/document_node.rb +27 -0
- data/lib/cadenza/nodes/filter_node.rb +25 -0
- data/lib/cadenza/nodes/for_node.rb +37 -0
- data/lib/cadenza/nodes/generic_block_node.rb +21 -0
- data/lib/cadenza/nodes/if_node.rb +42 -0
- data/lib/cadenza/nodes/inject_node.rb +34 -0
- data/lib/cadenza/nodes/operation_node.rb +66 -0
- data/lib/cadenza/nodes/text_node.rb +17 -0
- data/lib/cadenza/nodes/variable_node.rb +21 -0
- data/lib/cadenza/parser.rb +942 -0
- data/lib/cadenza/text_renderer.rb +90 -0
- data/lib/cadenza/token.rb +6 -0
- data/lib/cadenza/version.rb +11 -0
- metadata +158 -0
data/lib/cadenza.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'cadenza/token'
|
2
|
+
require 'cadenza/lexer'
|
3
|
+
require 'cadenza/parser'
|
4
|
+
require 'cadenza/context'
|
5
|
+
require 'cadenza/base_renderer'
|
6
|
+
require 'cadenza/text_renderer'
|
7
|
+
require 'cadenza/filesystem_loader'
|
8
|
+
require 'cadenza/version'
|
9
|
+
|
10
|
+
require 'stringio'
|
11
|
+
|
12
|
+
# require all nodes
|
13
|
+
Dir[File.join File.dirname(__FILE__), 'cadenza', 'nodes', '*.rb'].each {|f| require f }
|
14
|
+
|
15
|
+
module Cadenza
|
16
|
+
BaseContext = Context.new
|
17
|
+
|
18
|
+
def self.render(template_text, scope=nil)
|
19
|
+
template = Parser.new.parse(template_text)
|
20
|
+
|
21
|
+
context = BaseContext.clone
|
22
|
+
|
23
|
+
context.push(scope) if scope
|
24
|
+
|
25
|
+
output = StringIO.new
|
26
|
+
|
27
|
+
TextRenderer.new(output).render(template, context)
|
28
|
+
|
29
|
+
output.string
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.render_template(template_name, scope=nil)
|
33
|
+
context = BaseContext.clone
|
34
|
+
|
35
|
+
context.push(scope) if scope
|
36
|
+
|
37
|
+
template = context.load_template(template_name)
|
38
|
+
|
39
|
+
output = StringIO.new
|
40
|
+
|
41
|
+
TextRenderer.new(output).render(template, context)
|
42
|
+
|
43
|
+
output.string
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Dir[File.join File.dirname(__FILE__), 'cadenza', 'filters', '*.rb'].each do |filename|
|
48
|
+
Cadenza::BaseContext.instance_eval File.read(filename)
|
49
|
+
end
|
50
|
+
|
51
|
+
Dir[File.join File.dirname(__FILE__), 'cadenza', 'functional_variables', '*.rb'].each do |filename|
|
52
|
+
Cadenza::BaseContext.instance_eval File.read(filename)
|
53
|
+
end
|
54
|
+
|
55
|
+
Dir[File.join File.dirname(__FILE__), 'cadenza', 'blocks', '*.rb'].each do |filename|
|
56
|
+
Cadenza::BaseContext.instance_eval File.read(filename)
|
57
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cadenza
|
2
|
+
class BaseRenderer
|
3
|
+
attr_reader :output, :document
|
4
|
+
|
5
|
+
def initialize(output_io)
|
6
|
+
@output = output_io
|
7
|
+
end
|
8
|
+
|
9
|
+
def render(node, context, blocks={})
|
10
|
+
@document ||= node
|
11
|
+
|
12
|
+
node_type = node.class.name.split("::").last
|
13
|
+
|
14
|
+
node_name = underscore(node_type).gsub!(/_node$/, '')
|
15
|
+
|
16
|
+
send("render_#{node_name}", node, context, blocks)
|
17
|
+
end
|
18
|
+
|
19
|
+
# very stripped down form of ActiveSupport's underscore method
|
20
|
+
def underscore(word)
|
21
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2').downcase!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
|
2
|
+
module Cadenza
|
3
|
+
class TemplateNotFoundError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class FilterNotDefinedError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class FunctionalVariableNotDefinedError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class BlockNotDefinedError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class Context
|
16
|
+
attr_accessor :stack, :filters, :functional_variables, :blocks, :loaders
|
17
|
+
attr_accessor :whiny_template_loading
|
18
|
+
|
19
|
+
def initialize(initial_scope={})
|
20
|
+
@stack = []
|
21
|
+
@filters = {}
|
22
|
+
@functional_variables = {}
|
23
|
+
@blocks = {}
|
24
|
+
@loaders = []
|
25
|
+
@whiny_template_loading = false
|
26
|
+
|
27
|
+
push initial_scope
|
28
|
+
end
|
29
|
+
|
30
|
+
# creates a new instance of the context with the stack, loaders, filters,
|
31
|
+
# functional variables and blocks shallow copied.
|
32
|
+
def clone
|
33
|
+
copy = super
|
34
|
+
copy.stack = stack.dup
|
35
|
+
copy.loaders = loaders.dup
|
36
|
+
copy.filters = filters.dup
|
37
|
+
copy.functional_variables = functional_variables.dup
|
38
|
+
copy.blocks = blocks.dup
|
39
|
+
|
40
|
+
copy
|
41
|
+
end
|
42
|
+
|
43
|
+
def lookup(identifier)
|
44
|
+
@stack.reverse_each do |scope|
|
45
|
+
value = lookup_identifier(scope, identifier)
|
46
|
+
|
47
|
+
return value unless value.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def assign(identifier, value)
|
54
|
+
@stack.last[identifier.to_sym] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: symbolizing strings is slow so consider symbolizing here to improve
|
58
|
+
# the speed of the lookup method (its more important than push)
|
59
|
+
# TODO: since you can assign with the #assign method then make the scope
|
60
|
+
# variable optional (assigns an empty hash)
|
61
|
+
def push(scope)
|
62
|
+
@stack.push(scope)
|
63
|
+
end
|
64
|
+
|
65
|
+
def pop
|
66
|
+
@stack.pop
|
67
|
+
end
|
68
|
+
|
69
|
+
def define_filter(name, &block)
|
70
|
+
@filters[name.to_sym] = block
|
71
|
+
end
|
72
|
+
|
73
|
+
def evaluate_filter(name, params=[])
|
74
|
+
filter = @filters[name.to_sym]
|
75
|
+
raise FilterNotDefinedError.new("undefined filter '#{name}'") if filter.nil?
|
76
|
+
filter.call(*params)
|
77
|
+
end
|
78
|
+
|
79
|
+
def define_functional_variable(name, &block)
|
80
|
+
@functional_variables[name.to_sym] = block
|
81
|
+
end
|
82
|
+
|
83
|
+
def evaluate_functional_variable(name, params=[])
|
84
|
+
var = @functional_variables[name.to_sym]
|
85
|
+
raise FunctionalVariableNotDefinedError.new("undefined functional variable '#{name}'") if var.nil?
|
86
|
+
var.call([self] + params)
|
87
|
+
end
|
88
|
+
|
89
|
+
def define_block(name, &block)
|
90
|
+
@blocks[name.to_sym] = block
|
91
|
+
end
|
92
|
+
|
93
|
+
def evaluate_block(name, nodes, parameters)
|
94
|
+
block = @blocks[name.to_sym]
|
95
|
+
raise BlockNotDefinedError.new("undefined block '#{name}") if block.nil?
|
96
|
+
block.call(self, nodes, parameters)
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_loader(loader)
|
100
|
+
if loader.is_a?(String)
|
101
|
+
@loaders.push FilesystemLoader.new(loader)
|
102
|
+
else
|
103
|
+
@loaders.push loader
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def clear_loaders
|
108
|
+
@loaders.reject! { true }
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_source(template_name)
|
112
|
+
source = nil
|
113
|
+
|
114
|
+
@loaders.each do |loader|
|
115
|
+
source = loader.load_source(template_name)
|
116
|
+
break if source
|
117
|
+
end
|
118
|
+
|
119
|
+
if source.nil? and whiny_template_loading
|
120
|
+
raise TemplateNotFoundError.new(template_name)
|
121
|
+
else
|
122
|
+
return source
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def load_source!(template_name)
|
127
|
+
load_source(template_name) || raise(TemplateNotFoundError.new(template_name))
|
128
|
+
end
|
129
|
+
|
130
|
+
def load_template(template_name)
|
131
|
+
template = nil
|
132
|
+
|
133
|
+
@loaders.each do |loader|
|
134
|
+
template = loader.load_template(template_name)
|
135
|
+
break if template
|
136
|
+
end
|
137
|
+
|
138
|
+
if template.nil? and whiny_template_loading
|
139
|
+
raise TemplateNotFoundError.new(template_name)
|
140
|
+
else
|
141
|
+
return template
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def load_template!(template_name)
|
146
|
+
load_template(template_name) || raise(TemplateNotFoundError.new(template_name))
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
def lookup_identifier(scope, identifier)
|
151
|
+
if identifier.index('.')
|
152
|
+
lookup_path(scope, identifier.split("."))
|
153
|
+
else
|
154
|
+
lookup_on_scope(scope, identifier)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def lookup_path(scope, path)
|
159
|
+
loop do
|
160
|
+
component = path.shift
|
161
|
+
|
162
|
+
scope = lookup_on_scope(scope, component)
|
163
|
+
|
164
|
+
return scope if path.empty?
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def lookup_on_scope(scope, identifier)
|
169
|
+
sym_identifier = identifier.to_sym
|
170
|
+
|
171
|
+
# allow looking up array indexes with dot notation, example: alphabet.0 => "a"
|
172
|
+
if scope.respond_to?(:[]) and scope.is_a?(Array) and identifier =~ /\d+/
|
173
|
+
return scope[identifier.to_i]
|
174
|
+
end
|
175
|
+
|
176
|
+
# otherwise if it's a hash look up the string or symbolized key
|
177
|
+
if scope.respond_to?(:[]) and scope.is_a?(Hash) and (scope.has_key?(identifier) || scope.has_key?(sym_identifier))
|
178
|
+
return scope[identifier] || scope[sym_identifier]
|
179
|
+
end
|
180
|
+
|
181
|
+
# if the identifier is a callable method then call that
|
182
|
+
return scope.send(sym_identifier) if scope.respond_to?(sym_identifier)
|
183
|
+
|
184
|
+
# if a functional variable is defined matching the identifier name then return that
|
185
|
+
return @functional_variables[sym_identifier] if @functional_variables.has_key?(sym_identifier)
|
186
|
+
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module Cadenza
|
3
|
+
class FilesystemLoader
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_source(template)
|
11
|
+
filename = File.join(path, template)
|
12
|
+
|
13
|
+
return unless File.file?(filename)
|
14
|
+
|
15
|
+
File.read filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_template(template)
|
19
|
+
source = load_source(template)
|
20
|
+
|
21
|
+
if source
|
22
|
+
return Cadenza::Parser.new.parse(source)
|
23
|
+
else
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
# adds slashes to \, ', and " characters in the given string
|
4
|
+
define_filter :addslashes do |string|
|
5
|
+
word = string.dup
|
6
|
+
word.gsub!(/\\/, "\\\\\\\\")
|
7
|
+
word.gsub!(/'/, "\\\\'")
|
8
|
+
word.gsub!(/"/, "\\\"")
|
9
|
+
word
|
10
|
+
end
|
11
|
+
|
12
|
+
# capitalizes the first letter of the string
|
13
|
+
define_filter :capitalize, &:capitalize
|
14
|
+
|
15
|
+
# centers the string in a fixed width field with the given padding
|
16
|
+
define_filter :center do |string, length, *args|
|
17
|
+
padding = args.first || ' ' # Ruby 1.8.x compatibility
|
18
|
+
string.center(length, padding)
|
19
|
+
end
|
20
|
+
|
21
|
+
# removes all instances of the given string from the string
|
22
|
+
define_filter :cut do |string, value|
|
23
|
+
string.gsub(value, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
# formats the given date object with the given format string
|
27
|
+
define_filter :date do |date, *args|
|
28
|
+
format = args.first || '%F' # Ruby 1.8.x compatibility
|
29
|
+
date.strftime(format)
|
30
|
+
end
|
31
|
+
|
32
|
+
# returns the given value if the input is falsy or is empty
|
33
|
+
define_filter :default do |input, default|
|
34
|
+
if input.respond_to?(:empty?) and input.empty?
|
35
|
+
default
|
36
|
+
else
|
37
|
+
input || default
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# escapes the HTML content of the value
|
42
|
+
define_filter :escape do |input|
|
43
|
+
CGI::escapeHTML(input)
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns the first item of an iterable
|
47
|
+
define_filter :first do |input|
|
48
|
+
if input.respond_to?(:[])
|
49
|
+
RUBY_VERSION =~ /^1.8/ && input.is_a?(String) ? input[0].chr : input[0]
|
50
|
+
else
|
51
|
+
input.first
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# returns the last item of an iterable
|
56
|
+
define_filter :last do |input|
|
57
|
+
if input.respond_to?(:[])
|
58
|
+
RUBY_VERSION =~ /^1.8/ && input.is_a?(String) ? input[-1].chr : input[-1]
|
59
|
+
else
|
60
|
+
input.last
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# glues together elements of the input with the glue string
|
65
|
+
define_filter :join do |input, glue|
|
66
|
+
input.join(glue)
|
67
|
+
end
|
68
|
+
|
69
|
+
# returns the length of the input
|
70
|
+
define_filter :length, &:length
|
71
|
+
|
72
|
+
# returns the string left justified with the given padding character
|
73
|
+
define_filter :ljust do |input, length, *args|
|
74
|
+
padding = args.first || ' ' # Ruby 1.8.x compatibility
|
75
|
+
input.ljust(length, padding)
|
76
|
+
end
|
77
|
+
|
78
|
+
# returns the string right justified with the given padding character
|
79
|
+
define_filter :rjust do |input, length, *args|
|
80
|
+
padding = args.first || ' ' # Ruby 1.8.x compatibility
|
81
|
+
input.rjust(length, padding)
|
82
|
+
end
|
83
|
+
|
84
|
+
# returns the string downcased
|
85
|
+
define_filter :lower, &:downcase
|
86
|
+
|
87
|
+
# returns the string upcased
|
88
|
+
define_filter :upper, &:upcase
|
89
|
+
|
90
|
+
# returns the given words wrapped to fit inside the given column width. Wrapping
|
91
|
+
# is done on word boundaries so that no word cutting is done.
|
92
|
+
# source: http://www.java2s.com/Code/Ruby/String/WordwrappingLinesofText.htm
|
93
|
+
define_filter :wordwrap do |input, length, *args|
|
94
|
+
linefeed = args.first || "\n" # Ruby 1.8.x compatibility
|
95
|
+
input.gsub(/(.{1,#{length}})(\s+|\Z)/, "\\1#{linefeed}")
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
define_functional_variable :load do |context, template|
|
3
|
+
context.load_source(template)
|
4
|
+
end
|
5
|
+
|
6
|
+
define_functional_variable :render do |context, template|
|
7
|
+
template = context.load_template(template)
|
8
|
+
|
9
|
+
if template
|
10
|
+
Cadenza::TextRenderer.render(template, context)
|
11
|
+
else
|
12
|
+
""
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Cadenza
|
4
|
+
|
5
|
+
class Lexer
|
6
|
+
def initialize
|
7
|
+
@line = 0
|
8
|
+
@column = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def source=(source)
|
12
|
+
@scanner = ::StringScanner.new(source || "")
|
13
|
+
|
14
|
+
@line = 1
|
15
|
+
@column = 1
|
16
|
+
|
17
|
+
@context = :body
|
18
|
+
end
|
19
|
+
|
20
|
+
def position
|
21
|
+
[@line, @column]
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_token
|
25
|
+
if @scanner.eos?
|
26
|
+
[false, false]
|
27
|
+
else
|
28
|
+
send("scan_#{@context}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def remaining_tokens
|
33
|
+
result = []
|
34
|
+
|
35
|
+
loop do
|
36
|
+
result.push next_token
|
37
|
+
break if result.last == [false, false]
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
#
|
46
|
+
# Updates the line and column counters based on the given text.
|
47
|
+
#
|
48
|
+
def update_counter(text)
|
49
|
+
number_of_newlines = text.count("\n")
|
50
|
+
|
51
|
+
if number_of_newlines > 0
|
52
|
+
@line += text.count("\n")
|
53
|
+
@column = text.length - text.rindex("\n")
|
54
|
+
else
|
55
|
+
@column += text.length
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Creates and returns a token with the line and column number from the end of
|
61
|
+
# the previous token. Afterwards updates the counter based on the contents
|
62
|
+
# of the text. The value of the token is determined by the text given and
|
63
|
+
# the type of the token.
|
64
|
+
#
|
65
|
+
def token(type, text)
|
66
|
+
value = case type
|
67
|
+
when :INTEGER then text.to_i
|
68
|
+
when :REAL then text.to_f
|
69
|
+
when :STRING then text[1..-2]
|
70
|
+
else text
|
71
|
+
end
|
72
|
+
|
73
|
+
token = Token.new(value, text, @line, @column)
|
74
|
+
|
75
|
+
update_counter(token.source)
|
76
|
+
|
77
|
+
[type, token]
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Scans the next characters based on the body context (the context the lexer
|
82
|
+
# should initially be in), which will attempt to match the opening tokens
|
83
|
+
# for statements. Failing that it will parse a text block token.
|
84
|
+
#
|
85
|
+
def scan_body
|
86
|
+
case
|
87
|
+
when text = @scanner.scan(/\{\{/)
|
88
|
+
@context = :statement
|
89
|
+
token(:VAR_OPEN, text)
|
90
|
+
|
91
|
+
when text = @scanner.scan(/\{%/)
|
92
|
+
@context = :statement
|
93
|
+
token(:STMT_OPEN, text)
|
94
|
+
|
95
|
+
when text = @scanner.scan(/\{#/)
|
96
|
+
# scan until the end of the comment bracket, ignore the text for all
|
97
|
+
# purposes except for advancing the counters appropriately
|
98
|
+
comment = @scanner.scan_until(/#\}/)
|
99
|
+
|
100
|
+
# increment the counters based on the content of the counter
|
101
|
+
update_counter(text + comment)
|
102
|
+
|
103
|
+
# scan in the body context again, since we're not actually returning a
|
104
|
+
# token from the comment. Don't scan if we're at the end of the body,
|
105
|
+
# just return a terminator token.
|
106
|
+
if @scanner.eos?
|
107
|
+
[false, false]
|
108
|
+
else
|
109
|
+
scan_body
|
110
|
+
end
|
111
|
+
|
112
|
+
else
|
113
|
+
# scan ahead until we find a variable opening tag or a block opening tag
|
114
|
+
text = @scanner.scan_until(/\{[\{%#]/)
|
115
|
+
|
116
|
+
# if there was no instance of an opening block then just take what remains
|
117
|
+
# in the scanner otherwise return the pointer to before the block
|
118
|
+
if text
|
119
|
+
text = text[0..-3]
|
120
|
+
@scanner.pos -= 2
|
121
|
+
else
|
122
|
+
text = @scanner.rest
|
123
|
+
@scanner.terminate
|
124
|
+
end
|
125
|
+
|
126
|
+
token(:TEXT_BLOCK, text)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Scans the next characters based on the statement context, which will ignore
|
132
|
+
# whitespace and look for tokens you would expect to find inside any kind
|
133
|
+
# of statement.
|
134
|
+
#
|
135
|
+
def scan_statement
|
136
|
+
# eat any whitespace at the start of the string
|
137
|
+
whitespace = @scanner.scan_until(/\S/)
|
138
|
+
|
139
|
+
if whitespace
|
140
|
+
@scanner.pos -= 1
|
141
|
+
update_counter(whitespace[0..-2])
|
142
|
+
end
|
143
|
+
|
144
|
+
# look for matches
|
145
|
+
case
|
146
|
+
when text = @scanner.scan(/\}\}/)
|
147
|
+
@context = :body
|
148
|
+
token(:VAR_CLOSE, text)
|
149
|
+
|
150
|
+
when text = @scanner.scan(/%\}/)
|
151
|
+
@context = :body
|
152
|
+
token(:STMT_CLOSE, text)
|
153
|
+
|
154
|
+
when text = @scanner.scan(/[=]=/) # i've added the square brackets because syntax highlighters dont like /=
|
155
|
+
token(:OP_EQ, text)
|
156
|
+
|
157
|
+
when text = @scanner.scan(/!=/)
|
158
|
+
token(:OP_NEQ, text)
|
159
|
+
|
160
|
+
when text = @scanner.scan(/>=/)
|
161
|
+
token(:OP_GEQ, text)
|
162
|
+
|
163
|
+
when text = @scanner.scan(/<=/)
|
164
|
+
token(:OP_LEQ, text)
|
165
|
+
|
166
|
+
when text = @scanner.scan(/(if|unless|else|endif|endunless|for|in|endfor|block|endblock|extends|end|and|or|not)[\W]/)
|
167
|
+
keyword = text[0..-2]
|
168
|
+
@scanner.pos -= 1
|
169
|
+
|
170
|
+
token(keyword.upcase.to_sym, keyword)
|
171
|
+
|
172
|
+
when text = @scanner.scan(/[+\-]?[0-9]+\.[0-9]+/)
|
173
|
+
token(:REAL, text)
|
174
|
+
|
175
|
+
when text = @scanner.scan(/[+\-]?[1-9][0-9]*|0/)
|
176
|
+
token(:INTEGER, text)
|
177
|
+
|
178
|
+
when text = @scanner.scan(/['][^']*[']|["][^"]*["]/)
|
179
|
+
token(:STRING, text)
|
180
|
+
|
181
|
+
when text = @scanner.scan(/[A-Za-z_][A-Za-z0-9_\.]*/)
|
182
|
+
token(:IDENTIFIER, text)
|
183
|
+
|
184
|
+
else
|
185
|
+
next_character = @scanner.getch
|
186
|
+
token(next_character, next_character)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|