Skip to content

Commit

Permalink
Added resources and scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
wshayes committed Jul 18, 2018
1 parent 1b092cc commit 02ddc3e
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 0 deletions.
53 changes: 53 additions & 0 deletions bin/ebnf_to_parsers.py-fixme
@@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
######################
TODO Copied here from BEL module - needs to be re-worked to operate in bel_specifications repository
######################
Usage: python yaml_to_ebnf.py <version> <path to output .ebnf file>
Use this script to convert the user defined YAML file to two other files:
- an EBNF file used by Tatsu to compile into a parser (syntax)
"""

import click
import tatsu
import glob
import os

root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))


@click.command()
@click.option('--parser_fn', help='Specify resulting BEL Parser filename')
@click.option('--ebnf_fn', help='Specify EBNF input filename')
def main(ebnf_fn, parser_fn):
"""Create BEL Parser file from EBNF file
If you do not specify any options, then this will process all of the BEL EBNF
files in bel/lang/versions into EBNF files
"""
if ebnf_fn:
files = glob.glob(f'{root_dir}/{ebnf_fn}')
else:
files = glob.glob(f'{root_dir}/bel/lang/versions/bel_v*ebnf')

for fn in files:
if parser_fn is None:
parser_fn = fn.replace('bel_v', 'parser_v').replace('ebnf', 'py')

with open(fn, 'r') as f:
grammar = f.read()

print('Creating Parser:', parser_fn)
parser = tatsu.to_python_sourcecode(grammar, filename=parser_fn)
with open(parser_fn, 'wt') as f:
f.write(parser)


if __name__ == '__main__':
main()
51 changes: 51 additions & 0 deletions bin/make_test_parser.sh
@@ -0,0 +1,51 @@
#!/bin/bash


# non-version specific variables ---------------------------------------------------------------------------------------

EBNF_TEMPLATE_FILE="dev/bel.ebnf.j2"
SCRIPT_TO_TEST_PARSER="dev/parser_testing.py"
YAML_TO_EBNF_SCRIPT="dev/yaml_to_ebnf.py"
MULTIPLE_TEST_STATEMENTS="test/bel2_test_statements.txt"
SINGLE_TEST_STATEMENT="test/bel2_test_statement.txt"


# versions -------------------------------------------------------------------------------------------------------------

VERSIONS=(2_0_0 2_0_1)


# build all versions ---------------------------------------------------------------------------------------------------

for v in "${VERSIONS[@]}"
do

EBNF_SYNTAX_FILE="bel/lang/versions/bel_v$v.ebnf"
PARSER_PY_FILE="bel/lang/versions/parser_v$v.py"
YAML_FILE_NAME="bel/lang/versions/bel_v$v.yaml"

printf "\nBUILDING VERSION %s ********************************************************\n\n" "$v"

if [[ ! -f ${YAML_FILE_NAME} ]] ; then
printf "ERROR: %s does not exist! Exiting.\n" "${YAML_FILE_NAME}"
exit
fi

printf "1. Generating syntax EBNF file from YAML...\n"

python yaml_to_ebnf.py "$YAML_FILE_NAME" "$EBNF_TEMPLATE_FILE" "$EBNF_SYNTAX_FILE"

printf "2. Generated in %s.\n" "$EBNF_SYNTAX_FILE"
printf "3. Generating parser file from syntax EBNF file...\n"

python -m tatsu --generate-parser --outfile "$PARSER_PY_FILE" "$EBNF_SYNTAX_FILE"

printf "4. Generated in %s.\n" "$PARSER_PY_FILE"
printf "5. Complete!\n\n\n"

done


# testing --------------------------------------------------------------------------------------------------------------
# python "$SCRIPT_TO_TEST_PARSER" "$SINGLE_TEST_STATEMENT"
# python "$SCRIPT_TO_TEST_PARSER" "$MULTIPLE_TEST_STATEMENTS"
101 changes: 101 additions & 0 deletions bin/yaml_to_ebnf.py-fixme
@@ -0,0 +1,101 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
######################
TODO Copied here from BEL module - needs to be re-worked to operate in bel_specifications repository
######################
Usage: python yaml_to_ebnf.py <version> <path to output .ebnf file>
Use this script to convert the user defined YAML file to two other files:
- an EBNF file used by Tatsu to compile into a parser (syntax)
"""

import datetime
import logging
import glob
import os.path
import click
from bel.lang.bel_specification import get_specification
from jinja2 import Environment, FileSystemLoader

log = logging.getLogger(__name__)

root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
tmpl_fn = 'bel.ebnf.j2'


def render_ebnf(tmpl_fn, bel_version, created_time, bel_spec):

specs = get_specification(bel_version)

tmpl_dir = os.path.dirname(tmpl_fn)
tmpl_basename = os.path.basename(tmpl_fn)

