Skip to content

Commit 412d601

Browse files
committed
Add 'pythonx/ncm2_lsp_snippet/' from commit '1fe2e9c8dbe601c3129536fbd2ae7dd75bda454b'
git-subtree-dir: pythonx/ncm2_lsp_snippet git-subtree-mainline: 892bded git-subtree-split: 1fe2e9c
2 parents 892bded + 1fe2e9c commit 412d601

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

pythonx/ncm2_lsp_snippet/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

pythonx/ncm2_lsp_snippet/LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright © 2018 roxma@qq.com
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the "Software"),
5+
to deal in the Software without restriction, including without limitation
6+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
and/or sell copies of the Software, and to permit persons to whom the
8+
Software is furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included
11+
in all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
19+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+

pythonx/ncm2_lsp_snippet/parser.py

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import re
2+
import os.path
3+
4+
tabstop_pat1 = re.compile(r'^\$(\d+)')
5+
tabstop_pat2 = re.compile(r'^\$\{(\d+)\}')
6+
7+
placeholder_pat = re.compile(r'^\$\{(\d+):(.*?)\}')
8+
9+
var_pat1 = re.compile(r'^\$([_a-zA-Z][_a-zA-Z0-9]*)')
10+
var_pat2 = re.compile(r'^\$\{([_a-zA-Z][_a-zA-Z0-9]*)\}')
11+
12+
choice_pat = re.compile(r'^\$\{(\d+)\|(.*?[^\\])?\|\}')
13+
14+
15+
class Parser:
16+
17+
def get_elements(self, s, pos):
18+
elements = []
19+
while True:
20+
if len(s) == pos:
21+
break
22+
ele, end = self.get_tabstop(s, pos)
23+
if ele is not None:
24+
elements.append(['tabstop', ele])
25+
pos = end
26+
continue
27+
ele, end = self.get_placeholder(s, pos)
28+
if ele is not None:
29+
elements.append(['placeholder', ele])
30+
pos = end
31+
continue
32+
ele, end = self.get_choice(s, pos)
33+
if ele is not None:
34+
elements.append(['choice', ele])
35+
pos = end
36+
continue
37+
ele, end = self.get_variable(s, pos)
38+
if ele is not None:
39+
elements += ele
40+
pos = end
41+
continue
42+
ele, end = self.get_text(s, pos)
43+
if ele is None:
44+
pos = end
45+
break
46+
pos = end
47+
elements.append(['text', ele])
48+
if elements == []:
49+
return None, pos
50+
return elements, pos
51+
52+
def get_text(self, s, pos, escs=['$', '}', '\\']):
53+
s = s[pos:]
54+
ele = ''
55+
end = pos
56+
while len(s):
57+
esc = s[:2]
58+
if esc in ['\\'+e for e in escs]:
59+
ele += s[1]
60+
end += 2
61+
s = s[2:]
62+
continue
63+
# unexpected unescaped character
64+
if s[0] in escs:
65+
break
66+
end += 1
67+
ele += s[0]
68+
s = s[1:]
69+
if len(ele) == 0:
70+
return None, pos
71+
return ele, end
72+
73+
def get_tabstop(self, s, pos):
74+
m = tabstop_pat1.search(s[pos:])
75+
if m:
76+
return int(m.group(1)), pos + m.end()
77+
m = tabstop_pat2.search(s[pos:])
78+
if m:
79+
return int(m.group(1)), pos + m.end()
80+
return None, pos
81+
82+
def get_placeholder(self, s, pos):
83+
m = placeholder_pat.search(s[pos:])
84+
if not m:
85+
return None, pos
86+
tab = int(m.group(1))
87+
# NOTE specialcase, placeholder with empty text, not sure whether it is
88+
# valid placeholder
89+
if m.group(2) == '':
90+
return [tab, ["text", ""]], pos + m.end()
91+
subeles, pos = self.get_elements(s, pos + m.start(2))
92+
if pos == len(s) or s[pos] != '}':
93+
self.invalid_near(s, pos, "expecting '}' character")
94+
return [tab, subeles], pos + 1
95+
96+
def get_choice(self, s, pos=0):
97+
m = choice_pat.search(s[pos:])
98+
if not m:
99+
return None, pos
100+
tab = int(m.group(1))
101+
# there's no nested opts
102+
end = pos + m.end()
103+
opts = []
104+
# parse opts "one,two,three"
105+
opts_txt = m.group(2) or ""
106+
c_pos = 0
107+
while True:
108+
if c_pos == len(opts_txt):
109+
break
110+
cho, c_end = self.get_text(opts_txt, c_pos, ['$', '}', '\\', ',', '|'])
111+
if cho is None:
112+
self.invalid_near(s, pos + m.start(2) + c_pos,
113+
"get_text failed for choices")
114+
opts.append(cho)
115+
c_pos = c_end
116+
if c_pos == len(opts_txt):
117+
break
118+
if opts_txt[c_pos] != ',':
119+
self.invalid_near(s, pos + m.start(2) +
120+
c_pos, "expecting comma")
121+
c_pos += 1
122+
if c_pos == len(opts_txt):
123+
# FIXME empty choice ?
124+
opts.append('')
125+
break
126+
return opts, end
127+
128+
def invalid_near(self, s, pos, reason):
129+
if pos < len(s):
130+
s = s[:pos] + '>>' + s[pos] + '<<' + s[pos+1:]
131+
raise Exception("encounter invalid syntax: [%s] %s" % (s, reason))
132+
133+
def get_variable(self, s, pos):
134+
# variable: '$' var | '${' var }'
135+
# FIXME These two format is tooo-complicated and not supported
136+
# | '${' var ':' any '}'
137+
# | '${' var '/' regex '/' (format | text)+ '/' options '}'
138+
m = var_pat1.search(s[pos:])
139+
if m:
140+
return [["text", os.path.expandvars(m.group())]], pos + m.end()
141+
m = var_pat2.search(s[pos:])
142+
if m:
143+
return [["text", os.path.expandvars(m.group())]], pos + m.end()
144+
return None, pos
145+
146+
def get_ast(self, snippet):
147+
eles, pos = self.get_elements(snippet, 0)
148+
if pos != len(snippet):
149+
self.invalid_near(snippet, pos, "encounter invalid character")
150+
return eles
151+
152+
153+
154+
# snippet = "hello $123 $HOME fooba"
155+
# snippet = """unshift(${1:newelt})${0}"""
156+
# ast = Parser().get_ast(snippet)
157+
# print(ast)
158+
#
159+
#
160+
# def snipmate_escape(txt):
161+
# txt = txt.replace('$', r'\$')
162+
# txt = txt.replace('{', r'\{')
163+
# txt = txt.replace('}', r'\}')
164+
# txt = txt.replace(':', r'\:')
165+
# return txt
166+
#
167+
# def to_snipmate(ast):
168+
# txt = ''
169+
# for t, ele in ast:
170+
# if t == 'text':
171+
# txt += snipmate_escape(ele)
172+
# elif t == 'tabstop':
173+
# txt += "${%s}" % ele
174+
# elif t == 'placeholder':
175+
# tab, ph = ele
176+
# txt += "${%s:%s}" % (tab, to_snipmate(ph))
177+
# return txt
178+
#
179+
# print(to_snipmate(ast))

0 commit comments

Comments
 (0)