Skip to content

feat: add Markdown intrinsic procedure documentation #231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ exclude_lines =
log.debug
except:
if not PY3K:
def update_m_intrinsics
update_m_intrinsics()

[html]
show_contexts = True
37 changes: 37 additions & 0 deletions .github/workflows/update-intrinsics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
on:
# fire at 00:00 every 7th day of the month
schedule:
- cron: "0 0 */7 * *"
workflow_dispatch:

name: Check M_intrinsics for updates
jobs:
update-intrinsics:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Setup
run: |
python3 -m pip install --upgrade pip
pip install .[dev]

- name: Download M_intrinsics
run: |
git clone https://github.com/urbanjost/M_intrinsics

- name: Update Markdown intrinsics
run: |
python3 -m fortls.intrinsics

- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: update M_intrinsics"
title: Update M_intrinsics
body: |
Auto-generated Pull Request to update M_intrinsics JSON definitions.
branch: docs/update-intrinsics
delete-branch: true
reviewers: gnikit
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ fortls/_version.py

.coverage
coverage.xml

# Ignore M_intrinsics repo
M_intrinsics
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### Added

- Added support for Markdown intrinsics from the M_intrinsics repository
([#215](https://github.com/fortran-lang/fortls/issues/215))
- Added and create a schema for fortls configuration files
([#204](https://github.com/fortran-lang/fortls/issues/204))
- Added dependabot alers for PyPi
Expand Down
File renamed without changes.
File renamed without changes.
201 changes: 201 additions & 0 deletions fortls/intrinsic.procedures.markdown.json

Large diffs are not rendered by default.

269 changes: 184 additions & 85 deletions fortls/intrinsics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import annotations

import glob
import json
import os

Expand All @@ -12,7 +15,7 @@
Variable,
)

none_ast = FortranAST()
intrinsic_ast = FortranAST()
lowercase_intrinsics = False


Expand All @@ -21,17 +24,28 @@ def set_lowercase_intrinsics():
lowercase_intrinsics = True


def intrinsics_case(name: str, args: str):
if lowercase_intrinsics:
return name.lower(), args.lower()
return name, args


class Intrinsic(FortranObj):
def __init__(self, name, type, doc_str=None, args="", parent=None):
self.name = name
self.type = type
self.doc_str = doc_str
self.args = args.replace(" ", "")
def __init__(
self,
name: str,
type: int,
doc_str: str | None = None,
args: str = "",
parent=None,
):
self.name: str = name
self.type: int = type
self.doc_str: str | None = doc_str
self.args: str = args.replace(" ", "")
self.parent = parent
self.file_ast = none_ast
if lowercase_intrinsics:
self.name = self.name.lower()
self.args = self.args.lower()
self.file_ast: FortranAST = intrinsic_ast
self.name, self.args = intrinsics_case(self.name, self.args)

def get_type(self):
return self.type
Expand All @@ -43,8 +57,7 @@ def get_desc(self):
return "KEYWORD"
elif self.type == 15:
return "STATEMENT"
else:
return "INTRINSIC"
return "INTRINSIC"

def get_snippet(self, name_replace=None, drop_arg=-1):
if self.args == "":
Expand Down Expand Up @@ -81,55 +94,50 @@ def get_hover_md(self, long=False):
def is_callable(self):
if self.type == 2:
return True
else:
return False
return False


def load_intrinsics():
def create_int_object(name, json_obj, type):
def create_int_object(name: str, json_obj: dict, type: int):
args = json_obj.get("args", "")
doc_str = json_obj.get("doc")
if lowercase_intrinsics:
name = name.lower()
args = args.lower()
name, args = intrinsics_case(name, args)
return Intrinsic(name, type, doc_str=doc_str, args=args)

def create_object(json_obj, enc_obj=None):
def create_object(json_obj: dict, enc_obj=None):
intrinsic_ast.enc_scope_name = None
if enc_obj is not None:
none_ast.enc_scope_name = enc_obj.FQSN
else:
none_ast.enc_scope_name = None
intrinsic_ast.enc_scope_name = enc_obj.FQSN
keywords = []
keyword_info = {}
if "mods" in json_obj:
keywords, keyword_info = map_keywords(json_obj["mods"])
else:
keywords = []
keyword_info = {}
name = json_obj["name"]
args = json_obj.get("args", "")
if lowercase_intrinsics:
name = name.lower()
args = args.lower()
if json_obj["type"] == 0:
mod_tmp = Module(none_ast, 0, name)
name, args = intrinsics_case(name, args)

if json_obj["type"] == 0: # module, match "type": in JSON files
mod_tmp = Module(intrinsic_ast, 0, name)
if "use" in json_obj:
mod_tmp.add_use(json_obj["use"], 0)
return mod_tmp
elif json_obj["type"] == 1:
return Subroutine(none_ast, 0, name, args=args)
elif json_obj["type"] == 2:
elif json_obj["type"] == 1: # subroutine, match "type": in JSON files
return Subroutine(intrinsic_ast, 0, name, args=args)
elif json_obj["type"] == 2: # function, match "type": in JSON files
return Function(
none_ast,
intrinsic_ast,
0,
name,
args=args,
result_type=json_obj["return"],
keywords=keywords,
# keyword_info=keyword_info,
)
elif json_obj["type"] == 3:
return Variable(none_ast, 0, name, json_obj["desc"], keywords, keyword_info)
elif json_obj["type"] == 4:
return Type(none_ast, 0, name, keywords)
elif json_obj["type"] == 3: # variable, match "type": in JSON files
return Variable(
intrinsic_ast, 0, name, json_obj["desc"], keywords, keyword_info
)
elif json_obj["type"] == 4: # derived type, match "type": in JSON files
return Type(intrinsic_ast, 0, name, keywords)
else:
raise ValueError

Expand All @@ -139,52 +147,118 @@ def add_children(json_obj, fort_obj):
fort_obj.add_child(child_obj)
add_children(child, child_obj)

# Fortran statments taken from Intel Fortran documentation
# (https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/language-reference/a-to-z-reference)
json_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "statements.json"
)
statements = {"var_def": [], "int_stmnts": []}
with open(json_file, encoding="utf-8") as fid:
intrin_file = json.load(fid)
for key in statements:
for name, json_obj in sorted(intrin_file[key].items()):
statements[key].append(create_int_object(name, json_obj, 15))
# Fortran keywords taken from Intel Fortran documentation
# (https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/language-reference/a-to-z-reference)
json_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "keywords.json"
)
keywords = {"var_def": [], "arg": [], "type_mem": [], "vis": [], "param": []}
with open(json_file, encoding="utf-8") as fid:
intrin_file = json.load(fid)
for key in keywords:
for name, json_obj in sorted(intrin_file[key].items()):
keywords[key].append(create_int_object(name, json_obj, 14))
# Definitions taken from gfortran documentation
# (https://gcc.gnu.org/onlinedocs/gfortran/Intrinsic-Procedures.html#Intrinsic-Procedures)
json_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "intrinsic_funs.json"
)
int_funs = []
with open(json_file, encoding="utf-8") as fid:
intrin_file = json.load(fid)
for name, json_obj in sorted(intrin_file.items()):
int_funs.append(create_int_object(name, json_obj, json_obj["type"]))
# Definitions taken from gfortran documentation
# (https://gcc.gnu.org/onlinedocs/gfortran/Intrinsic-Modules.html#Intrinsic-Modules)
# Update OpenACC from here https://www.openacc.org/specification
json_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "intrinsic_mods.json"
)
int_mods = []
with open(json_file, encoding="utf-8") as fid:
intrin_file = json.load(fid)
for key, json_obj in intrin_file.items():
fort_obj = create_object(json_obj)
add_children(json_obj, fort_obj)
int_mods.append(fort_obj)
return statements, keywords, int_funs, int_mods
def load_statements(root: str):
"""Load the statements from the json file.
Fortran statements taken from Intel Fortran documentation
(https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/language-reference/a-to-z-reference)


Parameters
----------
root : str
root location of the json file.

Returns
-------
dict
statements dictionary
"""
json_file = os.path.join(root, "statements.json")
statements = {"var_def": [], "int_stmnts": []}
with open(json_file, encoding="utf-8") as fid:
json_data = json.load(fid)
for key in statements:
for name, json_obj in sorted(json_data[key].items()):
statements[key].append(create_int_object(name, json_obj, 15))
return statements

