jejune 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gemtest +0 -0
- data/History.txt +12 -0
- data/Manifest.txt +39 -0
- data/README.txt +55 -0
- data/Rakefile +75 -0
- data/bin/jjs +173 -0
- data/lib/jejune.rb +78 -0
- data/lib/jejune/boot.rb +107 -0
- data/lib/jejune/constants.rb +105 -0
- data/lib/jejune/data-extension.rb +144 -0
- data/lib/jejune/dependency-scanner.rb +69 -0
- data/lib/jejune/ejjs.rb +178 -0
- data/lib/jejune/errors.rb +53 -0
- data/lib/jejune/grammar.rb +32 -0
- data/lib/jejune/grammar/JavaScript.g +668 -0
- data/lib/jejune/grammar/Jejune.g +1029 -0
- data/lib/jejune/grammar/Jejune.tokens +241 -0
- data/lib/jejune/grammar/lexer.rb +6504 -0
- data/lib/jejune/grammar/parser.rb +17378 -0
- data/lib/jejune/grammar/rakefile +29 -0
- data/lib/jejune/grammar/tree.rb +6737 -0
- data/lib/jejune/input.rb +124 -0
- data/lib/jejune/jstring.rb +163 -0
- data/lib/jejune/lo-fi-lexer.rb +633 -0
- data/lib/jejune/macro.rb +78 -0
- data/lib/jejune/main.rb +289 -0
- data/lib/jejune/manager.rb +333 -0
- data/lib/jejune/node-test.rb +71 -0
- data/lib/jejune/parameters.rb +83 -0
- data/lib/jejune/rewrite-debug.rb +61 -0
- data/lib/jejune/rewrite.rb +125 -0
- data/lib/jejune/scanner.rb +201 -0
- data/lib/jejune/translator.rb +710 -0
- data/lib/jejune/tree-walker.rb +81 -0
- data/lib/jejune/utils.rb +81 -0
- data/lib/jejune/version.rb +38 -0
- data/spec/samples.txt +51 -0
- data/spec/translation.rb +69 -0
- data/spec/utils.rb +63 -0
- data/tools/env.fish +2 -0
- metadata +147 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2010-2011 Kyle C. Yetter
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
|
27
|
+
module Jejune
|
28
|
+
module NodeTest
|
29
|
+
include Constants
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def property_definition?( node )
|
34
|
+
if PROPERTY_DEFINITION_TYPES.include?( node.type )
|
35
|
+
if node.type == ARROW and not PROPERTY_TYPES.include?( node.first.type )
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
def need_parentheses?( type_in_question, base_type )
|
44
|
+
OPERATOR_PRECEDENCE[ type_in_question ] < OPERATOR_PRECEDENCE[ base_type ]
|
45
|
+
end
|
46
|
+
|
47
|
+
def property_name?( node )
|
48
|
+
PROPERTY_TYPES.include?( node.type )
|
49
|
+
end
|
50
|
+
|
51
|
+
def expression?( node )
|
52
|
+
EXPRESSION_TYPES.include?( node.type )
|
53
|
+
end
|
54
|
+
|
55
|
+
def enclosed?( node )
|
56
|
+
if node.start.type == LPAREN and node.stop.type == RPAREN
|
57
|
+
depth = 1
|
58
|
+
for token in node.tokens[ 1...-1 ]
|
59
|
+
case token.type
|
60
|
+
when LPAREN then depth += 1
|
61
|
+
when RPAREN
|
62
|
+
depth == 1 and return( false )
|
63
|
+
depth -= 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# author: Kyle Yetter
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module Jejune
|
9
|
+
|
10
|
+
ParameterSet = Struct.new( :parameters, :defaults, :indices, :splat )
|
11
|
+
|
12
|
+
class ParameterSet
|
13
|
+
include Constants
|
14
|
+
|
15
|
+
def self.study( params )
|
16
|
+
set = self.new
|
17
|
+
|
18
|
+
# Check that the parameters node actually meets the current
|
19
|
+
# semantic requirements of req > def > splat
|
20
|
+
# Also, organize the actual parameters by type
|
21
|
+
last_type = ID
|
22
|
+
for node in params
|
23
|
+
if last_type == SPLAT
|
24
|
+
raise( "additional parameters may not follow a splat parameter" )
|
25
|
+
end
|
26
|
+
|
27
|
+
case type = node.type
|
28
|
+
when ID
|
29
|
+
last_type == ASGN and
|
30
|
+
raise( "required parameters may not follow default parameters" )
|
31
|
+
set.add( node.source )
|
32
|
+
when ASGN
|
33
|
+
set.add( node.first.source, node.last.source )
|
34
|
+
when SPLAT
|
35
|
+
set.splat = node.first.source
|
36
|
+
else
|
37
|
+
raise( "Why is `#{ node.name }' in a parameters list? That shouldn't happen" )
|
38
|
+
end
|
39
|
+
|
40
|
+
last_type = type
|
41
|
+
end
|
42
|
+
|
43
|
+
return set
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize( parameters = [], defaults = {}, indices = {}, splat = nil )
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def declaration
|
51
|
+
parameters.empty? ? '()' : '( ' << parameters.join( ', ' ) << ' )'
|
52
|
+
end
|
53
|
+
|
54
|
+
def add( name, value = nil )
|
55
|
+
indices[ name ] = parameters.length
|
56
|
+
parameters << name
|
57
|
+
value and defaults[ name ] = value
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def include?( name )
|
62
|
+
indices[ name ]
|
63
|
+
end
|
64
|
+
|
65
|
+
def parsing_source
|
66
|
+
arity = parameters.length
|
67
|
+
source = StringIO.new( '' )
|
68
|
+
if splat
|
69
|
+
source.puts( "var #{ splat } = Array.prototype.slice.call( arguments, #{ arity } );" )
|
70
|
+
end
|
71
|
+
unless defaults.empty?
|
72
|
+
source.puts( "switch ( arguments.length ) {" )
|
73
|
+
parameters.each_with_index do | name, i |
|
74
|
+
source.puts( " case #{ i }:" )
|
75
|
+
value = defaults[ name ] and source.puts( " #{ name } = #{ value };" )
|
76
|
+
end
|
77
|
+
source.puts( "}" )
|
78
|
+
end
|
79
|
+
return source.string
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2010-2011 Kyle C. Yetter
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
module Jejune
|
27
|
+
module DebugRewrite
|
28
|
+
def debug_rewrite( tokens )
|
29
|
+
rewrite = tokens.rewrite
|
30
|
+
|
31
|
+
operations = rewrite.instance_variable_get( :@operations )
|
32
|
+
for op in operations
|
33
|
+
print_rewrite( op )
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def print_rewrite( operation )
|
38
|
+
tokens = operation.stream
|
39
|
+
|
40
|
+
case operation.name
|
41
|
+
when 'insert-before'
|
42
|
+
print(
|
43
|
+
"\e[33minsert-before\e[0m : ",
|
44
|
+
operation.stream[ operation.location ].inspect,
|
45
|
+
' -> ', operation.text.inspect
|
46
|
+
)
|
47
|
+
when 'replace'
|
48
|
+
range = operation.location
|
49
|
+
start, stop = range.begin, range.end
|
50
|
+
content = '%p %s %p' % [ tokens[ start ], range.exclude_end? ? '...' : '..', tokens[ stop ] ]
|
51
|
+
print( "\e[32mreplace\e[0m : ", content, ' -> ', operation.text.inspect )
|
52
|
+
when 'delete'
|
53
|
+
range = operation.location
|
54
|
+
start, stop = range.begin, range.end
|
55
|
+
content = '%p %s %p' % [ tokens[ start ], range.exclude_end? ? '...' : '..', tokens[ stop ] ]
|
56
|
+
print( "\e[34mdelete\e[0m : ", content )
|
57
|
+
end
|
58
|
+
puts
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2010-2011 Kyle C. Yetter
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
module Jejune
|
27
|
+
class RewriteTree < CommonTree
|
28
|
+
attr_accessor :token_stream
|
29
|
+
|
30
|
+
def initialize( payload = nil, stream = nil )
|
31
|
+
super( payload )
|
32
|
+
@token_stream = stream
|
33
|
+
end
|
34
|
+
|
35
|
+
def tokens( range = token_range )
|
36
|
+
@token_stream[ range ]
|
37
|
+
end
|
38
|
+
|
39
|
+
def rewrite( *args, &block )
|
40
|
+
@token_stream.rewrite( *args, &block )
|
41
|
+
end
|
42
|
+
|
43
|
+
def start
|
44
|
+
start_index > 0 and @token_stream.at( start_index )
|
45
|
+
end
|
46
|
+
|
47
|
+
def stop
|
48
|
+
stop_index > 0 and @token_stream.at( stop_index )
|
49
|
+
end
|
50
|
+
|
51
|
+
def source( range = token_range )
|
52
|
+
@token_stream.rewrite.execute( range )
|
53
|
+
end
|
54
|
+
|
55
|
+
def inner_range
|
56
|
+
first.start_index .. last.stop_index
|
57
|
+
end
|
58
|
+
|
59
|
+
def inner_source
|
60
|
+
@token_stream.rewrite.execute( inner_range )
|
61
|
+
end
|
62
|
+
|
63
|
+
def insert_after!( text, *more )
|
64
|
+
if more.empty?
|
65
|
+
@token_stream.insert_after( stop_index, text )
|
66
|
+
else
|
67
|
+
@token_stream.insert_after( text, *more )
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def insert_before!( text, *more )
|
72
|
+
if more.empty?
|
73
|
+
@token_stream.insert_before( start_index, text )
|
74
|
+
else
|
75
|
+
@token_stream.insert_before( text, *more )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def delete!( *more )
|
80
|
+
if more.empty?
|
81
|
+
@token_stream.delete( token_range )
|
82
|
+
else
|
83
|
+
@token_stream.delete( *more )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def replace!( text, *more )
|
88
|
+
if more.empty?
|
89
|
+
@token_stream.replace( token_range, text )
|
90
|
+
else
|
91
|
+
@token_stream.replace( text, *more )
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def strip!
|
96
|
+
@token_stream.delete( start_index )
|
97
|
+
@token_stream.delete( stop_index )
|
98
|
+
end
|
99
|
+
|
100
|
+
def surround!( before, after = before )
|
101
|
+
@token_stream.insert_before( start_index, before )
|
102
|
+
@token_stream.insert_after( stop_index, after )
|
103
|
+
end
|
104
|
+
|
105
|
+
alias prepend! insert_before!
|
106
|
+
alias append! insert_after!
|
107
|
+
|
108
|
+
def source_name
|
109
|
+
token.source_name rescue nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class RewriteAdaptor < CommonTreeAdaptor
|
114
|
+
def initialize( stream )
|
115
|
+
@token_stream = stream
|
116
|
+
super( TokenData::Token )
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_with_payload( payload )
|
120
|
+
return RewriteTree.new( payload, @token_stream )
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
@@ -0,0 +1,201 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2010-2011 Kyle C. Yetter
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
require 'strscan'
|
27
|
+
require 'delegate'
|
28
|
+
|
29
|
+
module Jejune
|
30
|
+
class SourceScanner < DelegateClass( StringScanner )
|
31
|
+
ESCAPE = /\\./m
|
32
|
+
SKIPPABLE = %r(
|
33
|
+
(?: // [^\n]* \n?
|
34
|
+
| /\* .*? \*/
|
35
|
+
| [ \ \t \f ]+
|
36
|
+
| \\ \r? \n
|
37
|
+
)+
|
38
|
+
)mx
|
39
|
+
SKIPPABLE_NL = %r(
|
40
|
+
(?: // [^\n]* \n?
|
41
|
+
| /\* .*? \*/
|
42
|
+
| \s+
|
43
|
+
)+
|
44
|
+
)mx
|
45
|
+
ID_RX = %r( [\$_a-z] [\$_a-z0-9]* )ix
|
46
|
+
STRING_RX = %r(
|
47
|
+
(?: " (?: [^ " \\ ] | \\ . )* "
|
48
|
+
| ' (?: [^ ' \\ ] | \\ . )* '
|
49
|
+
| ` (?: [^ ` \\ ] | \\ . )* `
|
50
|
+
)
|
51
|
+
)mx
|
52
|
+
CHUNK_RX = %r(
|
53
|
+
(?: [^ \s \\ ] | \\ . )+
|
54
|
+
)mx
|
55
|
+
|
56
|
+
attr_accessor :line, :column, :file
|
57
|
+
alias position pos
|
58
|
+
alias position= pos=
|
59
|
+
|
60
|
+
def self.from_token( token, options = {} )
|
61
|
+
options.is_a?( Integer ) and options = { :position => options }
|
62
|
+
source = token.text
|
63
|
+
options[ :file ] ||= token.source_name
|
64
|
+
options[ :line ] ||= token.line
|
65
|
+
options[ :column ] ||= token.column
|
66
|
+
new( source, options )
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize( source, options = {} )
|
70
|
+
@scanner = StringScanner.new( source )
|
71
|
+
super( @scanner )
|
72
|
+
|
73
|
+
@file = options.fetch( :file, nil )
|
74
|
+
@line = options.fetch( :line, 1 )
|
75
|
+
@column = options.fetch( :column, 0 )
|
76
|
+
@lexer = @stream = nil
|
77
|
+
if pos = options[ :position ] and pos > 0
|
78
|
+
@scanner.pos = pos
|
79
|
+
advance_over( source[ 0, pos ] )
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
for m in %w( scan scan_until skip skip_until )
|
84
|
+
class_eval( <<-END )
|
85
|
+
def #{ m }( pattern )
|
86
|
+
if match = super( pattern )
|
87
|
+
advance_over( match )
|
88
|
+
return match
|
89
|
+
end
|
90
|
+
end
|
91
|
+
END
|
92
|
+
end
|
93
|
+
|
94
|
+
def advance_over( str )
|
95
|
+
l = str.count( $/ )
|
96
|
+
if l.zero?
|
97
|
+
@column += str.length
|
98
|
+
else
|
99
|
+
@line += l
|
100
|
+
@column = str.length - str.rindex( $/ ) - 1
|
101
|
+
end
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def stream
|
106
|
+
@stream ||= ANTLR3::StringStream.new(
|
107
|
+
@scanner.string,
|
108
|
+
:position => @scanner.pos,
|
109
|
+
:file => @file,
|
110
|
+
:line => @line,
|
111
|
+
:column => @column
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
def lexer
|
116
|
+
input = self.stream
|
117
|
+
input.position == @scanner.pos or input.seek( @scanner.pos )
|
118
|
+
lexer = Lexer.new( input )
|
119
|
+
block_given? or return( lexer )
|
120
|
+
|
121
|
+
value = yield( lexer )
|
122
|
+
@scanner.pos = @stream.position
|
123
|
+
@line = @stream.line
|
124
|
+
@column = @stream.column
|
125
|
+
return value
|
126
|
+
end
|
127
|
+
|
128
|
+
def jump_to( pattern )
|
129
|
+
if found = @scanner.check_until( pattern )
|
130
|
+
size = found.length - @scanner.matched.length
|
131
|
+
size > 0 or return ''
|
132
|
+
matched = found[ 0, size ]
|
133
|
+
@scanner.pos += size
|
134
|
+
advance_over( matched )
|
135
|
+
return matched
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def see?( str )
|
140
|
+
peek( str.length ) == str
|
141
|
+
end
|
142
|
+
|
143
|
+
def space!( include_newlines = false )
|
144
|
+
scan( include_newlines ? SKIPPABLE_NL : SKIPPABLE )
|
145
|
+
end
|
146
|
+
|
147
|
+
def id!
|
148
|
+
scan( ID_RX )
|
149
|
+
end
|
150
|
+
|
151
|
+
@@delimited_rx = {}
|
152
|
+
|
153
|
+
def string!
|
154
|
+
scan( STRING_RX )
|
155
|
+
end
|
156
|
+
|
157
|
+
def chunk!
|
158
|
+
scan( CHUNK_RX )
|
159
|
+
end
|
160
|
+
|
161
|
+
def delimited!( open, close = open )
|
162
|
+
c = Regexp.escape( close[ 0, 1 ] )
|
163
|
+
pattern = /#{ cast_rx( open ) }(?m-ix:[^\\#{ c }]|\\.)*#{ cast_rx( close ) }/
|
164
|
+
scan( pattern )
|
165
|
+
end
|
166
|
+
|
167
|
+
def nested!( open, close )
|
168
|
+
start = @scanner.pos
|
169
|
+
@scanner.scan( cast_rx( open ) ) or return nil
|
170
|
+
depth = 1
|
171
|
+
pause = Regexp.union( open, close, /\\.|\z/m )
|
172
|
+
while @scanner.skip_until( pause )
|
173
|
+
case @scanner.matched
|
174
|
+
when open
|
175
|
+
depth += 1
|
176
|
+
when close
|
177
|
+
depth -= 1
|
178
|
+
depth.zero? and break
|
179
|
+
when /\\./m
|
180
|
+
# do nothing
|
181
|
+
else
|
182
|
+
@scanner.pos = start
|
183
|
+
return nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
text = @scanner.string[ start ... @scanner.pos ]
|
187
|
+
advance_over( text )
|
188
|
+
return text
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def cast_rx( arg )
|
194
|
+
case arg
|
195
|
+
when Regexp then arg
|
196
|
+
else Regexp.new( Regexp.escape( arg ) )
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|