123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- // Copyright (C) 2009 Per M.A. Bothner.
- // This is free software; for terms and warranty disclaimer see ../../COPYING.
- package kawa.standard;
- import kawa.lang.*;
- import gnu.expr.*;
- import gnu.lists.*;
- import gnu.kawa.io.FilePath;
- import gnu.kawa.io.Path;
- import gnu.mapping.*;
- import java.io.File;
- import gnu.bytecode.ObjectType;
- import java.util.*;
- import kawa.lang.Translator.FormStack;
- /** Implement R6RS import form.
- * This actually only implements simplified import;
- * we assumes it has been simplified by import macro defined in syntax.scm.
- */
- public class ImportFromLibrary extends Syntax
- {
- public static final ImportFromLibrary instance = new ImportFromLibrary();
- public static String[] classPrefixPath = { "", "kawa.lib." };
- private static final String BUILTIN = "<builtin>";
- private static final String MISSING = null;
- static final String[][] SRFI97Map = {
- { "1", "lists", "gnu.kawa.slib.srfi1" },
- { "2", "and-let*", "gnu.kawa.slib.srfi2" },
- { "5", "let", MISSING },
- { "6", "basic-string-ports", BUILTIN },
- { "8", "receive", Mangling.mangleQualifiedName("kawa.lib.srfi.8") },
- { "9", "records", BUILTIN },
- { "11", "let-values", BUILTIN },
- { "13", "strings", "gnu.kawa.slib.srfi13" },
- { "14", "char-sets", "gnu.kawa.slib.srfi14" },
- { "16", "case-lambda", BUILTIN },
- { "17", "generalized-set!", BUILTIN },
- { "18", "multithreading", MISSING },
- { "19", "time", MISSING },
- { "21", "real-time-multithreading", MISSING },
- { "23", "error", BUILTIN },
- { "25", "multi-dimensional-arrays", BUILTIN },
- { "26", "cut", "kawa.lib.srfi.26" },
- { "27", "random-bits", MISSING },
- { "28", "basic-format-strings", BUILTIN },
- { "29", "localization", MISSING },
- { "31", "rec", MISSING },
- { "35", "conditions", "gnu.kawa.slib.conditions" },
- { "37", "args-fold", "gnu.kawa.slib.srfi37" },
- { "38", "with-shared-structure", MISSING },
- { "39", "parameters", BUILTIN },
- // Note the default for (srfi :41) should be "streams". We put that last,
- // since the lable is searched from high index to low index.
- { "41", "streams.primitive", "gnu.kawa.slib.StreamsPrimitive" },
- { "41", "streams.derived", "gnu.kawa.slib.StreamsDerived" },
- { "41", "streams", "gnu.kawa.slib.Streams" },
- { "42", "eager-comprehensions", MISSING },
- { "43", "vectors", MISSING },
- { "44", "collections", MISSING },
- { "45", "lazy", MISSING },
- { "46", "syntax-rules", MISSING },
- { "47", "arrays", MISSING },
- { "48", "intermediate-format-strings", MISSING },
- { "51", "rest-values", MISSING },
- { "54", "cat", MISSING },
- { "57", "records", MISSING },
- { "59", "vicinities", MISSING },
- { "60", "integer-bits", "gnu.kawa.slib.srfi60" },
- { "61", "cond", MISSING },
- { "63", "arrays", MISSING },
- { "64", "testing", "gnu.kawa.slib.testing" },
- { "66", "octet-vectors", MISSING },
- { "67", "compare-procedures", MISSING },
- { "69", "basic-hash-tables", "gnu.kawa.slib.srfi69" },
- { "71", "let", MISSING },
- { "74", "blobs", MISSING },
- { "78", "lightweight-testing", MISSING },
- { "86", "mu-and-nu", MISSING },
- { "87", "case", BUILTIN },
- { "95", "sorting-and-merging",
- Mangling.mangleQualifiedName("kawa.lib.srfi.95") },
- { "98", "os-environment-variables", BUILTIN },
- { "101", "random-access-lists", "gnu.kawa.slib.ralists" }
- };
- @Override
- public void scanForm(Pair st, ScopeExp defs, Translator tr) {
- Object obj = st.getCdr();
- while (obj instanceof Pair) {
- Pair pair = (Pair) obj;
- Object save1 = tr.pushPositionOf(pair);
- scanImportSet(pair.getCar(), defs, tr, null);
- tr.popPositionOf(save1);
- obj = pair.getCdr();
- }
- if (obj != LList.Empty) tr.error('e', "improper list");
- }
- public static String checkSrfi(String lname, Translator tr) {
- if (lname.startsWith("srfi.")) {
- String demangled =
- Mangling.demangleQualifiedName(lname.substring(5));
- int dot = demangled.indexOf('.');
- String srfiName;
- StringBuilder badNameBuffer = null;
- if (dot < 0) {
- srfiName = null;
- dot = demangled.length();
- } else
- srfiName = demangled.substring(dot+1);
- String srfiNumber = null;
- if (dot > 0) {
- int numStart = demangled.charAt(0) == ':' ? 1 : 0;
- for (int i = numStart; ; i++) {
- if (i == dot) {
- srfiNumber = demangled.substring(numStart, dot);
- break;
- }
- if (Character.digit(demangled.charAt(i), 10) < 0)
- break;
- }
- }
- if (srfiNumber == null) {
- tr.error('e', "SRFI library reference must have the form: (srfi NNN [name]) or (srfi :NNN [name])");
- return lname;
- }
- int srfiIndex = SRFI97Map.length;
- for (;;) {
- if (--srfiIndex < 0) {
- break;
- }
- if (!SRFI97Map[srfiIndex][0].equals(srfiNumber))
- continue;
- String srfiNameExpected = SRFI97Map[srfiIndex][1];
- String srfiClass = SRFI97Map[srfiIndex][2];
- if (srfiName == null || srfiName.equals(srfiNameExpected))
- return srfiClass != MISSING ? srfiClass : lname;
- if (badNameBuffer == null) {
- badNameBuffer = new StringBuilder("the name of SRFI ");
- badNameBuffer.append(srfiNumber);
- badNameBuffer.append(" should be '");
- }
- else
- badNameBuffer.append(" or '");
- badNameBuffer.append(srfiNameExpected);
- badNameBuffer.append('\'');
- }
- if (badNameBuffer != null) {
- tr.error('e', badNameBuffer.toString());
- return BUILTIN;
- }
- }
- return lname;
- }
- void scanImportSet(Object imports, ScopeExp defs, Translator tr, require.DeclSetMapper mapper) {
- if (imports instanceof SimpleSymbol) {
- String sname = imports.toString();
- handleImport(sname, null,
- Mangling.mangleQualifiedName(sname),
- defs, tr, mapper);
- return;
- }
- int specLength = Translator.listLength(imports);
- if (specLength <= 0) {
- Object save1 = tr.pushPositionOf(imports);
- tr.error('e', "import specifier is not a proper list");
- tr.popPositionOf(save1);
- return;
- }
- Pair pimport = (Pair) imports;
- Object first = pimport.getCar();
- Object rest = pimport.getCdr();
- Pair cdrPair = specLength >= 2 ? (Pair) rest : null;
- char kind = '\0';
- if (first == onlySymbol)
- kind = 'O';
- else if (first == exceptSymbol)
- kind = 'E';
- else if (first == renameSymbol)
- kind = 'R';
- else if (first == prefixSymbol)
- kind = 'P';
- else if (first == librarySymbol && specLength == 2
- && cdrPair.getCar() instanceof Pair)
- pimport = (Pair) cdrPair.getCar();
- else if (first == classSymbol && specLength >= 2
- && cdrPair.getCar() instanceof SimpleSymbol) {
- Map<Symbol, Expression> decls =
- new LinkedHashMap<Symbol, Expression>();
- SimpleSymbol name1 = (SimpleSymbol) cdrPair.getCar();
- String prefix = name1.getName();
- if (prefix.length() > 0)
- prefix = prefix + '.';
- rest = cdrPair.getCdr();
- if (rest == LList.Empty) {
- tr.error('e', "class-prefix must be followed by class-names");
- }
- while (rest != LList.Empty) {
- cdrPair = (Pair) rest;
- Object part2 = cdrPair.getCar();
- String cname = null;
- SimpleSymbol dname = null;
- if (part2 instanceof SimpleSymbol) {
- dname = (SimpleSymbol) part2;
- String str2 = dname.getName();
- cname = prefix+str2;
- } else if (part2 instanceof Pair
- && Translator.listLength(part2) == 2) {
- Pair rpair1 = (Pair) part2;
- Pair rpair2 = (Pair) rpair1.getCdr();
- Object rname1 = rpair1.getCar();
- Object rname2 = rpair2.getCar();
- if (rname1 instanceof SimpleSymbol
- && rname2 instanceof SimpleSymbol) {
- cname = prefix + ((SimpleSymbol) rname1).getName();
- dname = (SimpleSymbol) rname2;
- }
- }
- if (dname == null) {
- tr.error('e', "imported class-name must be NAME or (NAME NEW-NAME)");
- } else {
- try {
- Class clas = ObjectType.getContextClass(cname);
- decls.put(dname, tr.makeQuoteExp(clas));
- } catch (ClassNotFoundException ex) {
- Object savePos = tr.pushPositionOf(cdrPair);
- tr.error('e', "no class found named "+cname);
- tr.popPositionOf(savePos);
- }
- }
- rest = cdrPair.getCdr();
- }
- if (mapper != null)
- decls = mapper.map(decls, tr);
- for (Map.Entry<Symbol,Expression> entry : decls.entrySet()) {
- Symbol aname = entry.getKey();
- Declaration decl = tr.define(aname, defs);
- decl.setAlias(true);
- decl.setFlag(Declaration.IS_CONSTANT|Declaration.EARLY_INIT);
- SetExp sexp = new SetExp(decl, entry.getValue());
- tr.setLineOf(sexp);
- decl.noteValueFromSet(sexp);
- sexp.setDefining (true);
- tr.formStack.push(sexp);
- }
- return;
- }
- if (specLength >= 2 && kind != '\0') {
- ImportSetMapper nmapper
- = new ImportSetMapper(kind, cdrPair.getCdr(), specLength-2);
- nmapper.chain = mapper;
- scanImportSet(cdrPair.getCar(), defs, tr, nmapper);
- return;
- }
- scanImportSet1(pimport, defs, tr, mapper);
- }
- boolean scanImportSet1(Object libref, ScopeExp defs, Translator tr, require.DeclSetMapper mapper) {
- String explicitSource = null;
- Object versionSpec = null;
- StringBuilder cbuf = new StringBuilder(); // for class name
- StringBuilder sbuf = new StringBuilder(); // for source file name
- while (libref instanceof Pair) {
- Pair pair = (Pair) libref;
- Object car = pair.getCar();
- Object cdr = pair.getCdr();
- if (car instanceof Pair) {
- if (versionSpec != null) {
- tr.error('e', "duplicate version reference - was "+versionSpec);
- }
- versionSpec = car;
- } else if (car instanceof String) {
- if (cdr instanceof Pair)
- tr.error('e', "source specifier must be last element in library reference");
- explicitSource = (String) car;
- } else {
- if (cbuf.length() > 0)
- cbuf.append('.');
- if (sbuf.length() > 0)
- sbuf.append('/');
- String part = car.toString();
- cbuf.append(Mangling.mangleClassName(part));
- sbuf.append(part);
- }
- libref = cdr;
- }
- return handleImport(sbuf.toString(), explicitSource,
- cbuf.toString(),
- defs, tr, mapper);
- }
- /** Do the actual work of importing a module.
- * @param implicitSource Source name inferred from library name,
- * with '/' as separator. Does not include a file extension.
- * @param explicitSource If non-null, an exlicitly specified
- * source file name.
- */
- public static boolean handleImport(String implicitSource, String explicitSource, String requestedClass, ScopeExp defs, Translator tr, require.DeclSetMapper mapper) {
- boolean checkExistsOnly = defs == null;
- ModuleManager mmanager = ModuleManager.getInstance();
- ModuleInfo minfo = null;
- String lname = checkSrfi(requestedClass, tr);
- if (lname == BUILTIN)
- return true; // nothing to do
- boolean foundSrfi = lname != requestedClass;
- int classPrefixPathLength = classPrefixPath.length;
- Class existingClass = null;
- for (int i = 0; i < classPrefixPathLength; i++) {
- String tname = classPrefixPath[i] + lname;
- minfo = mmanager.searchWithClassName(tname);
- if (minfo != null)
- break;
- try {
- existingClass = ObjectType.getContextClass(tname);
- break;
- } catch (Exception ex) {
- } catch (NoClassDefFoundError ex) {
- }
- }
- ModuleInfo curinfo = tr.getMinfo();
- Path currentSource = curinfo.getSourceAbsPath();
- String currentExtension = currentSource == null ? null
- : currentSource.getExtension();
- if (currentExtension == null) {
- List<String> langExtensions = tr.getLanguage().getExtensions();
- if (! langExtensions.isEmpty())
- currentExtension = langExtensions.get(0);
- }
- boolean hasDot;
- boolean isAbsolute;
- if (explicitSource != null) {
- hasDot = explicitSource.indexOf("./") >= 0;
- isAbsolute = Path.valueOf(explicitSource).isAbsolute();
- } else {
- hasDot = false;
- isAbsolute = false;
- }
- String currentClassName = curinfo.getClassName();
- // Is the current module a file - as opposed to (say) a tty?
- boolean currentIsFile = currentSource != null
- && currentSource.isPlainFile();
- Path currentRoot = currentIsFile ? currentSource.getDirectory()
- : Path.currentPath();
- if (currentIsFile
- && ! (explicitSource != null && (hasDot || isAbsolute))) {
- int currentDots = 0;
- String prefix = currentClassName != null ? currentClassName
- : tr.classPrefix != null ? tr.classPrefix : "";
- for (int i = prefix.length(); --i >= 0; )
- if (prefix.charAt(i) == '.')
- currentDots++;
- if (currentDots > 0) {
- StringBuilder ups = new StringBuilder("..");
- for (int i = currentDots; -- i > 0; )
- ups.append("/..");
- currentRoot = currentRoot.resolve(ups.toString());
- }
- }
- List<CharSequence> srcSearchPath;
- boolean skipSourceSearch = minfo != null && explicitSource == null;
- if (isAbsolute || hasDot || skipSourceSearch) {
- srcSearchPath = new ArrayList<CharSequence>();
- if (! skipSourceSearch)
- srcSearchPath.add(currentRoot.toString());
- }
- else
- srcSearchPath = getImportSearchPath();
- String pathStr = null;
- for (CharSequence searchElement : srcSearchPath) {
- if (isAbsolute)
- pathStr = explicitSource;
- else {
- String pathElement = searchElement.toString();
- int selectorEnd;
- int star;
- int prefixLength = 0;
- StringBuilder pbuf = new StringBuilder();
- if (pathElement.length() >= 3
- && pathElement.charAt(0) == '<'
- && (selectorEnd = pathElement.indexOf('>')+1) > 0) {
- StringBuilder prefixBuf = new StringBuilder();
- boolean slashNeeded = false;
- for (int i = 1; i < selectorEnd-1; i++) {
- char ch = pathElement.charAt(i);
- if (ch == ' ') {
- if (prefixBuf.length() > 0)
- slashNeeded = true;
- } else {
- if (slashNeeded)
- prefixBuf.append('/');
- prefixBuf.append(ch);
- prefixLength += slashNeeded ? 2 : 1;
- slashNeeded = false;
- }
- }
- if (! implicitSource.startsWith(prefixBuf.toString()))
- continue;
- if (implicitSource.length() != prefixLength) {
- if (implicitSource.charAt(prefixLength) != '/')
- continue;
- prefixLength++;
- }
- star = pathElement.indexOf('*', selectorEnd);
- if (star < 0) {
- pathElement = pathElement.substring(selectorEnd);
- }
- } else { // No "<...>..." selector
- star = pathElement.indexOf('*');
- selectorEnd = 0;
- if (foundSrfi && explicitSource == null)
- continue;
- }
- if (star >= 0) {
- pbuf.append(pathElement.substring(selectorEnd, star));
- pbuf.append(implicitSource.substring(prefixLength));
- pbuf.append(pathElement.substring(star+1));
- } else {
- if (! ".".equals(pathElement)) {
- pbuf.append(pathElement);
- pbuf.append('/');
- }
- if (explicitSource != null)
- pbuf.append(explicitSource);
- else {
- pbuf.append(implicitSource);
- if (currentExtension != null) {
- pbuf.append('.');
- pbuf.append(currentExtension);
- }
- }
- }
- pathStr = pbuf.toString();
- }
- Path path = currentRoot.resolve(pathStr).getCanonical();
- // Might be more efficient to first check the ModuleManager,
- // before asking the file-system. FIXME
- long lastModifiedTime = path.getLastModified();
- if (lastModifiedTime != 0) {
- if (checkExistsOnly)
- return true;
- if (minfo != null) {
- String pstring = path.toString();
- Path infoPath = minfo.getSourceAbsPath();
- if (infoPath == null
- || ! (pstring.equals(infoPath.toString()))) {
- tr.error('w', "ignoring source file at "+pstring
- +" - instead using class "+minfo.getClassName()
- +(infoPath==null?""
- :(" from "+infoPath.toString())));
- }
- } else
- minfo = mmanager.findWithSourcePath(path, pathStr);
- // Should save lastModifiedTime in minfo FIXME
- if (foundSrfi)
- lname = requestedClass;
- break;
- }
- }
- if (checkExistsOnly)
- return existingClass != null || minfo != null;
- if (existingClass != null) {
- if (minfo == null)
- minfo = mmanager.findWithClass(existingClass);
- else
- minfo.setModuleClass(existingClass);
- }
- if (minfo == null)
- tr.error('e', "unknown library ("+implicitSource.replace('/', ' ')+")");
- else
- require.importDefinitions(lname, minfo, mapper,
- tr.formStack, defs, tr);
- return minfo != null;
- }
- public Expression rewriteForm(Pair form, Translator tr) {
- return tr.syntaxError(getName()+" is only allowed in a <body>");
- }
- static class ImportSetMapper implements require.DeclSetMapper {
- char kind;
- Object list;
- int listLength;
- require.DeclSetMapper chain;
- public ImportSetMapper(char kind, Object list, int listLength) {
- this.kind = kind;
- this.list = list;
- this.listLength = listLength;
- }
- public Map<Symbol, Expression> map(Map<Symbol, Expression> decls, Compilation comp) {
- Translator tr = (Translator) comp;
- Object lst = this.list;
- Map<Symbol,Expression> nmap = decls;
- switch (kind) {
- case 'E': // 'except; list has the form (name ...)
- case 'O': // 'only; list has the form (name ...)
- if (kind == 'O')
- nmap = new LinkedHashMap<Symbol,Expression>();
- while (lst instanceof Pair) {
- Pair pair = (Pair) lst;
- Object save1 = tr.pushPositionOf(pair);
- Object name = Translator.stripSyntax(pair.getCar());
- name = tr.namespaceResolve(name);
- Symbol oldsym = null;
- Symbol newsym = null;
- if (name instanceof Symbol) {
- oldsym = (Symbol) name;
- newsym = oldsym;
- } else if (kind == 'O' && name instanceof Pair
- && Translator.listLength(name) == 2) {
- Pair rpair1 = (Pair) name;
- Object rname1 = rpair1.getCar();
- Object rname2 = ((Pair) rpair1.getCdr()).getCar();
- rname1 = tr.namespaceResolve(rname1);
- rname2 = tr.namespaceResolve(rname2);
- if (rname1 instanceof Symbol
- && rname2 instanceof Symbol) {
- oldsym = (Symbol) rname1;
- newsym = (Symbol) rname2;
- }
- }
- if (oldsym == null)
- tr.error('e', "non-symbol in name list");
- else {
- Expression old = decls.get(oldsym);
- if (old == null)
- tr.error(kind == 'E' ? 'w' : 'e',
- "unknown symbol in import set: "+oldsym);
- else if (kind == 'E')
- nmap.remove(oldsym);
- else
- nmap.put(newsym, old);
- }
- tr.popPositionOf(save1);
- lst = pair.getCdr();
- }
- break;
- case 'R': // 'rename; list has the form: ((oldname newname) ...)
- Symbol[] pendingSymbols = new Symbol[listLength];
- Expression[] pendingDecls = new Expression[listLength];
- int npending = 0;
- while (lst instanceof Pair) {
- Pair pair = (Pair) lst;
- Object save1 = tr.pushPositionOf(pair);
- Object entry = pair.getCar();
- int entryLen = Translator.listLength(entry);
- if (entryLen == 2) {
- Pair p1 = (Pair) entry;
- Object oldname = p1.getCar();
- Object newname = ((Pair) p1.getCdr()).getCar();
- if (oldname instanceof Symbol
- && newname instanceof Symbol) {
- Symbol oldSymbol = (Symbol) oldname;
- Symbol newSymbol = (Symbol) newname;
- Expression oldValue = decls.remove(oldSymbol);
- if (oldValue == null)
- tr.error('e', "missing binding "+oldSymbol);
- else {
- pendingSymbols[npending] = newSymbol;
- pendingDecls[npending] = oldValue;
- npending++;
- }
- }
- else
- entryLen = -1;
- }
- if (entryLen != 2)
- tr.error('e', "entry is not a pair of names");
- tr.popPositionOf(save1);
- lst = pair.getCdr();
- }
- for (int i = 0; i < npending; i++) {
- Symbol newSymbol = pendingSymbols[i];
- Expression decl = pendingDecls[i];
- if (decls.put(newSymbol, decl) != null)
- tr.error('e', "duplicate binding for "+newSymbol);
- }
- break;
- case 'P': // 'prefix; list has the form: (name-prefix)
- nmap = new LinkedHashMap<Symbol,Expression>();
- if (listLength != 1
- || ! (((Pair) list).getCar() instanceof SimpleSymbol))
- tr.error('e', "bad syntax for prefix import specifier");
- else {
- String prefix
- = ((SimpleSymbol) ((Pair) list).getCar()).getName();
- for (Map.Entry<Symbol,Expression> entry : decls.entrySet()) {
- Symbol aname = entry.getKey();
- Expression old = entry.getValue();
- Symbol nname = Symbol.valueOf(prefix+aname);
- nmap.put(nname, old);
- }
- }
- break;
- }
- if (chain != null)
- nmap = chain.map(nmap, tr);
- return nmap;
- }
- }
- /** Check if library (in r7rs import syntax) exists.
- * @return if library exists: class name of (existing) library class,
- * or the special BUILTIN value; otherwise null.
- */
- public boolean libraryExists(Object list, Translator tr) {
- ModuleManager mmanager = ModuleManager.getInstance();
- return scanImportSet1(list, null, tr, null);
- }
- public static final ThreadLocal<List<CharSequence>> searchPath
- = new InheritableThreadLocal<List<CharSequence>>();
- public static List<CharSequence> getImportSearchPath() {
- return Include.getSearchPath(searchPath, "kawa.import.path", ".");
- }
- public static final SimpleSymbol classSymbol = Symbol.valueOf("class");
- public static final SimpleSymbol exceptSymbol = Symbol.valueOf("except");
- public static final SimpleSymbol librarySymbol = Symbol.valueOf("library");
- public static final SimpleSymbol onlySymbol = Symbol.valueOf("only");
- public static final SimpleSymbol prefixSymbol = Symbol.valueOf("prefix");
- public static final SimpleSymbol renameSymbol = Symbol.valueOf("rename");
- }
|