def load_keywords(root: str):
"""Load the Fortran keywords from the json file.
Fortran statements taken from Intel Fortran documentation
(https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/language-reference/a-to-z-reference)


Parameters
----------
root : str
root location of the json file.

Returns
-------
dict
keywords dictionary
"""
json_file = os.path.join(root, "keywords.json")
keywords = {"var_def": [], "arg": [], "type_mem": [], "vis": [], "param": []}
with open(json_file, encoding="utf-8") as fid:
json_data = json.load(fid)
for key in keywords:
for name, json_obj in sorted(json_data[key].items()):
keywords[key].append(create_int_object(name, json_obj, 14))
return keywords

def load_intrinsic_procedures(root: str):
"""Load Intrinsics procedure definitions, from gfortran
(https://gcc.gnu.org/onlinedocs/gfortran/intrinsic-procedures.html)

Parameters
----------
root : str
root location of the json file.

Returns
-------
dict
intrinsic procedures dictionary
"""
json_file = os.path.join(root, "intrinsic.procedures.markdown.json")
with open(json_file, encoding="utf-8") as f:
md_files = json.load(f)
json_file = os.path.join(root, "intrinsic.procedures.json")
intrinsic_procedures = []
with open(json_file, encoding="utf-8") as f:
json_data = json.load(f)
for name, json_obj in sorted(json_data.items()):
# Replace the plain documentation with the Markdown if available
if name in md_files:
json_obj["doc"] = md_files[name]
intrinsic_procedures.append(
create_int_object(name, json_obj, json_obj["type"])
)
return intrinsic_procedures

