├── .gitignore ├── LICENSE ├── README.rst ├── dsl1.py ├── dsl2.py ├── dsl3.py ├── modules ├── module1.py ├── module2.py └── module3.py ├── src1.dsl ├── src2.dsl └── src3.dsl /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | .eggs/ 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | *~ 8 | .*.swp 9 | .coverage 10 | .coverage.* 11 | .DS_Store 12 | coverage.xml 13 | coverage-*.xml 14 | build/ 15 | .cache/ 16 | dist/ 17 | .idea/ 18 | distribute-*.tar.gz 19 | env*/ 20 | .tox/ 21 | venv/ 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nathan Jennings 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Writing a Simple DSL in Python 2 | ============================== 3 | 4 | This is the source code for a tutorial I wrote for Dan Bader's blog: 5 | `Writing a Domain Specific Language (DSL) in Python `_. 6 | 7 | Dan's spent a lot of time putting together a ton of very high-quality resources on his 8 | site `dbader.org `_ and 9 | YouTube channel `Python Training by Dan Bader `_. 10 | If you're interested in improving your Python skills, check it out. 11 | 12 | Requirements 13 | ------------ 14 | 15 | - `Python `_ 2 or 3. 16 | 17 | License 18 | ------- 19 | 20 | The MIT License. See `LICENSE `_. 21 | -------------------------------------------------------------------------------- /dsl1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | 4 | # dsl1.py 5 | 6 | import sys 7 | import importlib 8 | 9 | # the source file is the 1st argument to the script 10 | if len(sys.argv) != 2: 11 | print('usage: %s ' % sys.argv[0]) 12 | sys.exit(1) 13 | 14 | sys.path.insert(0, '/Users/nathan/code/dsl/modules') 15 | 16 | with open(sys.argv[1], 'r') as file: 17 | for line in file: 18 | line = line.strip() 19 | if not line or line[0] == '#': 20 | continue 21 | parts = line.split() 22 | print(parts) 23 | mod = importlib.import_module(parts[0]) 24 | print(mod) 25 | getattr(mod, parts[1])(parts[2], parts[3]) 26 | -------------------------------------------------------------------------------- /dsl2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | 4 | # dsl2.py 5 | 6 | import sys 7 | import importlib 8 | 9 | def get_args(dsl_args): 10 | """return args, kwargs""" 11 | args = [] 12 | kwargs = {} 13 | for dsl_arg in dsl_args: 14 | if '=' in dsl_arg: 15 | k, v = dsl_arg.split('=', 1) 16 | kwargs[k] = v 17 | else: 18 | args.append(dsl_arg) 19 | return args, kwargs 20 | 21 | # the source file is the 1st argument to the script 22 | if len(sys.argv) != 2: 23 | print('usage: %s ' % sys.argv[0]) 24 | sys.exit(1) 25 | 26 | sys.path.insert(0, '/Users/nathan/code/dsl/modules') 27 | 28 | with open(sys.argv[1], 'r') as file: 29 | for line in file: 30 | line = line.strip() 31 | if not line or line[0] == '#': 32 | continue 33 | parts = line.split() 34 | mod = importlib.import_module(parts[0]) 35 | args, kwargs = get_args(parts[2:]) 36 | getattr(mod, parts[1])(*args, **kwargs) 37 | -------------------------------------------------------------------------------- /dsl3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | 4 | # dsl3.py 5 | 6 | import sys 7 | import importlib 8 | 9 | def get_args(dsl_args): 10 | """return args, kwargs""" 11 | args = [] 12 | kwargs = {} 13 | for dsl_arg in dsl_args: 14 | if '=' in dsl_arg: 15 | k, v = dsl_arg.split('=', 1) 16 | kwargs[k] = v 17 | else: 18 | args.append(dsl_arg) 19 | return args, kwargs 20 | 21 | def get_help(module_name): 22 | mod = importlib.import_module(module_name) 23 | print(mod.__doc__ or '') 24 | for name in dir(mod): 25 | if not name.startswith('_'): 26 | attr = getattr(mod, name) 27 | print(attr.__name__) 28 | print(attr.__doc__ or '', '\n') 29 | 30 | # the source file is the 1st argument to the script 31 | if len(sys.argv) != 2: 32 | print('usage 1: %s ' % sys.argv[0]) 33 | print('usage 2: %s help=' % sys.argv[0]) 34 | sys.exit(1) 35 | 36 | sys.path.insert(0, '/Users/nathan/code/dsl/modules') 37 | 38 | if sys.argv[1].startswith('help='): 39 | get_help(sys.argv[1][5:]) 40 | else: 41 | with open(sys.argv[1], 'r') as file: 42 | for line in file: 43 | line = line.strip() 44 | if not line or line[0] == '#': 45 | continue 46 | parts = line.split() 47 | mod = importlib.import_module(parts[0]) 48 | args, kwargs = get_args(parts[2:]) 49 | getattr(mod, parts[1])(*args, **kwargs) 50 | -------------------------------------------------------------------------------- /modules/module1.py: -------------------------------------------------------------------------------- 1 | # module1.py 2 | 3 | def add(a, b): 4 | print(int(a) + int(b)) 5 | -------------------------------------------------------------------------------- /modules/module2.py: -------------------------------------------------------------------------------- 1 | # module2.py 2 | 3 | def add_str(*args, **kwargs): 4 | kwargs_list = ['%s=%s' % (k, kwargs[k]) for k in kwargs] 5 | print(''.join(args), ','.join(kwargs_list)) 6 | 7 | def add_num(*args, **kwargs): 8 | t = globals()['__builtins__'][kwargs['type']] 9 | print(sum(map(t, args))) 10 | -------------------------------------------------------------------------------- /modules/module3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module 3. 3 | 4 | A module for adding objects. 5 | """ 6 | 7 | def add_str(*args, **kwargs): 8 | """Concatenates all arguments as strings. Prints results to stdout.""" 9 | kwargs_list = ['%s=%s' % (k, kwargs[k]) for k in kwargs] 10 | print(''.join(args), ','.join(kwargs_list)) 11 | 12 | def add_num(*args, **kwargs): 13 | """Adds all arguments as numbers of type int or float. Prints results to stdout.""" 14 | t = globals()['__builtins__'][kwargs['type']] 15 | print(sum(map(t, args))) 16 | -------------------------------------------------------------------------------- /src1.dsl: -------------------------------------------------------------------------------- 1 | # src1.dsl 2 | 3 | module1 add 1 2 4 | -------------------------------------------------------------------------------- /src2.dsl: -------------------------------------------------------------------------------- 1 | # src2.dsl 2 | 3 | module2 add_str foo bar baz debug=1 trace=0 4 | module2 add_num 1 2 3 type=int 5 | module2 add_num 1 2 3.0 type=float 6 | -------------------------------------------------------------------------------- /src3.dsl: -------------------------------------------------------------------------------- 1 | # src3.dsl 2 | 3 | module2 add_str foo bar baz debug=1 trace=0 4 | module2 add_num 1 2 3 type=int 5 | module2 add_num 1 2 3.0 type=float 6 | --------------------------------------------------------------------------------