bel_major_version = bel_version.split('.')[0]

env = Environment(loader=FileSystemLoader(tmpl_dir)) # create environment for template
template = env.get_template(tmpl_basename) # get the template

# replace template placeholders with appropriate variables
functions_list = sorted(specs['functions']['primary']['list_short'] + specs['functions']['primary']['list_long'])
modifiers_list = sorted(specs['functions']['modifier']['list_short'] + specs['functions']['modifier']['list_long'])
ebnf = template.render(functions=sorted(functions_list, key=len, reverse=True),
m_functions=sorted(modifiers_list, key=len, reverse=True),
relations=sorted(specs['relations']['list'], key=len, reverse=True),
bel_version=bel_version,
bel_major_version=bel_major_version,
created_time=created_time)

return ebnf


def get_version(belspec_fn: str) -> str:
"""Recover version number from belspec_fn"""

version = os.path.basename(belspec_fn).replace('bel_v', '').replace('.yaml', '').replace('_', '.')
return version


def save_ebnf(ebnf_fn, ebnf):
with open(ebnf_fn, 'w') as f:
f.write(ebnf)


@click.command()
@click.option('--belspec_fn', help='BEL Language Specification filename')
@click.option('--ebnf_fn', help='Specify EBNF filename')
@click.option('--ebnf_tmpl_fn', default="./bel/lang/versions/bel.ebnf.j2", help='EBNF template filename')
def main(belspec_fn, ebnf_fn, ebnf_tmpl_fn):
"""Create EBNF files from BEL Specification yaml files and template
If you do not specify any options, then this will process all of the BEL Specification
files in bel/lang/versions into EBNF files
"""
created_time = datetime.datetime.now().strftime('%B %d, %Y - %I:%M:%S%p')

if belspec_fn:
bel_version = get_version(belspec_fn)
if not ebnf_fn:
ebnf_fn = belspec_fn.replace('yaml', 'ebnf')
print(f'EBNF output file name is: {ebnf_fn}')

ebnf = render_ebnf(ebnf_tmpl_fn, bel_version, created_time, belspec_fn)
print(ebnf_fn)
save_ebnf(ebnf_fn, ebnf)

else:
files = glob.glob(f'{root_dir}/bel/lang/versions/bel_v*yaml')
for fn in files:
bel_version = get_version(fn)
bel_spec = get_specification(bel_version)
ebnf = render_ebnf(tmpl_fn, bel_version, created_time, bel_spec)
ebnf_fn = fn.replace('yaml', 'ebnf')
save_ebnf(ebnf_fn, ebnf)


if __name__ == '__main__':
main()
63 changes: 63 additions & 0 deletions resources/bel.ebnf.j2
@@ -0,0 +1,63 @@
@@grammar::BEL
@@parseinfo :: True

##############
# METADATA #
##############

# BEL VERSION: {{ bel_version }}
# EBNF CREATION TIMESTAMP: {{ created_time }}

#########################
# GRAMMAR DEFINITIONS #
#########################

start = bel_statement $ ;
bel_statement = subject:function ~ [relation:relation ~ object:obj] ;

obj = (function | enclosed_statement) ;
enclosed_statement = function_open bel_statement:bel_statement function_close ;

##### FUNCTIONS #####

function = function:funcs function_open function_args:f_args function_close ;
funcs =
{% for function in functions %}'{{ function }}'{{ " | " if not loop.last }}{% endfor %} ;

f_args = ','.{(function | modifier_function | namespace_arg | string_arg)}* ;

##### MODIFIER FUNCTIONS #####

modifier_function = modifier:m_funcs function_open modifier_args:m_args function_close ;
m_funcs =
{% for modifier in m_functions %}'{{ modifier }}'{{ " | " if not loop.last }}{% endfor %} ;

m_args = ','.{(function | namespace_arg | string_arg)}* ;

##### RELATIONSHIPS #####

relation = relations ;
relations =
{% for relation in relations %}'{{ relation }}'{{ " | " if not loop.last }}{% endfor %} ;

##### MISCELLANEOUS #####

namespace_arg = ns_arg:full_nsv ;
full_nsv = ns:ns_string ':' ns_value:(quoted_string | string) ;

string_arg = str_arg:full_string ;
full_string = (quoted_string | string) ;



# quoted_string: Matches like any string but requires it to be in quotation marks.
quoted_string = /\"(?:[^"\\]|\\.)*\"/ ;

# string: Matches any char other than space, comma or ')'
string = /[^\s\),]+/ ;

# ns_string: Matches any capital letter or digit.
ns_string = /[A-Z0-9]+/ ;

function_open = '(' ;
function_close = ')' ;

0 comments on commit 02ddc3e

Please sign in to comment.