We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`meson_options.txt` cannot be used to specify negative integer values
*Actual:*
When trying to specify a negative number in `meson_options.txt`, I get the error `Arguments may only be string, int, bool, or array of those.`.

*Expected:*
No error is raised, and negative values are accepted.

*Rationale:*
These options are ideal for setting defaults for build configuration via `#mesondefine`. It's fairly common for integer options to treat zero and negative values as having special meanings. And even if not, sometimes you'd like to have negative values as options.

*Workaround:*
- Add a second option for the special meaning (ugh!)
- Pass the option as a string, and use `#define` instead of `#mesondefine` (ugh!)

*Details:*
The error is raised here:
https://github.com/mesonbuild/meson/blob/6e865fc08d0d13b3846d00e80c9f3ea090645fb8/mesonbuild/optinterpreter.py#L170

The culprit looks like it may be here: https://github.com/mesonbuild/meson/blob/6e865fc08d0d13b3846d00e80c9f3ea090645fb8/mesonbuild/mparser.py#L107

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mesonbuild/optinterpreter.py`
Content:
```
1 # Copyright 2013-2014 The Meson development team
2 
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import re
16 import functools
17 import typing as T
18 
19 from . import mparser
20 from . import coredata
21 from . import mesonlib
22 from . import compilers
23 
24 forbidden_option_names = set(coredata.builtin_options.keys())
25 forbidden_prefixes = [lang + '_' for lang in compilers.all_languages] + ['b_', 'backend_']
26 reserved_prefixes = ['cross_']
27 
28 def is_invalid_name(name: str, *, log: bool = True) -> bool:
29     if name in forbidden_option_names:
30         return True
31     pref = name.split('_')[0] + '_'
32     if pref in forbidden_prefixes:
33         return True
34     if pref in reserved_prefixes:
35         if log:
36             from . import mlog
37             mlog.deprecation('Option uses prefix "%s", which is reserved for Meson. This will become an error in the future.' % pref)
38     return False
39 
40 class OptionException(mesonlib.MesonException):
41     pass
42 
43 
44 def permitted_kwargs(permitted):
45     """Function that validates kwargs for options."""
46     def _wraps(func):
47         @functools.wraps(func)
48         def _inner(name, description, kwargs):
49             bad = [a for a in kwargs.keys() if a not in permitted]
50             if bad:
51                 raise OptionException('Invalid kwargs for option "{}": "{}"'.format(
52                     name, ' '.join(bad)))
53             return func(description, kwargs)
54         return _inner
55     return _wraps
56 
57 
58 optname_regex = re.compile('[^a-zA-Z0-9_-]')
59 
60 @permitted_kwargs({'value', 'yield'})
61 def StringParser(description, kwargs):
62     return coredata.UserStringOption(description,
63                                      kwargs.get('value', ''),
64                                      kwargs.get('choices', []),
65                                      kwargs.get('yield', coredata.default_yielding))
66 
67 @permitted_kwargs({'value', 'yield'})
68 def BooleanParser(description, kwargs):
69     return coredata.UserBooleanOption(description,
70                                       kwargs.get('value', True),
71                                       kwargs.get('yield', coredata.default_yielding))
72 
73 @permitted_kwargs({'value', 'yield', 'choices'})
74 def ComboParser(description, kwargs):
75     if 'choices' not in kwargs:
76         raise OptionException('Combo option missing "choices" keyword.')
77     choices = kwargs['choices']
78     if not isinstance(choices, list):
79         raise OptionException('Combo choices must be an array.')
80     for i in choices:
81         if not isinstance(i, str):
82             raise OptionException('Combo choice elements must be strings.')
83     return coredata.UserComboOption(description,
84                                     choices,
85                                     kwargs.get('value', choices[0]),
86                                     kwargs.get('yield', coredata.default_yielding),)
87 
88 
89 @permitted_kwargs({'value', 'min', 'max', 'yield'})
90 def IntegerParser(description, kwargs):
91     if 'value' not in kwargs:
92         raise OptionException('Integer option must contain value argument.')
93     inttuple = (kwargs.get('min', None), kwargs.get('max', None), kwargs['value'])
94     return coredata.UserIntegerOption(description,
95                                       inttuple,
96                                       kwargs.get('yield', coredata.default_yielding))
97 
98 # FIXME: Cannot use FeatureNew while parsing options because we parse it before
99 # reading options in project(). See func_project() in interpreter.py
100 #@FeatureNew('array type option()', '0.44.0')
101 @permitted_kwargs({'value', 'yield', 'choices'})
102 def string_array_parser(description, kwargs):
103     if 'choices' in kwargs:
104         choices = kwargs['choices']
105         if not isinstance(choices, list):
106             raise OptionException('Array choices must be an array.')
107         for i in choices:
108             if not isinstance(i, str):
109                 raise OptionException('Array choice elements must be strings.')
110             value = kwargs.get('value', choices)
111     else:
112         choices = None
113         value = kwargs.get('value', [])
114     if not isinstance(value, list):
115         raise OptionException('Array choices must be passed as an array.')
116     return coredata.UserArrayOption(description,
117                                     value,
118                                     choices=choices,
119                                     yielding=kwargs.get('yield', coredata.default_yielding))
120 
121 @permitted_kwargs({'value', 'yield'})
122 def FeatureParser(description, kwargs):
123     return coredata.UserFeatureOption(description,
124                                       kwargs.get('value', 'auto'),
125                                       yielding=kwargs.get('yield', coredata.default_yielding))
126 
127 option_types = {'string': StringParser,
128                 'boolean': BooleanParser,
129                 'combo': ComboParser,
130                 'integer': IntegerParser,
131                 'array': string_array_parser,
132                 'feature': FeatureParser,
133                 } # type: T.Dict[str, T.Callable[[str, T.Dict], coredata.UserOption]]
134 
135 class OptionInterpreter:
136     def __init__(self, subproject):
137         self.options = {}
138         self.subproject = subproject
139 
140     def process(self, option_file):
141         try:
142             with open(option_file, 'r', encoding='utf8') as f:
143                 ast = mparser.Parser(f.read(), option_file).parse()
144         except mesonlib.MesonException as me:
145             me.file = option_file
146             raise me
147         if not isinstance(ast, mparser.CodeBlockNode):
148             e = OptionException('Option file is malformed.')
149             e.lineno = ast.lineno()
150             e.file = option_file
151             raise e
152         for cur in ast.lines:
153             try:
154                 self.evaluate_statement(cur)
155             except Exception as e:
156                 e.lineno = cur.lineno
157                 e.colno = cur.colno
158                 e.file = option_file
159                 raise e
160 
161     def reduce_single(self, arg):
162         if isinstance(arg, str):
163             return arg
164         elif isinstance(arg, (mparser.StringNode, mparser.BooleanNode,
165                               mparser.NumberNode)):
166             return arg.value
167         elif isinstance(arg, mparser.ArrayNode):
168             return [self.reduce_single(curarg) for curarg in arg.args.arguments]
169         else:
170             raise OptionException('Arguments may only be string, int, bool, or array of those.')
171 
172     def reduce_arguments(self, args):
173         assert(isinstance(args, mparser.ArgumentNode))
174         if args.incorrect_order():
175             raise OptionException('All keyword arguments must be after positional arguments.')
176         reduced_pos = [self.reduce_single(arg) for arg in args.arguments]
177         reduced_kw = {}
178         for key in args.kwargs.keys():
179             if not isinstance(key, mparser.IdNode):
180                 raise OptionException('Keyword argument name is not a string.')
181             a = args.kwargs[key]
182             reduced_kw[key.value] = self.reduce_single(a)
183         return reduced_pos, reduced_kw
184 
185     def evaluate_statement(self, node):
186         if not isinstance(node, mparser.FunctionNode):
187             raise OptionException('Option file may only contain option definitions')
188         func_name = node.func_name
189         if func_name != 'option':
190             raise OptionException('Only calls to option() are allowed in option files.')
191         (posargs, kwargs) = self.reduce_arguments(node.args)
192 
193         # FIXME: Cannot use FeatureNew while parsing options because we parse
194         # it before reading options in project(). See func_project() in
195         # interpreter.py
196         #if 'yield' in kwargs:
197         #    FeatureNew('option yield', '0.45.0').use(self.subproject)
198 
199         if 'type' not in kwargs:
200             raise OptionException('Option call missing mandatory "type" keyword argument')
201         opt_type = kwargs.pop('type')
202         if opt_type not in option_types:
203             raise OptionException('Unknown type %s.' % opt_type)
204         if len(posargs) != 1:
205             raise OptionException('Option() must have one (and only one) positional argument')
206         opt_name = posargs[0]
207         if not isinstance(opt_name, str):
208             raise OptionException('Positional argument must be a string.')
209         if optname_regex.search(opt_name) is not None:
210             raise OptionException('Option names can only contain letters, numbers or dashes.')
211         if is_invalid_name(opt_name):
212             raise OptionException('Option name %s is reserved.' % opt_name)
213         if self.subproject != '':
214             opt_name = self.subproject + ':' + opt_name
215         opt = option_types[opt_type](opt_name, kwargs.pop('description', ''), kwargs)
216         if opt.description == '':
217             opt.description = opt_name
218         self.options[opt_name] = opt
219
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -166,6 +166,16 @@
             return arg.value
         elif isinstance(arg, mparser.ArrayNode):
             return [self.reduce_single(curarg) for curarg in arg.args.arguments]
