diff options
author | Kurt Smith <kwsmith1@wisc.edu> | 2009-08-16 13:05:10 -0500 |
---|---|---|
committer | Kurt Smith <kwsmith1@wisc.edu> | 2009-08-16 13:05:10 -0500 |
commit | b3ef4bec70eec5390f733123f76775e434f2103f (patch) | |
tree | 2700dd3be07073295342ec74866ed0ab78f04431 | |
parent | 088be9eed970118254d78a14d847eb9a30c3e05c (diff) | |
download | cython-b3ef4bec70eec5390f733123f76775e434f2103f.tar.gz |
[mq]: fwrap_partial_array_impl
-rw-r--r-- | Tools/fwrap/Main.py | 2 | ||||
-rw-r--r-- | Tools/fwrap/Visitor.py | 172 | ||||
-rw-r--r-- | Tools/fwrap/WrapperArgs.py | 152 | ||||
-rw-r--r-- | Tools/fwrap/runtests.py | 3 | ||||
-rw-r--r-- | Tools/fwrap/utils.py | 2 |
5 files changed, 246 insertions, 85 deletions
diff --git a/Tools/fwrap/Main.py b/Tools/fwrap/Main.py index 7aed99e83..52552e69a 100644 --- a/Tools/fwrap/Main.py +++ b/Tools/fwrap/Main.py @@ -64,7 +64,7 @@ def wrap(filenames, directory, outdir, projectname): # copy the fortran source files to the project dir. for fname in filenames: - shutil.copy(fname, projdir) + shutil.copy(fname, projdir) def gen_makefile(projname, src_files, src_dir, out_dir): import sys diff --git a/Tools/fwrap/Visitor.py b/Tools/fwrap/Visitor.py index bce6aa883..98d89569c 100644 --- a/Tools/fwrap/Visitor.py +++ b/Tools/fwrap/Visitor.py @@ -18,8 +18,9 @@ from fparser.block_statements import Function, SubProgramStatement, \ EndFunction, EndSubroutine, Interface from fparser.statements import Use, Parameter, Dimension, Pointer from fparser.typedecl_statements import TypeDeclarationStatement +import WrapperArgs from WrapperArgs import wrapper_var_factory -from utils import warning, mangle_prefix, valid_name +from utils import warning, mangle_prefix, valid_name, CY_IMPORT_ALIAS class BasicVisitor(object): """A generic visitor base class which can be used for visiting any kind of object.""" @@ -212,6 +213,8 @@ class AutoConfigGenerator(GeneratorBase): def __init__(self, projname, *args, **kwargs): GeneratorBase.__init__(self, *args, **kwargs) + self.default_types = [] + self.temp_ctr = 0 self.projname = projname @@ -235,6 +238,15 @@ class AutoConfigGenerator(GeneratorBase): self.resolve_mod.add_subprogram_block(self.type_len_subr) self.resolve_subrs['type_len_subr'] = self.type_len_subr + # Type used for array shapes. + local_ktp_subr.executable_stmts.put(ktp_scalar_int_code % { + 'scalar_int_expr' : 'c_long', + 'type_name' : 'integer', + 'mapped_name' : WrapperArgs.ARRAY_SHAPE_TYPE} + ) + + + def _init_toplevel(self): self.utility.root.put(lookup_mod_code,indent=True) self.resolve_mod.block_start.putln("module resolve_mod") @@ -284,6 +296,7 @@ class AutoConfigGenerator(GeneratorBase): def copyto(self, fh): + # write the utility code. self.utility.copyto(fh) # write the resove_mod code. @@ -313,7 +326,7 @@ class FortranWrapperGenerator(GeneratorBase): @staticmethod def make_fname(base): - return "%s_fortran.f95" % base.lower().strip() + return "%s_fortran.f95" % base.strip() def __init__(self, projname, *args, **kwargs): GeneratorBase.__init__(self, *args, **kwargs) @@ -368,7 +381,7 @@ class FortranWrapperGenerator(GeneratorBase): argnames = [] for varw in arg_varws(node): - argnames.extend(varw.get_argnames()) + argnames.extend(varw.get_fort_argnames()) wrapname = mangle_prefix+node.name # argnames = node.args[:] @@ -382,7 +395,7 @@ class FortranWrapperGenerator(GeneratorBase): subp_code.use_stmts.putln("use config") for varw in arg_varws(node): - varw.generate_declaration(subp_code.declarations) + varw.generate_fort_declarations(subp_code.declarations) if isinstance(node, Function): # declare function return type @@ -400,12 +413,12 @@ class FortranWrapperGenerator(GeneratorBase): # pre-call code here. for varw in all_varws(node): - varw.pre_call_code(subp_code.executable_stmts) + varw.pre_call_fortran_code(subp_code.executable_stmts) # call the wrapped function/subr. pass_argnames = [] for varw in arg_varws(node): - pass_argnames.append(varw.get_pass_argname()[1]) + pass_argnames.append(varw.get_fort_pass_argnames_map()[1]) if isinstance(node, Function): subp_code.executable_stmts.putln("%(wrapname)s = %(funcname)s(%(arglst)s)" % \ @@ -421,7 +434,7 @@ class FortranWrapperGenerator(GeneratorBase): # post-call code here. for varw in all_varws(node): - varw.post_call_code(subp_code.executable_stmts) + varw.post_call_fortran_code(subp_code.executable_stmts) return node @@ -435,7 +448,7 @@ class CHeaderGenerator(GeneratorBase): @staticmethod def make_fname(base): - return "%s_header.h" % base.lower().strip() + return "%s_header.h" % base.strip() def __init__(self, projname, *args, **kwargs): GeneratorBase.__init__(self, *args, **kwargs) @@ -508,7 +521,7 @@ class PxdGenerator(GeneratorBase): @staticmethod def make_fname(base): - return "%s_fortran.pxd" % base.lower().rstrip() + return "%s_fortran.pxd" % base.rstrip() def __init__(self, projname, *args, **kwargs): GeneratorBase.__init__(self, *args, **kwargs) @@ -521,6 +534,10 @@ class PxdGenerator(GeneratorBase): self.typedef_suite = CySuiteCode(level=0) self.proto_suite = CySuiteCode(level=0) + # array shape type + self.typedef_suite.suite_body.putln("ctypedef %s %s" % \ + ("long int", WrapperArgs.ARRAY_SHAPE_TYPE)) + def visit_SubProgramStatement(self, node): if node.name in self.seen_subps: return node @@ -565,13 +582,12 @@ class CyHeaderGenerator(GeneratorBase): @staticmethod def make_fname(base): - return "%s.pxd" % base.lower().rstrip() + return "%s.pxd" % base.rstrip() def __init__(self, projname, *args, **kwargs): GeneratorBase.__init__(self, *args, **kwargs) self.projname = projname - self.import_alias = "wf" self.import_code = UtilityCode(level=0) self.proto_code = UtilityCode(level=0) @@ -584,34 +600,42 @@ class CyHeaderGenerator(GeneratorBase): self.seen_subps.add(node.name) # TODO: return struct typedecl # Cython API function prototype - cypro = cy_prototype(node, self.import_alias) + cypro = cy_prototype(node) - self.proto_code.root.putln("cdef api %s cy_%s(%s)" % (cypro['return_type'], - cypro['proto_name'], ", ".join(cypro['arglst']))) + ret_type = cypro['return_type'] + if ret_type not in ('void',): + ret_type = "%s.%s" % (CY_IMPORT_ALIAS, ret_type) + + arg_type_list = ["%s.%s" % (CY_IMPORT_ALIAS, arg_type) for (arg_type, _) in cypro['arglst']] + self.proto_code.root.putln("cdef api %s cy_%s(%s)" % ( + ret_type, + cypro['proto_name'], + ", ".join(arg_type_list))) def copyto(self, fh): - self.import_code.root.putln("cimport %s as %s" % (PxdGenerator.make_fname(self.projname).split('.')[0], self.import_alias)) + self.import_code.root.putln("cimport %s as %s" % ( + PxdGenerator.make_fname(self.projname).split('.')[0], + CY_IMPORT_ALIAS)) self.import_code.root.putln("") self.import_code.copyto(fh) self.proto_code.copyto(fh) -def cy_prototype(node, import_alias): - c_proto = c_prototype(node) - arg_lst_varws = arg_varws(node) +def cy_prototype(node): res_varw = None if isinstance(node, Function): res_varw = result_varw(node) - arglst = c_proto['arglst'][:] - arglst = [("%s.%s" % (import_alias, arg)) for arg in arglst] + arglst = [] + for varw in arg_varws(node): + names = varw.get_cy_arg_declarations() + arglst.extend(names) if res_varw is None: res_str = "void" else: - res_str = "%s.%s" % (import_alias, res_varw.resolved_name) + res_str = res_varw.resolved_name return { 'arglst' : arglst, - 'arglst_varws' : arg_lst_varws, 'proto_name' : node.name, 'return_type' : res_str} @@ -619,61 +643,103 @@ class CyImplGenerator(GeneratorBase): @staticmethod def make_fname(base): - return "%s.pyx" % base.lower().rstrip() + return "%s.pyx" % base.rstrip() def __init__(self, projname, *args, **kwargs): GeneratorBase.__init__(self, *args, **kwargs) self.projname = projname - self.import_alias = 'wf' self.import_code = UtilityCode(level=0) self.functions = [] self.seen_subps = set() def gen_api_func(self, node): - cypro = cy_prototype(node, self.import_alias) + is_func = isinstance(node, Function) + avws = arg_varws(node) + # cypro = cy_prototype(node) api_func = CySuiteCode(level=0) args = []; call_args = [] - for type_name, varw in zip(cypro['arglst'], cypro['arglst_varws']): - args.append("%s %s" % (type_name, varw.var.name)) - call_args.append(varw.var.name) + for varw in avws: + for type_name, arg_name in varw.get_cy_arg_declarations(): + args.append("%s.%s %s" % (CY_IMPORT_ALIAS, type_name, arg_name)) + for arg_name in varw.get_cy_pass_argnames(): + call_args.append(arg_name) - api_func.suite_start.putln("cdef api %s cy_%s(%s):" % (cypro['return_type'], cypro['proto_name'], ", ".join(args))) + res_varw = result_varw(node) + if res_varw: + ret_type = "%s.%s" % (CY_IMPORT_ALIAS, res_varw.resolved_name) + else: + ret_type = 'void' - if isinstance(node, Function): + api_func.suite_start.putln("cdef api %s cy_%s(%s):" % ( + ret_type, node.name, ", ".join(args))) + + # generate declarations + if is_func: ret_var = '__fwrap_return' - api_func.suite_body.putln("cdef %s %s" % (cypro['return_type'], ret_var)) - api_func.suite_body.putln("%s = %s.%s(%s)" % (ret_var, self.import_alias, cypro['proto_name'], ", ".join(call_args))) + api_func.suite_body.putln("cdef %s %s" % ( + ret_type, ret_var)) + for varw in avws: + varw.generate_cy_declarations(api_func.suite_body) + + # generate pre-call code + for varw in avws: + varw.pre_call_cy_code(api_func.suite_body) + + # generate the call itself + func_call = "%s.%s(%s)" % (CY_IMPORT_ALIAS, + node.name, + ", ".join(call_args)) + if is_func: + api_func.suite_body.putln("%s = %s" % ( + ret_var, func_call)) + else: + api_func.suite_body.putln(func_call) + + # post-call code + for varw in avws: + varw.post_call_cy_code(api_func.suite_body) + + # return value if function. + if is_func: api_func.suite_body.putln("return %s" % ret_var) - elif isinstance(node, Subroutine): - api_func.suite_body.putln("%s.%s(%s)" % (self.import_alias, cypro['proto_name'], ", ".join(call_args))) + self.functions.append(api_func) def gen_py_func(self, node): - cypro = cy_prototype(node, self.import_alias) py_func = CySuiteCode(level=0) args = []; call_args = []; ret_lst = [] - for type_name, varw in zip(cypro['arglst'], cypro['arglst_varws']): + for varw in arg_varws(node): # no pointer types in python function argument list. - args.append("%s.%s %s" % (self.import_alias, varw.resolved_name, varw.var.name)) - if '*' in type_name: - call_args.append("&%s" % varw.var.name) - else: - call_args.append(varw.var.name) - if varw.var.is_intent_out() or varw.var.is_intent_inout(): - ret_lst.append(varw.var.name) - - py_func.suite_start.putln("def %s(%s):" % (cypro['proto_name'], ", ".join(args))) - - proc_call = "cy_%s(%s)" % (cypro['proto_name'], ", ".join(call_args)) + # if varw.is_array: + # import pdb; pdb.set_trace() + # else: + for type_name, arg_name in varw.get_py_arg_declarations(): + if varw.is_array: + args.append(arg_name) + call_args.append(arg_name) + else: + args.append("%s.%s %s" % (CY_IMPORT_ALIAS, type_name, arg_name)) + if varw.var.is_intent_inout() or varw.var.is_intent_out(): + call_args.append("&%s" % arg_name) + else: + call_args.append(arg_name) + if varw.var.is_intent_inout() or varw.var.is_intent_out(): + ret_lst.append(arg_name) + + py_func.suite_start.putln("def %s(%s):" % (node.name, ", ".join(args))) + + proc_call = "cy_%s(%s)" % (node.name, ", ".join(call_args)) if isinstance(node, Function): - ret_var = '__fwrap_return' - ret_lst.insert(0, ret_var) - py_func.suite_body.putln("cdef %s %s" % (cypro['return_type'], ret_var)) - proc_call = "%s = %s" % (ret_var, proc_call) + ret_var_name = '__fwrap_return' + ret_varw = result_varw(node) + ret_lst.insert(0, ret_var_name) + ret_type = ret_varw.resolved_name + py_func.suite_body.putln("cdef %s.%s %s" % (CY_IMPORT_ALIAS, ret_type, ret_var_name)) + proc_call = "%s = %s" % (ret_var_name, proc_call) py_func.suite_body.putln(proc_call) if not ret_lst: @@ -695,7 +761,9 @@ class CyImplGenerator(GeneratorBase): self.gen_py_func(node) def copyto(self, fh): - self.import_code.root.putln("cimport %s as %s" % (PxdGenerator.make_fname(self.projname).split('.')[0], self.import_alias)) + self.import_code.root.putln("cimport %s as %s" % ( + PxdGenerator.make_fname(self.projname).split('.')[0], + CY_IMPORT_ALIAS)) self.import_code.root.putln("") self.import_code.copyto(fh) diff --git a/Tools/fwrap/WrapperArgs.py b/Tools/fwrap/WrapperArgs.py index c608c3156..9961c14e3 100644 --- a/Tools/fwrap/WrapperArgs.py +++ b/Tools/fwrap/WrapperArgs.py @@ -1,7 +1,7 @@ from fparser.block_statements import Function, SubProgramStatement, \ Module, Program, Subroutine, \ EndFunction, EndSubroutine, Interface -from utils import warning, mangle_prefix, valid_name +from utils import warning, mangle_prefix, valid_name, CY_IMPORT_ALIAS class WrapperError(Exception): pass @@ -291,6 +291,8 @@ class FortranWrapperVar(object): # XXX: user-defined type description here. + is_array = False + def __init__(self, var, disambig): self.disambig = disambig vkr = VarKindResolution(var) @@ -313,6 +315,28 @@ class FortranWrapperVar(object): # self.var is node.a.variables[node.result]: # self.intent = None + def get_py_arg_declarations(self): + return ((self.resolved_name, self.var.name),) + + def get_py_pass_argnames(self): + return (self.var.name,) + + def get_cy_arg_declarations(self): + return ((self.get_c_proto_types()[0], self.var.name),) + + def pre_call_cy_code(self, code): + pass + + def post_call_cy_code(self, code): + pass + + + def get_cy_pass_argnames(self): + return (self.var.name,) + + def generate_cy_declarations(self, code): + pass + def get_c_proto_types(self): if self.intent == INTENT_IN: ret = self.resolved_name @@ -320,13 +344,13 @@ class FortranWrapperVar(object): ret = self.resolved_name+'*' return [ret] - def get_argnames(self): + def get_fort_argnames(self): return [self.var.name] - def get_pass_argname(self): + def get_fort_pass_argnames_map(self): return (self.var.name, self.var.name) - def generate_declaration(self, code): + def generate_fort_declarations(self, code): var = self.var attributes = [] if self.intent is not None: @@ -345,10 +369,10 @@ class FortranWrapperVar(object): } code.putln(decl) - def pre_call_code(self, code): + def pre_call_fortran_code(self, code): pass - def post_call_code(self, code): + def post_call_fortran_code(self, code): pass class LogicalWrapperVar(FortranWrapperVar): @@ -367,23 +391,26 @@ class LogicalWrapperVar(FortranWrapperVar): self.log_var.is_arg = False def get_c_proto_types(self): - return [self.int_proxy.get_c_proto_type()] + if not self.int_proxy.is_value: + return [self.int_proxy.get_base_type_name() + '*'] + else: + return [self.int_proxy.get_base_type_name()] - def get_argnames(self): + def get_fort_argnames(self): return [self.int_proxy.name] - def get_pass_argname(self): + def get_fort_pass_argnames_map(self): return (self.var.name, self.log_var.name) - def generate_declaration(self, code): + def generate_fort_declarations(self, code): for entry in (self.int_proxy, self.log_var): - entry.generate_declaration(code) + entry.generate_fort_declaration(code) - def pre_call_code(self, code): + def pre_call_fortran_code(self, code): if self.intent in (INTENT_IN, INTENT_INOUT): code.putln("%s = logical(%s .ne. 0, kind=%s)" % (self.log_var.name, self.int_proxy.name, self.int_proxy.type.ktp)) - def post_call_code(self, code): + def post_call_fortran_code(self, code): if self.intent in (INTENT_INOUT, INTENT_OUT): code.putln("if (%s) then" % self.log_var.name ) code.putln(" %s = 1 " % self.int_proxy.name) @@ -403,12 +430,27 @@ class ComplexWrapperVar(FortranWrapperVar): class CharacterWrapperVar(FortranWrapperVar): pass +# class CyArrayWrapperVar(object): + + # def __init__(self, fort_wrapper_var, disambig): + # self.disambig = disambig + # self.fort_wrapper_var = fort_wrapper_var + # self.memview_in = Entry(self.disambig()+'mvs', fort_wrapper_var.var_entry.type) + # self.ndim = fort_wrapper_var.ndim + + # def get_signature_types(self): + # slice_decl = [":"]*self.ndim + # slice_decl[0] = "::1" + # return ["%s[%s]" % (self.memview_in.get_base_type_name(),','.join(slice_decl))] + +ARRAY_SHAPE_TYPE = "fwrap_ardim_long" class ArrayWrapperVar(FortranWrapperVar): def __init__(self, var, disambig): assert var.is_array() FortranWrapperVar.__init__(self, var, disambig) # array-specific initializations + self.is_array = True self.is_explicit_shape = var.is_explicit_shape_array() self.is_assumed_shape = var.is_assumed_shape_array() self.is_assumed_size = var.is_assumed_size_array() @@ -418,7 +460,12 @@ class ArrayWrapperVar(FortranWrapperVar): if not self.is_assumed_shape: raise NotImplementedError("only assumed shape arrays for now...") - self.shape_array = Entry(self.disambig()+self.var.name, FortranArrayType(FortranIntegerType(ktp="c_int"), (str(self.ndim),))) + self.shape_array = Entry(self.disambig()+self.var.name, + FortranArrayType(FortranIntegerType(ktp=ARRAY_SHAPE_TYPE), + (str(self.ndim),) + ) + ) + self.shape_array.is_arg = True self.shape_array.intent = INTENT_IN @@ -426,31 +473,73 @@ class ArrayWrapperVar(FortranWrapperVar): self.data_ptr.is_arg = True self.data_ptr.is_value = True - self.arr_proxy = Entry(self.disambig()+self.var.name, FortranArrayType(type_from_vkr(self.vkr), (('',''),)*self.ndim)) + self.arr_proxy = Entry(self.disambig()+self.var.name, + FortranArrayType(type_from_vkr(self.vkr), + (('',''),)*self.ndim)) self.arr_proxy.is_pointer = True self.var_entry = Entry(self.var.name, type_from_vkr(self.vkr)) + def get_py_arg_declarations(self): + return (('', self.var_entry.name),) + + def get_py_pass_argnames(self): + return (self.var_entry.name,) + + def get_cy_arg_declarations(self): + slice_decl = [":"]*self.ndim + slice_decl[0] = "::1" + return (("%s[%s]" % (self.var_entry.get_base_type_name(),','.join(slice_decl)), + self.var_entry.name),) + + def get_cy_pass_argnames(self): + return (self.data_ptr.name, self.shape_array.name) + + def generate_cy_declarations(self, code): + if not self.is_assumed_shape: + raise NotImplementedError("only assumed shape arrays for now...") + code.putln("cdef %s.%s *%s" % (CY_IMPORT_ALIAS, + self.var_entry.get_base_type_name(), + self.data_ptr.name)) + code.putln("cdef %s.%s *%s" % (CY_IMPORT_ALIAS, + self.shape_array.get_base_type_name(), + self.shape_array.name)) + + def pre_call_cy_code(self, code): + code.putln("%s = <%s.%s*>%s._data" % ( + self.data_ptr.name, + CY_IMPORT_ALIAS, + self.var_entry.get_base_type_name(), + self.var_entry.name)) + code.putln("%s = <%s.%s*>%s.shape" % ( + self.shape_array.name, + CY_IMPORT_ALIAS, + self.shape_array.get_base_type_name(), + self.var_entry.name)) + + def post_call_cy_code(self, code): + pass + def get_c_proto_types(self): - return [self.var_entry.get_c_proto_type(), self.shape_array.get_c_proto_type()] + return [self.var_entry.get_base_type_name()+'*', self.shape_array.get_base_type_name()+'*'] - def get_argnames(self): + def get_fort_argnames(self): return [self.data_ptr.name, self.shape_array.name] - def get_pass_argname(self): - return (self.var.name, self.arr_proxy.name) + def get_fort_pass_argnames_map(self): + return [self.var.name, self.arr_proxy.name] - def generate_declaration(self, code): + def generate_fort_declarations(self, code): if self.is_assumed_shape: for entry in (self.shape_array, self.data_ptr, self.arr_proxy): - entry.generate_declaration(code) + entry.generate_fort_declaration(code) else: raise NotImplementedError("only assumed shape arrays for now...") - def pre_call_code(self, code): + def pre_call_fortran_code(self, code): code.putln("call c_f_pointer(%s, %s, %s)" % (self.data_ptr.name, self.arr_proxy.name, self.shape_array.name)) - def post_call_code(self, code): + def post_call_fortran_code(self, code): pass class IntegerArrayWrapperVar(ArrayWrapperVar): @@ -466,7 +555,7 @@ class LogicalArrayWrapperVar(ArrayWrapperVar): pass _c_binding_to_c_type = { - 'c_int' : 'int', + # 'c_int' : 'int', } class Entry(object): @@ -479,7 +568,7 @@ class Entry(object): self.is_pointer = False self.intent = None - def generate_declaration(self, code): + def generate_fort_declaration(self, code): attributes = [] if self.is_pointer: attributes.append('pointer') @@ -491,12 +580,13 @@ class Entry(object): before_colons = [self.type.get_type_code()] + self.type.attributes + attributes code.putln("%s :: %s" % (",".join(before_colons), self.name)) - def get_c_proto_type(self): - ret = _c_binding_to_c_type.get(self.type.ktp, self.type.ktp) - if self.is_value: - return ret - else: - return ret+"*" + def get_base_type_name(self): + # ret = _c_binding_to_c_type.get(self.type.ktp, self.type.ktp) + return self.type.ktp + # if self.is_value: + # return ret + # else: + # return ret+"*" class FortranType(object): diff --git a/Tools/fwrap/runtests.py b/Tools/fwrap/runtests.py index c66b58913..4e92bb627 100644 --- a/Tools/fwrap/runtests.py +++ b/Tools/fwrap/runtests.py @@ -189,7 +189,8 @@ class FwrapCompileTestCase(unittest.TestCase): self.fwrap_config_source='genconfig.f95' self.fwrap_config_module_source='config.f95' self.fwrap_cython_source=self.projname+'.pyx' - wrap([self.filename], self.directory, self.workdir, self.projname) + fq_fname = os.path.join(os.path.abspath(self.directory), self.filename) + wrap([fq_fname], self.directory, self.workdir, self.projname) # self.runCompileTest() self.runCompileTest_distutils() diff --git a/Tools/fwrap/utils.py b/Tools/fwrap/utils.py index c4cceb4a3..92ea8d5ba 100644 --- a/Tools/fwrap/utils.py +++ b/Tools/fwrap/utils.py @@ -12,3 +12,5 @@ def warning(message, position=None, level=0, stream=sys.stderr): mangle_prefix = 'fwrap_' valid_name = re.compile(r'[a-z]\w+$',re.I).match + +CY_IMPORT_ALIAS = "__wf" |