def load_intrinsic_modules(root: str):
"""Load Intrinsics procedure definitions, from gfortran
(https://gcc.gnu.org/onlinedocs/gfortran/intrinsic-modules.html)
Update OpenACC from here https://www.openacc.org/specification

Parameters
----------
root : str
root location of the json file.

Returns
-------
dict
intrinsic modules dictionary
"""
json_file = os.path.join(root, "intrinsic.modules.json")
intrinsic_modules = []
with open(json_file, encoding="utf-8") as fid:
intrin_file = json.load(fid)
for key, json_obj in intrin_file.items():
fort_obj = create_object(json_obj)
add_children(json_obj, fort_obj)
intrinsic_modules.append(fort_obj)
return intrinsic_modules

root = os.path.dirname(os.path.abspath(__file__))
statements = load_statements(root)
keywords = load_keywords(root)
intrinsic_procedures = load_intrinsic_procedures(root)
intrinsic_modules = load_intrinsic_modules(root)

return statements, keywords, intrinsic_procedures, intrinsic_modules


def get_intrinsic_keywords(statements, keywords, context=-1):
Expand All @@ -197,3 +271,28 @@ def get_intrinsic_keywords(statements, keywords, context=-1):
elif context == 3:
return keywords["var_def"] + keywords["type_mem"] + keywords["vis"]
return keywords["var_def"] + keywords["param"]


def update_m_intrinsics():
try:
files = glob.glob("M_intrinsics/md/*.md")
markdown_intrinsics = {}
for f in files:
key = f.replace("M_intrinsics/md/", "")
key = key.replace(".md", "").upper() # remove md extension
with open(f) as md_f:
val = md_f.read()
# remove manpage tag
val = val.replace(f"**{key.lower()}**(3)", f"**{key.lower()}**")
val = val.replace(f"**{key.upper()}**(3)", f"**{key.upper()}**")
markdown_intrinsics[key] = val

with open("fortls/intrinsic.procedures.markdown.json", "w") as f:
json.dump(markdown_intrinsics, f, indent=2)
f.write("\n") # add newline at end of file
except Exception as e:
print(e)


if __name__ == "__main__":
update_m_intrinsics()
Loading