+        elif isinstance(arg, mparser.UMinusNode):
+            res = self.reduce_single(arg.value)
+            if not isinstance(res, (int, float)):
+                raise OptionException('Token after "-" is not a number')
+            return -res
+        elif isinstance(arg, mparser.NotNode):
+            res = self.reduce_single(arg.value)
+            if not isinstance(res, bool):
+                raise OptionException('Token after "not" is not a a boolean')
+            return not res
         else:
             raise OptionException('Arguments may only be string, int, bool, or array of those.')

{"golden_diff": "diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py\n--- a/mesonbuild/optinterpreter.py\n+++ b/mesonbuild/optinterpreter.py\n@@ -166,6 +166,16 @@\n             return arg.value\n         elif isinstance(arg, mparser.ArrayNode):\n             return [self.reduce_single(curarg) for curarg in arg.args.arguments]\n+        elif isinstance(arg, mparser.UMinusNode):\n+            res = self.reduce_single(arg.value)\n+            if not isinstance(res, (int, float)):\n+                raise OptionException('Token after \"-\" is not a number')\n+            return -res\n+        elif isinstance(arg, mparser.NotNode):\n+            res = self.reduce_single(arg.value)\n+            if not isinstance(res, bool):\n+                raise OptionException('Token after \"not\" is not a a boolean')\n+            return not res\n         else:\n             raise OptionException('Arguments may only be string, int, bool, or array of those.')\n", "issue": "`meson_options.txt` cannot be used to specify negative integer values\n*Actual:*\r\nWhen trying to specify a negative number in `meson_options.txt`, I get the error `Arguments may only be string, int, bool, or array of those.`.\r\n\r\n*Expected:*\r\nNo error is raised, and negative values are accepted.\r\n\r\n*Rationale:*\r\nThese options are ideal for setting defaults for build configuration via `#mesondefine`. It's fairly common for integer options to treat zero and negative values as having special meanings. And even if not, sometimes you'd like to have negative values as options.\r\n\r\n*Workaround:*\r\n- Add a second option for the special meaning (ugh!)\r\n- Pass the option as a string, and use `#define` instead of `#mesondefine` (ugh!)\r\n\r\n*Details:*\r\nThe error is raised here:\r\nhttps://github.com/mesonbuild/meson/blob/6e865fc08d0d13b3846d00e80c9f3ea090645fb8/mesonbuild/optinterpreter.py#L170\r\n\r\nThe culprit looks like it may be here: https://github.com/mesonbuild/meson/blob/6e865fc08d0d13b3846d00e80c9f3ea090645fb8/mesonbuild/mparser.py#L107\n", "before_files": [{"content": "# Copyright 2013-2014 The Meson development team\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#     http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\nimport functools\nimport typing as T\n\nfrom . import mparser\nfrom . import coredata\nfrom . import mesonlib\nfrom . import compilers\n\nforbidden_option_names = set(coredata.builtin_options.keys())\nforbidden_prefixes = [lang + '_' for lang in compilers.all_languages] + ['b_', 'backend_']\nreserved_prefixes = ['cross_']\n\ndef is_invalid_name(name: str, *, log: bool = True) -> bool:\n    if name in forbidden_option_names:\n        return True\n    pref = name.split('_')[0] + '_'\n    if pref in forbidden_prefixes:\n        return True\n    if pref in reserved_prefixes:\n        if log:\n            from . import mlog\n            mlog.deprecation('Option uses prefix \"%s\", which is reserved for Meson. This will become an error in the future.' % pref)\n    return False\n\nclass OptionException(mesonlib.MesonException):\n    pass\n\n\ndef permitted_kwargs(permitted):\n    \"\"\"Function that validates kwargs for options.\"\"\"\n    def _wraps(func):\n        @functools.wraps(func)\n        def _inner(name, description, kwargs):\n            bad = [a for a in kwargs.keys() if a not in permitted]\n            if bad:\n                raise OptionException('Invalid kwargs for option \"{}\": \"{}\"'.format(\n                    name, ' '.join(bad)))\n            return func(description, kwargs)\n        return _inner\n    return _wraps\n\n\noptname_regex = re.compile('[^a-zA-Z0-9_-]')\n\n@permitted_kwargs({'value', 'yield'})\ndef StringParser(description, kwargs):\n    return coredata.UserStringOption(description,\n                                     kwargs.get('value', ''),\n                                     kwargs.get('choices', []),\n                                     kwargs.get('yield', coredata.default_yielding))\n\n@permitted_kwargs({'value', 'yield'})\ndef BooleanParser(description, kwargs):\n    return coredata.UserBooleanOption(description,\n                                      kwargs.get('value', True),\n                                      kwargs.get('yield', coredata.default_yielding))\n\n@permitted_kwargs({'value', 'yield', 'choices'})\ndef ComboParser(description, kwargs):\n    if 'choices' not in kwargs:\n        raise OptionException('Combo option missing \"choices\" keyword.')\n    choices = kwargs['choices']\n    if not isinstance(choices, list):\n        raise OptionException('Combo choices must be an array.')\n    for i in choices:\n        if not isinstance(i, str):\n            raise OptionException('Combo choice elements must be strings.')\n    return coredata.UserComboOption(description,\n                                    choices,\n                                    kwargs.get('value', choices[0]),\n                                    kwargs.get('yield', coredata.default_yielding),)\n\n\n@permitted_kwargs({'value', 'min', 'max', 'yield'})\ndef IntegerParser(description, kwargs):\n    if 'value' not in kwargs:\n        raise OptionException('Integer option must contain value argument.')\n    inttuple = (kwargs.get('min', None), kwargs.get('max', None), kwargs['value'])\n    return coredata.UserIntegerOption(description,\n                                      inttuple,\n                                      kwargs.get('yield', coredata.default_yielding))\n\n# FIXME: Cannot use FeatureNew while parsing options because we parse it before\n# reading options in project(). See func_project() in interpreter.py\n#@FeatureNew('array type option()', '0.44.0')\n@permitted_kwargs({'value', 'yield', 'choices'})\ndef string_array_parser(description, kwargs):\n    if 'choices' in kwargs:\n        choices = kwargs['choices']\n        if not isinstance(choices, list):\n            raise OptionException('Array choices must be an array.')\n        for i in choices:\n            if not isinstance(i, str):\n                raise OptionException('Array choice elements must be strings.')\n            value = kwargs.get('value', choices)\n    else:\n        choices = None\n        value = kwargs.get('value', [])\n    if not isinstance(value, list):\n        raise OptionException('Array choices must be passed as an array.')\n    return coredata.UserArrayOption(description,\n                                    value,\n                                    choices=choices,\n                                    yielding=kwargs.get('yield', coredata.default_yielding))\n\n@permitted_kwargs({'value', 'yield'})\ndef FeatureParser(description, kwargs):\n    return coredata.UserFeatureOption(description,\n                                      kwargs.get('value', 'auto'),\n                                      yielding=kwargs.get('yield', coredata.default_yielding))\n\noption_types = {'string': StringParser,\n                'boolean': BooleanParser,\n                'combo': ComboParser,\n                'integer': IntegerParser,\n                'array': string_array_parser,\n                'feature': FeatureParser,\n                } # type: T.Dict[str, T.Callable[[str, T.Dict], coredata.UserOption]]\n\nclass OptionInterpreter:\n    def __init__(self, subproject):\n        self.options = {}\n        self.subproject = subproject\n\n    def process(self, option_file):\n        try:\n            with open(option_file, 'r', encoding='utf8') as f:\n                ast = mparser.Parser(f.read(), option_file).parse()\n        except mesonlib.MesonException as me:\n            me.file = option_file\n            raise me\n        if not isinstance(ast, mparser.CodeBlockNode):\n            e = OptionException('Option file is malformed.')\n            e.lineno = ast.lineno()\n            e.file = option_file\n            raise e\n        for cur in ast.lines:\n            try:\n                self.evaluate_statement(cur)\n            except Exception as e:\n                e.lineno = cur.lineno\n                e.colno = cur.colno\n                e.file = option_file\n                raise e\n\n    def reduce_single(self, arg):\n        if isinstance(arg, str):\n            return arg\n        elif isinstance(arg, (mparser.StringNode, mparser.BooleanNode,\n                              mparser.NumberNode)):\n            return arg.value\n        elif isinstance(arg, mparser.ArrayNode):\n            return [self.reduce_single(curarg) for curarg in arg.args.arguments]\n        else:\n            raise OptionException('Arguments may only be string, int, bool, or array of those.')\n\n    def reduce_arguments(self, args):\n        assert(isinstance(args, mparser.ArgumentNode))\n        if args.incorrect_order():\n            raise OptionException('All keyword arguments must be after positional arguments.')\n        reduced_pos = [self.reduce_single(arg) for arg in args.arguments]\n        reduced_kw = {}\n        for key in args.kwargs.keys():\n            if not isinstance(key, mparser.IdNode):\n                raise OptionException('Keyword argument name is not a string.')\n            a = args.kwargs[key]\n            reduced_kw[key.value] = self.reduce_single(a)\n        return reduced_pos, reduced_kw\n\n    def evaluate_statement(self, node):\n        if not isinstance(node, mparser.FunctionNode):\n            raise OptionException('Option file may only contain option definitions')\n        func_name = node.func_name\n        if func_name != 'option':\n            raise OptionException('Only calls to option() are allowed in option files.')\n        (posargs, kwargs) = self.reduce_arguments(node.args)\n\n        # FIXME: Cannot use FeatureNew while parsing options because we parse\n        # it before reading options in project(). See func_project() in\n        # interpreter.py\n        #if 'yield' in kwargs:\n        #    FeatureNew('option yield', '0.45.0').use(self.subproject)\n\n        if 'type' not in kwargs:\n            raise OptionException('Option call missing mandatory \"type\" keyword argument')\n        opt_type = kwargs.pop('type')\n        if opt_type not in option_types:\n            raise OptionException('Unknown type %s.' % opt_type)\n        if len(posargs) != 1:\n            raise OptionException('Option() must have one (and only one) positional argument')\n        opt_name = posargs[0]\n        if not isinstance(opt_name, str):\n            raise OptionException('Positional argument must be a string.')\n        if optname_regex.search(opt_name) is not None:\n            raise OptionException('Option names can only contain letters, numbers or dashes.')\n        if is_invalid_name(opt_name):\n            raise OptionException('Option name %s is reserved.' % opt_name)\n        if self.subproject != '':\n            opt_name = self.subproject + ':' + opt_name\n        opt = option_types[opt_type](opt_name, kwargs.pop('description', ''), kwargs)\n        if opt.description == '':\n            opt.description = opt_name\n        self.options[opt_name] = opt\n", "path": "mesonbuild/optinterpreter.py"}], "after_files": [{"content": "# Copyright 2013-2014 The Meson development team\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n#     http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\nimport functools\nimport typing as T\n\nfrom . import mparser\nfrom . import coredata\nfrom . import mesonlib\nfrom . import compilers\n\nforbidden_option_names = set(coredata.builtin_options.keys())\nforbidden_prefixes = [lang + '_' for lang in compilers.all_languages] + ['b_', 'backend_']\nreserved_prefixes = ['cross_']\n\ndef is_invalid_name(name: str, *, log: bool = True) -> bool:\n    if name in forbidden_option_names:\n        return True\n    pref = name.split('_')[0] + '_'\n    if pref in forbidden_prefixes:\n        return True\n    if pref in reserved_prefixes:\n        if log:\n            from . import mlog\n            mlog.deprecation('Option uses prefix \"%s\", which is reserved for Meson. This will become an error in the future.' % pref)\n    return False\n\nclass OptionException(mesonlib.MesonException):\n    pass\n\n\ndef permitted_kwargs(permitted):\n    \"\"\"Function that validates kwargs for options.\"\"\"\n    def _wraps(func):\n        @functools.wraps(func)\n        def _inner(name, description, kwargs):\n            bad = [a for a in kwargs.keys() if a not in permitted]\n            if bad:\n                raise OptionException('Invalid kwargs for option \"{}\": \"{}\"'.format(\n                    name, ' '.join(bad)))\n            return func(description, kwargs)\n        return _inner\n    return _wraps\n\n\noptname_regex = re.compile('[^a-zA-Z0-9_-]')\n\n@permitted_kwargs({'value', 'yield'})\ndef StringParser(description, kwargs):\n    return coredata.UserStringOption(description,\n                                     kwargs.get('value', ''),\n                                     kwargs.get('choices', []),\n                                     kwargs.get('yield', coredata.default_yielding))\n\n@permitted_kwargs({'value', 'yield'})\ndef BooleanParser(description, kwargs):\n    return coredata.UserBooleanOption(description,\n                                      kwargs.get('value', True),\n                                      kwargs.get('yield', coredata.default_yielding))\n\n@permitted_kwargs({'value', 'yield', 'choices'})\ndef ComboParser(description, kwargs):\n    if 'choices' not in kwargs:\n        raise OptionException('Combo option missing \"choices\" keyword.')\n    choices = kwargs['choices']\n    if not isinstance(choices, list):\n        raise OptionException('Combo choices must be an array.')\n    for i in choices:\n        if not isinstance(i, str):\n            raise OptionException('Combo choice elements must be strings.')\n    return coredata.UserComboOption(description,\n                                    choices,\n                                    kwargs.get('value', choices[0]),\n                                    kwargs.get('yield', coredata.default_yielding),)\n\n\n@permitted_kwargs({'value', 'min', 'max', 'yield'})\ndef IntegerParser(description, kwargs):\n    if 'value' not in kwargs:\n        raise OptionException('Integer option must contain value argument.')\n    inttuple = (kwargs.get('min', None), kwargs.get('max', None), kwargs['value'])\n    return coredata.UserIntegerOption(description,\n                                      inttuple,\n                                      kwargs.get('yield', coredata.default_yielding))\n\n# FIXME: Cannot use FeatureNew while parsing options because we parse it before\n# reading options in project(). See func_project() in interpreter.py\n#@FeatureNew('array type option()', '0.44.0')\n@permitted_kwargs({'value', 'yield', 'choices'})\ndef string_array_parser(description, kwargs):\n    if 'choices' in kwargs:\n        choices = kwargs['choices']\n        if not isinstance(choices, list):\n            raise OptionException('Array choices must be an array.')\n        for i in choices:\n            if not isinstance(i, str):\n                raise OptionException('Array choice elements must be strings.')\n            value = kwargs.get('value', choices)\n    else:\n        choices = None\n        value = kwargs.get('value', [])\n    if not isinstance(value, list):\n        raise OptionException('Array choices must be passed as an array.')\n    return coredata.UserArrayOption(description,\n                                    value,\n                                    choices=choices,\n                                    yielding=kwargs.get('yield', coredata.default_yielding))\n\n@permitted_kwargs({'value', 'yield'})\ndef FeatureParser(description, kwargs):\n    return coredata.UserFeatureOption(description,\n                                      kwargs.get('value', 'auto'),\n                                      yielding=kwargs.get('yield', coredata.default_yielding))\n\noption_types = {'string': StringParser,\n                'boolean': BooleanParser,\n                'combo': ComboParser,\n                'integer': IntegerParser,\n                'array': string_array_parser,\n                'feature': FeatureParser,\n                } # type: T.Dict[str, T.Callable[[str, T.Dict], coredata.UserOption]]\n\nclass OptionInterpreter:\n    def __init__(self, subproject):\n        self.options = {}\n        self.subproject = subproject\n\n    def process(self, option_file):\n        try:\n            with open(option_file, 'r', encoding='utf8') as f:\n                ast = mparser.Parser(f.read(), option_file).parse()\n        except mesonlib.MesonException as me:\n            me.file = option_file\n            raise me\n        if not isinstance(ast, mparser.CodeBlockNode):\n            e = OptionException('Option file is malformed.')\n            e.lineno = ast.lineno()\n            e.file = option_file\n            raise e\n        for cur in ast.lines:\n            try:\n                self.evaluate_statement(cur)\n            except Exception as e:\n                e.lineno = cur.lineno\n                e.colno = cur.colno\n                e.file = option_file\n                raise e\n\n    def reduce_single(self, arg):\n        if isinstance(arg, str):\n            return arg\n        elif isinstance(arg, (mparser.StringNode, mparser.BooleanNode,\n                              mparser.NumberNode)):\n            return arg.value\n        elif isinstance(arg, mparser.ArrayNode):\n            return [self.reduce_single(curarg) for curarg in arg.args.arguments]\n        elif isinstance(arg, mparser.UMinusNode):\n            res = self.reduce_single(arg.value)\n            if not isinstance(res, (int, float)):\n                raise OptionException('Token after \"-\" is not a number')\n            return -res\n        elif isinstance(arg, mparser.NotNode):\n            res = self.reduce_single(arg.value)\n            if not isinstance(res, bool):\n                raise OptionException('Token after \"not\" is not a a boolean')\n            return not res\n        else:\n            raise OptionException('Arguments may only be string, int, bool, or array of those.')\n\n    def reduce_arguments(self, args):\n        assert(isinstance(args, mparser.ArgumentNode))\n        if args.incorrect_order():\n            raise OptionException('All keyword arguments must be after positional arguments.')\n        reduced_pos = [self.reduce_single(arg) for arg in args.arguments]\n        reduced_kw = {}\n        for key in args.kwargs.keys():\n            if not isinstance(key, mparser.IdNode):\n                raise OptionException('Keyword argument name is not a string.')\n            a = args.kwargs[key]\n            reduced_kw[key.value] = self.reduce_single(a)\n        return reduced_pos, reduced_kw\n\n    def evaluate_statement(self, node):\n        if not isinstance(node, mparser.FunctionNode):\n            raise OptionException('Option file may only contain option definitions')\n        func_name = node.func_name\n        if func_name != 'option':\n            raise OptionException('Only calls to option() are allowed in option files.')\n        (posargs, kwargs) = self.reduce_arguments(node.args)\n\n        # FIXME: Cannot use FeatureNew while parsing options because we parse\n        # it before reading options in project(). See func_project() in\n        # interpreter.py\n        #if 'yield' in kwargs:\n        #    FeatureNew('option yield', '0.45.0').use(self.subproject)\n\n        if 'type' not in kwargs:\n            raise OptionException('Option call missing mandatory \"type\" keyword argument')\n        opt_type = kwargs.pop('type')\n        if opt_type not in option_types:\n            raise OptionException('Unknown type %s.' % opt_type)\n        if len(posargs) != 1:\n            raise OptionException('Option() must have one (and only one) positional argument')\n        opt_name = posargs[0]\n        if not isinstance(opt_name, str):\n            raise OptionException('Positional argument must be a string.')\n        if optname_regex.search(opt_name) is not None:\n            raise OptionException('Option names can only contain letters, numbers or dashes.')\n        if is_invalid_name(opt_name):\n            raise OptionException('Option name %s is reserved.' % opt_name)\n        if self.subproject != '':\n            opt_name = self.subproject + ':' + opt_name\n        opt = option_types[opt_type](opt_name, kwargs.pop('description', ''), kwargs)\n        if opt.description == '':\n            opt.description = opt_name\n        self.options[opt_name] = opt\n", "path": "mesonbuild/optinterpreter.py"}]}