Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = ')' ; |