1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
"""
Read and parse the file pari.desc
"""
#*****************************************************************************
# Copyright (C) 2015 Jeroen Demeyer <jdemeyer@cage.ugent.be>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************
from __future__ import absolute_import, unicode_literals
import os, re, io
from .args import pari_arg_types
from .ret import pari_ret_types
from .paths import pari_share
paren_re = re.compile(r"[(](.*)[)]")
argname_re = re.compile(r"[ {]*&?([A-Za-z_][A-Za-z0-9_]*)")
def read_pari_desc():
"""
Read and parse the file ``pari.desc``.
The output is a dictionary where the keys are GP function names
and the corresponding values are dictionaries containing the
``(key, value)`` pairs from ``pari.desc``.
EXAMPLES::
>>> from autogen.parser import read_pari_desc
>>> D = read_pari_desc()
>>> Dcos = D["cos"]
>>> if "description" in Dcos: _ = Dcos.pop("description")
>>> Dcos.pop("doc").startswith('cosine of $x$.')
True
>>> Dcos == { 'class': 'basic',
... 'cname': 'gcos',
... 'function': 'cos',
... 'help': 'cos(x): cosine of x.',
... 'prototype': 'Gp',
... 'section': 'transcendental'}
True
"""
pari_desc = os.path.join(pari_share(), 'pari.desc')
with io.open(pari_desc, encoding="utf-8") as f:
lines = f.readlines()
n = 0
N = len(lines)
functions = {}
while n < N:
fun = {}
while True:
L = lines[n]; n += 1
if L == "\n":
break
# As long as the next lines start with a space, append them
while lines[n].startswith(" "):
L += (lines[n])[1:]; n += 1
key, value = L.split(":", 1)
# Change key to an allowed identifier name
key = key.lower().replace("-", "")
fun[key] = value.strip()
name = fun["function"]
functions[name] = fun
return functions
def parse_prototype(proto, help, initial_args=[]):
"""
Parse arguments and return type of a PARI function.
INPUT:
- ``proto`` -- a PARI prototype like ``"GD0,L,DGDGDG"``
- ``help`` -- a PARI help string like
``"qfbred(x,{flag=0},{d},{isd},{sd})"``
- ``initial_args`` -- other arguments to this function which come
before the PARI arguments, for example a ``self`` argument.
OUTPUT: a tuple ``(args, ret)`` where
- ``args`` is a list consisting of ``initial_args`` followed by
:class:`PariArgument` instances with all arguments of this
function.
- ``ret`` is a :class:`PariReturn` instance with the return type of
this function.
EXAMPLES::
>>> from autogen.parser import parse_prototype
>>> proto = 'GD0,L,DGDGDG'
>>> help = 'qfbred(x,{flag=0},{d},{isd},{sd})'
>>> parse_prototype(proto, help)
([GEN x, long flag=0, GEN d=NULL, GEN isd=NULL, GEN sd=NULL], GEN)
>>> proto = "GD&"
>>> help = "sqrtint(x,{&r})"
>>> parse_prototype(proto, help)
([GEN x, GEN* r=NULL], GEN)
>>> parse_prototype("lp", "foo()", [str("TEST")])
(['TEST', prec precision=0], long)
"""
# Use the help string just for the argument names.
# "names" should be an iterator over the argument names.
m = paren_re.search(help)
if m is None:
names = iter([])
else:
s = m.groups()[0]
matches = [argname_re.match(x) for x in s.split(",")]
names = (m.groups()[0] for m in matches if m is not None)
# First, handle the return type
try:
c = proto[0]
t = pari_ret_types[c]
n = 1 # index in proto
except (IndexError, KeyError):
t = pari_ret_types[""]
n = 0 # index in proto
ret = t()
# Go over the prototype characters and build up the arguments
args = list(initial_args)
have_default = False # Have we seen any default argument?
while n < len(proto):
c = proto[n]; n += 1
# Parse default value
if c == "D":
default = ""
if proto[n] not in pari_arg_types:
while True:
c = proto[n]; n += 1
if c == ",":
break
default += c
c = proto[n]; n += 1
else:
default = None
try:
t = pari_arg_types[c]
if t is None:
raise NotImplementedError('unsupported prototype character %r' % c)
except KeyError:
if c == ",":
continue # Just skip additional commas
else:
raise ValueError('unknown prototype character %r' % c)
arg = t(names, default, index=len(args))
if arg.default is not None:
have_default = True
elif have_default:
# We have a non-default argument following a default
# argument, which means trouble...
#
# A syntactical wart of Python is that it does not allow
# that: something like def foo(x=None, y) is a SyntaxError
# (at least with Python-2.7.13, Python-3.6.1 and Cython-0.25.2)
#
# A small number of GP functions (nfroots() for example)
# wants to do this anyway. Luckily, this seems to occur only
# for arguments of type GEN (prototype code "G")
#
# To work around this, we add a "fake" default value and
# then raise an error if it was not given...
if c != "G":
raise NotImplementedError("non-default argument after default argument is only implemented for GEN arguments")
arg.default = False
args.append(arg)
return (args, ret)
|