cadenza 0.7.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|