Coverage for C:\leo.repo\leo-editor\leo\plugins\importers\php.py: 79%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#@+leo-ver=5-thin
2#@+node:ekr.20140723122936.18148: * @file ../plugins/importers/php.py
3"""The @auto importer for the php language."""
4import re
5from typing import Any, Dict, List
6from leo.core import leoGlobals as g
7from leo.plugins.importers import linescanner
8Importer = linescanner.Importer
9#@+others
10#@+node:ekr.20161129213243.2: ** class Php_Importer
11class Php_Importer(Importer):
12 """The importer for the php lanuage."""
14 def __init__(self, importCommands, **kwargs):
15 """Php_Importer.__init__"""
16 super().__init__(
17 importCommands,
18 language='php',
19 state_class=Php_ScanState,
20 strict=False,
21 )
22 self.here_doc_pattern = re.compile(r'<<<\s*([\w_]+)')
23 self.here_doc_target = None
25 #@+others
26 #@+node:ekr.20161129213243.4: *3* php_i.clean_headline
27 def clean_headline(self, s, p=None):
28 """Return a cleaned up headline s."""
29 return s.rstrip('{').strip()
30 #@+node:ekr.20161129213808.1: *3* php_i.get_new_dict
31 #@@nobeautify
33 def get_new_dict(self, context):
34 """
35 Return a *general* state dictionary for the given context.
36 Subclasses may override...
37 """
38 trace = 'importers' in g.app.debug
39 comment, block1, block2 = self.single_comment, self.block1, self.block2
41 def add_key(d, key, data):
42 aList = d.get(key,[])
43 aList.append(data)
44 d[key] = aList
46 d: Dict[str, List[Any]]
48 if context:
49 d = {
50 # key kind pattern ends?
51 '\\': [('len+1', '\\', None),],
52 '"': [('len', '"', context == '"'),],
53 "'": [('len', "'", context == "'"),],
55 }
56 if block1 and block2:
57 add_key(d, block2[0], ('len', block2, True)) # #1717.
58 else:
59 # Not in any context.
60 d = {
61 # key kind pattern new-ctx deltas
62 '\\':[('len+1', '\\', context, None),],
63 '<': [('<<<', '<<<', '<<<', None),],
64 '"': [('len', '"', '"', None),],
65 "'": [('len', "'", "'", None),],
66 '{': [('len', '{', context, (1,0,0)),],
67 '}': [('len', '}', context, (-1,0,0)),],
68 '(': [('len', '(', context, (0,1,0)),],
69 ')': [('len', ')', context, (0,-1,0)),],
70 '[': [('len', '[', context, (0,0,1)),],
71 ']': [('len', ']', context, (0,0,-1)),],
72 }
73 if comment:
74 add_key(d, comment[0], ('all', comment, '', None))
75 if block1 and block2:
76 add_key(d, block1[0], ('len', block1, block1, None))
77 if trace:
78 g.trace(f"{comment!r} {block1!r} {block2!r}")
79 g.printObj(d, tag=f"scan table for context {context!r}")
80 return d
81 #@+node:ekr.20161129214803.1: *3* php_i.scan_dict (supports here docs)
82 def scan_dict(self, context, i, s, d):
83 """
84 i.scan_dict: Scan at position i of s with the give context and dict.
85 Return the 6-tuple: (new_context, i, delta_c, delta_p, delta_s, bs_nl)
86 """
87 found = False
88 delta_c = delta_p = delta_s = 0
89 if self.here_doc_target:
90 assert i == 0, repr(i)
91 n = len(self.here_doc_target)
92 if self.here_doc_target.lower() == s[:n].lower():
93 self.here_doc_target = None
94 i = n
95 return '', i, 0, 0, 0, False
96 # Skip the rest of the line
97 return '', len(s), 0, 0, 0, False
98 ch = s[i] # For traces.
99 aList = d.get(ch)
100 if aList and context:
101 # In context.
102 for data in aList:
103 kind, pattern, ends = data
104 if self.match(s, i, pattern):
105 if ends is None:
106 found = True
107 new_context = context
108 break
109 elif ends:
110 found = True
111 new_context = ''
112 break
113 else:
114 pass # Ignore this match.
115 elif aList:
116 # Not in context.
117 for data in aList:
118 kind, pattern, new_context, deltas = data
119 if self.match(s, i, pattern):
120 found = True
121 if deltas:
122 delta_c, delta_p, delta_s = deltas
123 break
124 if found:
125 if kind == 'all':
126 i = len(s)
127 elif kind == 'len+1':
128 i += (len(pattern) + 1)
129 elif kind == '<<<': # Special flag for here docs.
130 new_context = context # here_doc_target is a another kind of context.
131 m = self.here_doc_pattern.match(s[i:])
132 if m:
133 i = len(s) # Skip the rest of the line.
134 self.here_doc_target = '%s;' % m.group(1)
135 else:
136 i += 3
137 else:
138 assert kind == 'len', (kind, self.name)
139 i += len(pattern)
140 bs_nl = pattern == '\\\n'
141 return new_context, i, delta_c, delta_p, delta_s, bs_nl
142 #
143 # No match: stay in present state. All deltas are zero.
144 new_context = context
145 return new_context, i + 1, 0, 0, 0, False
146 #@+node:ekr.20161130044051.1: *3* php_i.skip_heredoc_string (not used)
147 # EKR: This is Dave Hein's heredoc code from the old PHP scanner.
148 # I have included it for reference in case heredoc problems arise.
149 #
150 # php_i.scan dict uses r'<<<\s*([\w_]+)' instead of the more complex pattern below.
151 # This is likely good enough. Importers can assume that code is well formed.
153 def skip_heredoc_string(self, s, i):
154 #@+<< skip_heredoc docstrig >>
155 #@+node:ekr.20161130044051.2: *4* << skip_heredoc docstrig >>
156 #@@nocolor-node
157 """
158 08-SEP-2002 DTHEIN: added function skip_heredoc_string
159 A heredoc string in PHP looks like:
161 <<<EOS
162 This is my string.
163 It is mine. I own it.
164 No one else has it.
165 EOS
167 It begins with <<< plus a token (naming same as PHP variable names).
168 It ends with the token on a line by itself (must start in first position.
169 """
170 #@-<< skip_heredoc docstrig >>
171 j = i
172 assert g.match(s, i, "<<<")
173 m = re.match(r"\<\<\<([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)", s[i:])
174 if m is None:
175 i += 3
176 return i
177 # 14-SEP-2002 DTHEIN: needed to add \n to find word, not just string
178 delim = m.group(1) + '\n'
179 i = g.skip_line(s, i) # 14-SEP-2002 DTHEIN: look after \n, not before
180 n = len(s)
181 while i < n and not g.match(s, i, delim):
182 i = g.skip_line(s, i) # 14-SEP-2002 DTHEIN: move past \n
183 if i >= n:
184 g.scanError("Run on string: " + s[j:i])
185 elif g.match(s, i, delim):
186 i += len(delim)
187 return i
188 #@-others
189#@+node:ekr.20161129213243.6: ** class Php_ScanState
190class Php_ScanState:
191 """A class representing the state of the php line-oriented scan."""
193 def __init__(self, d=None):
194 """Php_ScanState.__init__"""
195 if d:
196 prev = d.get('prev')
197 self.context = prev.context
198 self.curlies = prev.curlies
199 else:
200 self.context = ''
201 self.curlies = 0
203 def __repr__(self):
204 """Php_ScanState.__repr__"""
205 return "Php_ScanState context: %r curlies: %s" % (
206 self.context, self.curlies)
208 __str__ = __repr__
210 #@+others
211 #@+node:ekr.20161129213243.7: *3* php_state.level
212 def level(self):
213 """Php_ScanState.level."""
214 return self.curlies
216 #@+node:ekr.20161129213243.8: *3* php_state.update
217 def update(self, data):
218 """
219 Php_ScanState.update
221 Update the state using the 6-tuple returned by i.scan_line.
222 Return i = data[1]
223 """
224 trace = 'importers' in g.app.debug
225 context, i, delta_c, delta_p, delta_s, bs_nl = data
226 if trace:
227 g.trace(
228 f"context: {context!s} "
229 f"delta_c: {delta_c} "
230 f"delta_s: {delta_s} "
231 f"bs_nl: {bs_nl}")
232 # All ScanState classes must have a context ivar.
233 self.context = context
234 self.curlies += delta_c
235 return i
236 #@-others
237#@-others
238importer_dict = {
239 'func': Php_Importer.do_import(),
240 'extensions': ['.php'],
241}
242#@@language python
243#@@tabwidth -4
244#@-leo