xipd

programming language for audio processing that compiles to PureData
git clone https://git.ce9e.org/xipd.git

commit
ee5c52c73ecdcdfc002ea5e12bd93f9abbed31f7
parent
efd21a6a6564e2fc336b2dd8fb2094f5ef0af1f5
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2022-08-26 10:13
create parser

Diffstat

A xipd/parser.py 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

1 files changed, 179 insertions, 0 deletions


diff --git a/xipd/parser.py b/xipd/parser.py

@@ -0,0 +1,179 @@
   -1     1 import re
   -1     2 import sys
   -1     3 
   -1     4 
   -1     5 class SyntaxError(TypeError):
   -1     6 	pass
   -1     7 
   -1     8 
   -1     9 class Parser:
   -1    10 	def parse_re(self, s, pattern):
   -1    11 		m = re.match(pattern, s)
   -1    12 		if m:
   -1    13 			head = s[:m.end()]
   -1    14 			tail = s[m.end():]
   -1    15 			return head, tail
   -1    16 		else:
   -1    17 			raise SyntaxError('expected "%s"' % pattern, s)
   -1    18 
   -1    19 	def parse_any(self, s, parsers, parse_to_end=False):
   -1    20 		errors = []
   -1    21 		for parser in parsers:
   -1    22 			try:
   -1    23 				value, s = parser(s)
   -1    24 				if parse_to_end and s:
   -1    25 					raise SyntaxError('tail', s)
   -1    26 				return value, s
   -1    27 			except SyntaxError as e:
   -1    28 				errors.append(e)
   -1    29 				pass
   -1    30 		raise SyntaxError(errors)
   -1    31 
   -1    32 	def parse_list(self, s, parse_item):
   -1    33 		_, s = self.parse_re(s, r'\(')
   -1    34 		items = []
   -1    35 		while not s.startswith(')'):
   -1    36 			if items:
   -1    37 				_, s = self.parse_re(s, r', *')
   -1    38 			item, s = parse_item(s)
   -1    39 			items.append(item)
   -1    40 		_, s = self.parse_re(s, r'\)')
   -1    41 		return items, s
   -1    42 
   -1    43 	def parse_name(self, s):
   -1    44 		return self.parse_re(s, r'[a-zA-Z_][a-zA-Z0-9_]*')
   -1    45 
   -1    46 	def parse_port(self, s):
   -1    47 		port, s = self.parse_re(s, r':[0-9]+')
   -1    48 		return int(port[1:]), s
   -1    49 
   -1    50 	def parse_str(self, s):
   -1    51 		value, s = self.parse_re(s, r'"[^"]*"')
   -1    52 		return ('str', value[1:-1]), s
   -1    53 
   -1    54 	def parse_int(self, s):
   -1    55 		value, s = self.parse_re(s, r'[0-9]+')
   -1    56 		return ('int', int(value)), s
   -1    57 
   -1    58 	def parse_float(self, s):
   -1    59 		value, s = self.parse_re(s, r'[0-9]+(\.[0-9]+)')
   -1    60 		return ('float', float(value)), s
   -1    61 
   -1    62 	def parse_raw(self, s):
   -1    63 		value, s = self.parse_re(s, r'`[^`]*`')
   -1    64 		return ('raw', value[1:-1]), s
   -1    65 
   -1    66 	def parse_ref(self, s):
   -1    67 		name, s = self.parse_name(s)
   -1    68 		if s.startswith(':'):
   -1    69 			port, s = self.parse_port(s)
   -1    70 		else:
   -1    71 			port = None
   -1    72 		return ('ref', name, port), s
   -1    73 
   -1    74 	def parse_connect(self, s):
   -1    75 		left, s = self.parse_expr(s)
   -1    76 		_, s = self.parse_re(s, r' *-> *')
   -1    77 		right, s = self.parse_expr(s)
   -1    78 		return ('connect', left, right), s
   -1    79 
   -1    80 	def parse_expr_no_op(self, s):
   -1    81 		return self.parse_any(s, [
   -1    82 			self.parse_parens,
   -1    83 			self.parse_call,
   -1    84 			self.parse_ref,
   -1    85 			self.parse_str,
   -1    86 			self.parse_float,
   -1    87 			self.parse_int,
   -1    88 			self.parse_raw,
   -1    89 		])
   -1    90 
   -1    91 	def parse_expr(self, s):
   -1    92 		return self.parse_any(s, [
   -1    93 			self.parse_op,
   -1    94 			self.parse_expr_no_op,
   -1    95 		])
   -1    96 
   -1    97 	def parse_parens(self, s):
   -1    98 		_, s = self.parse_re(s, r'\(')
   -1    99 		expr, s = self.parse_expr(s)
   -1   100 		_, s = self.parse_re(s, r'\)')
   -1   101 		return expr, s
   -1   102 
   -1   103 	def parse_op(self, s):
   -1   104 		left, s = self.parse_expr_no_op(s)
   -1   105 		op, s = self.parse_re(s, r' *(\+|-|\*|/)~? *')
   -1   106 		right, s = self.parse_expr(s)
   -1   107 		return ('op', op.strip(), left, right), s
   -1   108 
   -1   109 	def parse_assign(self, s):
   -1   110 		name, s = self.parse_name(s)
   -1   111 		_, s = self.parse_re(s, r' *= *')
   -1   112 		expr, s = self.parse_expr(s)
   -1   113 		return ('assign', name, expr), s
   -1   114 
   -1   115 	def parse_startfunc(self, s):
   -1   116 		name, s = self.parse_name(s)
   -1   117 		args, s = self.parse_list(s, self.parse_name)
   -1   118 		_, s = self.parse_re(s, ' *{')
   -1   119 		return ('startfunc', name, args), s
   -1   120 
   -1   121 	def parse_endfunc(self, s):
   -1   122 		_, s = self.parse_re(s, '}')
   -1   123 		return ('endfunc',), s
   -1   124 
   -1   125 	def parse_call(self, s):
   -1   126 		name, s = self.parse_name(s)
   -1   127 		args, s = self.parse_list(s, self.parse_expr)
   -1   128 		return ('call', name, args), s
   -1   129 
   -1   130 	def parse_include(self, s):
   -1   131 		_, s = self.parse_re(s, r'include *')
   -1   132 		path, s = self.parse_str(s)
   -1   133 		return ('include', path[1]), s
   -1   134 
   -1   135 	def parse_return(self, s):
   -1   136 		_, s = self.parse_re(s, r'return *')
   -1   137 		expr, s = self.parse_expr(s)
   -1   138 		return ('return', expr), s
   -1   139 
   -1   140 	def parse_line(self, s):
   -1   141 		ast, s = self.parse_any(s, [
   -1   142 			self.parse_include,
   -1   143 			self.parse_return,
   -1   144 			self.parse_connect,
   -1   145 			self.parse_assign,
   -1   146 			self.parse_startfunc,
   -1   147 			self.parse_endfunc,
   -1   148 		], parse_to_end=True)
   -1   149 		return ast
   -1   150 
   -1   151 	def parse_file(self, fh):
   -1   152 		stack = [[]]
   -1   153 		for lineno, line in enumerate(fh):
   -1   154 			line = line.strip()
   -1   155 			if not line or line.startswith('#'):
   -1   156 				continue
   -1   157 			parsed = self.parse_line(line)
   -1   158 
   -1   159 			if parsed[0] == 'startfunc':
   -1   160 				stack[-1].append(parsed)
   -1   161 				stack.append([])
   -1   162 			elif parsed[0] == 'endfunc':
   -1   163 				body = stack.pop()
   -1   164 				startfunc = stack[-1].pop()
   -1   165 				stack[-1].append(('func', *startfunc[1:], body))
   -1   166 			else:
   -1   167 				stack[-1].append(parsed)
   -1   168 		if not len(stack) == 1:
   -1   169 			raise SyntaxError('unbalanced blocks')
   -1   170 		return stack[0]
   -1   171 
   -1   172 
   -1   173 if __name__ == '__main__':
   -1   174 	parser = Parser()
   -1   175 	with open(sys.argv[1]) as fh:
   -1   176 		ast = parser.parse_file(fh)
   -1   177 
   -1   178 	from pprint import pprint
   -1   179 	pprint(ast)