Guest User

Untitled

a guest
Dec 14th, 2018
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.23 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. import re
  4. import tatsu
  5. import binascii
  6. from tatsu.exceptions import TatSuException
  7.  
  8. class Node:
  9. def __init__(self, *args, **kwargs):
  10. for index, key in enumerate(self.__slots__):
  11. if key in kwargs:
  12. setattr(self, key, kwargs[key])
  13. elif index < len(args):
  14. setattr(self, key, args[index])
  15. else:
  16. setattr(self, key, None)
  17.  
  18. def __repr__(self):
  19. rv = []
  20.  
  21. for item in dir(self):
  22. if item[0] != '_':
  23. rv.append('%s=%r' % (item, getattr(self, item)))
  24.  
  25. return '%s(%s)' % (type(self).__name__, ', '.join(rv))
  26.  
  27. class Variable(Node):
  28. __slots__ = ('name',)
  29.  
  30. class Word(Node):
  31. __slots__ = ('value',)
  32.  
  33. class Bareword(Word):
  34. pass
  35.  
  36. class DoubleQuoted(Word):
  37. pass
  38.  
  39. class SingleQuoted(Word):
  40. pass
  41.  
  42. class Redirection(Node):
  43. __slots__ = ('fd', 'type', 'file')
  44.  
  45. class CompoundCommand(Node):
  46. pass
  47.  
  48. class ForClause(CompoundCommand):
  49. __slots__ = ('variable', 'sequence', 'command', 'redirections')
  50.  
  51. class WhileClause(CompoundCommand):
  52. __slots__ = ('condition', 'invert', 'command', 'redirections')
  53.  
  54. class CaseClause(CompoundCommand):
  55. __slots__ = ('word', 'conditions', 'redirections')
  56.  
  57. class VariableDeclaration(Node):
  58. __slots__ = ('name', 'value')
  59.  
  60. class CommandSubstitution(Node):
  61. __slots__ = ('command',)
  62.  
  63. class SimpleCommand(Node):
  64. __slots__ = ('variables', 'arguments', 'redirections', 'background')
  65.  
  66. special_words = ['elif', 'else', 'fi', 'esac']
  67.  
  68. class ShellSemantics:
  69. def wspace(self, ast):
  70. pass
  71.  
  72. def command_seperator(self, ast):
  73. pass
  74.  
  75. def action_seperator(self, ast):
  76. pass
  77.  
  78. def bareword(self, ast):
  79. rv = []
  80.  
  81. # If we find a single character, it was extracted by bareword_nonspecial
  82. # so, attach it with the previous character (if any). Otherwise, if it
  83. # is a bareword_nonspecial with no previous character, a variable or a
  84. # command substitution, add it to the list as a seperate element.
  85.  
  86. for i in ast:
  87. if rv and (isinstance(i, str) and isinstance(rv[-1], str)):
  88. rv[-1] += i
  89. else:
  90. rv.append(i)
  91.  
  92. return Bareword(rv)
  93.  
  94. def double_quoted(self, ast):
  95. rv = []
  96. ast = ast[1:-1]
  97.  
  98. # Same reasoning as for bareword (see above) but with
  99. # doublequoted_nonspecial involved here.
  100.  
  101. for i in ast:
  102. if rv and (isinstance(i, str) and isinstance(rv[-1], str)):
  103. rv[-1] += i
  104. else:
  105. rv.append(i)
  106.  
  107. return DoubleQuoted(rv)
  108.  
  109. def literal_quoted(self, ast):
  110. def replace(s):
  111. if s[1][0] == 'x':
  112. return binascii.unhexlify(s[1:]).decode()
  113.  
  114. return {
  115. 'r': '\r', 'n': '\n', 'f': '\f', 't': '\t', 'v': '\v'
  116. }[s[1]]
  117.  
  118. return SingleQuoted(re.sub(r'\\([nrftv]|x[0-9]{2})', replace, ast[2:-1]))
  119.  
  120. def variable_declaration(self, ast):
  121. return VariableDeclaration(**ast)
  122.  
  123. def while_clause(self, ast):
  124. return WhileClause(
  125. condition=ast[1]['command'],
  126. invert=bool(ast[1]['invert']),
  127. command=ast[3]
  128. )
  129.  
  130. def until_clause(self, ast):
  131. return WhileClause(
  132. condition=ast[1]['command'],
  133. invert=(not ast[1]['invert']),
  134. command=ast[3]
  135. )
  136.  
  137. def for_clause(self, ast):
  138. if not 'sequence' in ast:
  139. ast['sequence'] = []
  140.  
  141. return ForClause(**ast)
  142.  
  143. def case_clause(self, ast):
  144. return CaseClause(**ast)
  145.  
  146. def compound_command(self, ast):
  147. ast[0].redirections = ast[1]
  148. return ast
  149.  
  150. def simple_command(self, ast):
  151. arguments = []
  152. variables = []
  153. redirections = []
  154. background = 'background' in ast
  155.  
  156. if 'variables' in ast:
  157. variables.extend(ast['variables'])
  158.  
  159. if 'rest' in ast:
  160. for i in ast['rest']:
  161. if type(i) is Redirection:
  162. redirections.append(i)
  163. else:
  164. arguments.append(i)
  165.  
  166. # This is not handled in the grammar itself.
  167. if isinstance(arguments[0], Word) and arguments[0].value[0] in special_words:
  168. raise TatSuException("syntax error near unexpected token `%s'" % arguments[0].value[0])
  169.  
  170. return SimpleCommand(variables, arguments, redirections, background)
  171.  
  172. def command_substitution(self, ast):
  173. return CommandSubstitution(ast[1])
  174.  
  175. def variable(self, ast):
  176. return Variable(**ast)
  177.  
  178. def redirection(self, ast):
  179. return Redirection(**ast)
  180.  
  181. def single_quoted(self, ast):
  182. return SingleQuoted(ast[1:-1])
  183.  
  184. parser = tatsu.compile(r'''
  185. @@nameguard :: False
  186. @@left_recursion :: False
  187.  
  188. start = (command_seperator).{ command }* $
  189. ;
  190.  
  191. command_seperator = /[ \t]*[;\n][ \t\n]*(?!;)/
  192. ;
  193.  
  194. wspace = /[ \t]+/
  195. ;
  196.  
  197. action_seperator = /[ \t\n]*/
  198. ;
  199.  
  200. command = compound_command
  201. | simple_command
  202. ;
  203.  
  204. simple_command = (
  205. variables:{ variable_declaration [ wspace ] }+
  206. rest:{ ( redirection | word ) [ wspace ] }*
  207. |
  208. rest:{ ( redirection | word ) [ wspace ] }+
  209. ) [ background:'&' ]
  210. ;
  211.  
  212. compound_command = ( while_clause
  213. | until_clause
  214. | if_clause
  215. | for_clause
  216. | case_clause
  217. ) [ wspace ]
  218. { redirection [ wspace ] }*
  219. ;
  220.  
  221. redirection = ( type:'<'
  222. | [ fd:/[0-9]+/ ] type:( '>' | '>>' | '|>' )
  223. ) [ wspace ] file:word
  224. ;
  225.  
  226. while_clause = 'while' ~condition 'do'
  227. commands_until_done
  228. 'done'
  229. ;
  230.  
  231. until_clause = 'until' ~condition 'do'
  232. commands_until_done
  233. 'done'
  234. ;
  235.  
  236. commands_until_done = [ wspace ] { ( ! 'done' command ) command_seperator }+
  237. ;
  238.  
  239. if_clause = 'if' ~condition 'then' action_seperator
  240. commands_until_if
  241. 'fi'
  242. ;
  243.  
  244. elif_clause = 'elif' ~condition 'then' commands_until_if
  245. ;
  246.  
  247. else_clause = 'else' ~commands_until_if
  248. ;
  249.  
  250. commands_until_if = { ( ! ( 'elif' | 'else' | 'fi' )
  251. command [ command_seperator ] )
  252. }+
  253. ;
  254.  
  255. condition = wspace [ invert:'!' wspace ]
  256. command:simple_command
  257. command_seperator
  258. ;
  259.  
  260. for_clause = 'for' ~wspace variable:identifier wspace 'in'
  261. [ wspace sequence:{ word [ wspace ] }* ] command_seperator
  262. 'do' action_seperator
  263. command:commands_until_done
  264. 'done'
  265. ;
  266.  
  267. case_clause = 'case' ~wspace word:word wspace 'in' wspace
  268. conditions:{ case_conditions }+
  269. 'esac'
  270. ;
  271.  
  272. case_conditions = word:word [ wspace ] ')' [ wspace ]
  273. command:{ commands_until_esac }+ [ wspace ]
  274. ';;'
  275. [ wspace ]
  276. ;
  277.  
  278. commands_until_esac = { ( ! 'esac' command [ command_seperator ] ) }+
  279. ;
  280.  
  281. variable_declaration = name:identifier '=' [ value:word ]
  282. ;
  283.  
  284. word = literal_quoted
  285. | single_quoted
  286. | double_quoted
  287. | bareword
  288. ;
  289.  
  290. literal_quoted = /\$'[^']*'/
  291. ;
  292.  
  293. single_quoted = /'[^']*'/
  294. ;
  295.  
  296. double_quoted = '"' { ( command_substitution
  297. | variable
  298. | doublequoted_nonspecial
  299. ) }* '"'
  300. ;
  301.  
  302. command_substitution = '$(' command ')' | '`' command '`'
  303. ;
  304.  
  305. variable = '$' ( name:( identifier | '?')
  306. | '{' name:( identifier | '?' ) '}'
  307. )
  308. ;
  309.  
  310. doublequoted_nonspecial = /(?:\\.|[^\\$`])/
  311. ;
  312.  
  313. identifier = /[a-zA-Z_][a-zA-Z0-9_]*/
  314. ;
  315.  
  316. bareword = { ( command_substitution
  317. | variable
  318. | bareword_nonspecial
  319. ) }+
  320. ;
  321.  
  322. bareword_nonspecial = /(?:\\.|[^'"&\s$()<>`|;#])/
  323. ;
  324. ''')
  325.  
  326. if __name__ == '__main__':
  327. from pprint import pprint
  328. semantics = ShellSemantics()
  329.  
  330. while True:
  331. try:
  332. cmd = input('[root@localhost ~]# ')
  333. if cmd == 'exit':
  334. break
  335.  
  336. pprint(parser.parse(cmd, whitespace='', semantics=semantics))
  337. except KeyboardInterrupt:
  338. print()
  339. except EOFError:
  340. print()
  341. break
  342. except TatSuException as e:
  343. if getattr(e, 'pos', None):
  344. print("-bash: syntax error near unexpected token: `%s'" % cmd[e.pos])
  345. else:
  346. print('-bash: %s' % e.args[0])
Add Comment
Please, Sign In to add comment