001: /*
002: * Closure.java
003: *
004: * Copyright (C) 2002-2003 Peter Graves
005: * $Id: Closure.java,v 1.6 2003/11/15 11:03:31 beedlem Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.lisp;
023:
024: import java.util.ArrayList;
025:
026: public class Closure extends Function {
027: // Parameter types.
028: private static final int REQUIRED = 0;
029: private static final int OPTIONAL = 1;
030: private static final int KEYWORD = 2;
031: private static final int REST = 3;
032: private static final int AUX = 4;
033:
034: // States.
035: private static final int STATE_REQUIRED = 0;
036: private static final int STATE_OPTIONAL = 1;
037: private static final int STATE_KEYWORD = 2;
038: private static final int STATE_REST = 3;
039: private static final int STATE_AUX = 4;
040:
041: private final LispObject lambdaList;
042: private final Parameter[] requiredParameters;
043: private final Parameter[] optionalParameters;
044: private final Parameter[] keywordParameters;
045: private final Parameter[] auxVars;
046: private final LispObject body;
047: private final Environment environment;
048: private final boolean allowOtherKeys;
049: private Symbol restVar;
050: private Symbol envVar;
051: private int arity;
052:
053: private int minArgs;
054: private int maxArgs;
055:
056: private final Symbol[] variables;
057:
058: public Closure(LispObject lambdaList, LispObject body,
059: Environment env) throws ConditionThrowable {
060: this (null, lambdaList, body, env);
061: }
062:
063: public Closure(String name, LispObject lambdaList, LispObject body,
064: Environment env) throws ConditionThrowable {
065: super (name, getCurrentPackage());
066: this .lambdaList = lambdaList;
067: Debug.assertTrue(lambdaList == NIL
068: || lambdaList instanceof Cons);
069: boolean allowOtherKeys = false;
070: if (lambdaList instanceof Cons) {
071: final int length = lambdaList.length();
072: ArrayList required = null;
073: ArrayList optional = null;
074: ArrayList keywords = null;
075: ArrayList aux = null;
076: int state = STATE_REQUIRED;
077: LispObject remaining = lambdaList;
078: while (remaining != NIL) {
079: LispObject obj = remaining.car();
080: if (obj instanceof Symbol) {
081: if (state == STATE_AUX) {
082: if (aux == null)
083: aux = new ArrayList();
084: aux.add(new Parameter((Symbol) obj, NIL, AUX));
085: } else if (obj == Symbol.AND_OPTIONAL) {
086: state = STATE_OPTIONAL;
087: arity = -1;
088: } else if (obj == Symbol.AND_REST
089: || obj == Symbol.AND_BODY) {
090: state = STATE_REST;
091: arity = -1;
092: maxArgs = -1;
093: remaining = remaining.cdr();
094: if (remaining == NIL)
095: throw new ConditionThrowable(
096: new LispError(
097: "&REST/&BODY must be followed by a variable"));
098: Debug.assertTrue(restVar == null);
099: try {
100: restVar = (Symbol) remaining.car();
101: } catch (ClassCastException e) {
102: throw new ConditionThrowable(
103: new LispError(
104: "&REST/&BODY must be followed by a variable"));
105: }
106: } else if (obj == Symbol.AND_ENVIRONMENT) {
107: remaining = remaining.cdr();
108: envVar = (Symbol) remaining.car();
109: arity = -1; // FIXME
110: } else if (obj == Symbol.AND_KEY) {
111: state = STATE_KEYWORD;
112: arity = -1;
113: } else if (obj == Symbol.AND_ALLOW_OTHER_KEYS) {
114: allowOtherKeys = true;
115: maxArgs = -1;
116: } else if (obj == Symbol.AND_AUX) {
117: // All remaining specifiers are aux variable specifiers.
118: state = STATE_AUX;
119: arity = -1; // FIXME
120: } else {
121: if (state == STATE_OPTIONAL) {
122: if (optional == null)
123: optional = new ArrayList();
124: optional.add(new Parameter((Symbol) obj,
125: NIL, OPTIONAL));
126: if (maxArgs >= 0)
127: ++maxArgs;
128: } else if (state == STATE_KEYWORD) {
129: if (keywords == null)
130: keywords = new ArrayList();
131: keywords.add(new Parameter((Symbol) obj,
132: NIL, KEYWORD));
133: if (maxArgs >= 0)
134: maxArgs += 2;
135: } else {
136: Debug.assertTrue(state == STATE_REQUIRED);
137: if (required == null)
138: required = new ArrayList();
139: required.add(new Parameter((Symbol) obj));
140: if (maxArgs >= 0)
141: ++maxArgs;
142: }
143: }
144: } else if (obj instanceof Cons) {
145: if (state == STATE_AUX) {
146: Symbol symbol = checkSymbol(obj.car());
147: LispObject initForm = obj.cadr();
148: Debug.assertTrue(initForm != null);
149: if (aux == null)
150: aux = new ArrayList();
151: aux.add(new Parameter(symbol, initForm, AUX));
152: } else if (state == STATE_OPTIONAL) {
153: Symbol symbol = checkSymbol(obj.car());
154: LispObject initForm = obj.cadr();
155: LispObject svar = obj.cdr().cdr().car();
156: if (optional == null)
157: optional = new ArrayList();
158: optional.add(new Parameter(symbol, initForm,
159: svar, OPTIONAL));
160: if (maxArgs >= 0)
161: ++maxArgs;
162: } else if (state == STATE_KEYWORD) {
163: Symbol keyword;
164: Symbol var;
165: LispObject initForm = NIL;
166: LispObject svar = NIL;
167: LispObject first = obj.car();
168: if (first instanceof Cons) {
169: keyword = checkSymbol(first.car());
170: var = checkSymbol(first.cadr());
171: } else {
172: var = checkSymbol(first);
173: keyword = PACKAGE_KEYWORD.intern(var
174: .getName());
175: }
176: obj = obj.cdr();
177: if (obj != NIL) {
178: initForm = obj.car();
179: obj = obj.cdr();
180: if (obj != NIL)
181: svar = obj.car();
182: }
183: if (keywords == null)
184: keywords = new ArrayList();
185: keywords.add(new Parameter(keyword, var,
186: initForm, svar));
187: if (maxArgs >= 0)
188: maxArgs += 2;
189: } else
190: invalidParameter(obj);
191: } else
192: invalidParameter(obj);
193: remaining = remaining.cdr();
194: }
195: if (arity == 0)
196: arity = length;
197: if (required != null) {
198: requiredParameters = new Parameter[required.size()];
199: required.toArray(requiredParameters);
200: } else
201: requiredParameters = null;
202: if (optional != null) {
203: optionalParameters = new Parameter[optional.size()];
204: optional.toArray(optionalParameters);
205: } else
206: optionalParameters = null;
207: if (keywords != null) {
208: keywordParameters = new Parameter[keywords.size()];
209: keywords.toArray(keywordParameters);
210: } else
211: keywordParameters = null;
212: if (aux != null) {
213: auxVars = new Parameter[aux.size()];
214: aux.toArray(auxVars);
215: } else
216: auxVars = null;
217: } else {
218: // Lambda list is empty.
219: Debug.assertTrue(lambdaList == NIL);
220: requiredParameters = null;
221: optionalParameters = null;
222: keywordParameters = null;
223: auxVars = null;
224: arity = 0;
225: minArgs = maxArgs = 0;
226: }
227: this .body = body;
228: this .environment = env;
229: this .allowOtherKeys = allowOtherKeys;
230: minArgs = requiredParameters != null ? requiredParameters.length
231: : 0;
232: if (arity >= 0)
233: Debug.assertTrue(arity == minArgs);
234: variables = processVariables();
235: }
236:
237: private final Symbol[] processVariables() {
238: ArrayList vars = new ArrayList();
239: if (requiredParameters != null) {
240: for (int i = 0; i < requiredParameters.length; i++)
241: vars.add(requiredParameters[i].var);
242: }
243: if (optionalParameters != null) {
244: for (int i = 0; i < optionalParameters.length; i++) {
245: vars.add(optionalParameters[i].var);
246: if (optionalParameters[i].svar != NIL)
247: vars.add(optionalParameters[i].svar);
248: }
249: }
250: if (restVar != null) {
251: vars.add(restVar);
252: }
253: if (keywordParameters != null) {
254: for (int i = 0; i < keywordParameters.length; i++) {
255: vars.add(keywordParameters[i].var);
256: if (keywordParameters[i].svar != NIL)
257: vars.add(keywordParameters[i].svar);
258: }
259: }
260: Symbol[] array = new Symbol[vars.size()];
261: vars.toArray(array);
262: return array;
263: }
264:
265: private static final void invalidParameter(LispObject obj)
266: throws ConditionThrowable {
267: throw new ConditionThrowable(new LispError(String.valueOf(obj)
268: + " may not be used as a variable in a lambda list"));
269: }
270:
271: public LispObject typep(LispObject typeSpecifier)
272: throws ConditionThrowable {
273: if (typeSpecifier == Symbol.COMPILED_FUNCTION)
274: return NIL;
275: return super .typep(typeSpecifier);
276: }
277:
278: public final LispObject getParameterList() {
279: return lambdaList;
280: }
281:
282: public final LispObject getVariableList() {
283: LispObject result = NIL;
284: if (variables != null) {
285: for (int i = variables.length; i-- > 0;)
286: result = new Cons(variables[i], result);
287: }
288: return result;
289: }
290:
291: // Returns body as a list.
292: public final LispObject getBody() {
293: return body;
294: }
295:
296: public final Environment getEnvironment() {
297: return environment;
298: }
299:
300: public LispObject execute() throws ConditionThrowable {
301: if (arity == 0) {
302: final LispThread thread = LispThread.currentThread();
303: LispObject result = NIL;
304: LispObject prog = body;
305: while (prog != NIL) {
306: result = eval(prog.car(), environment, thread);
307: prog = prog.cdr();
308: }
309: return result;
310: } else
311: return execute(new LispObject[0]);
312: }
313:
314: public LispObject execute(LispObject arg) throws ConditionThrowable {
315: if (minArgs == 1) {
316: final LispThread thread = LispThread.currentThread();
317: Environment oldDynEnv = thread.getDynamicEnvironment();
318: Environment ext = new Environment(environment);
319: bind(requiredParameters[0].var, arg, ext);
320: if (arity != 1) {
321: if (optionalParameters != null)
322: bindOptionalParameterDefaults(ext, thread);
323: if (restVar != null)
324: bind(restVar, NIL, ext);
325: if (keywordParameters != null)
326: bindKeywordParameterDefaults(ext, thread);
327: }
328: if (auxVars != null)
329: bindAuxVars(ext, thread);
330: LispObject result = NIL;
331: LispObject prog = body;
332: while (prog != NIL) {
333: result = eval(prog.car(), ext, thread);
334: prog = prog.cdr();
335: }
336: thread.setDynamicEnvironment(oldDynEnv);
337: return result;
338: } else {
339: LispObject[] args = new LispObject[1];
340: args[0] = arg;
341: return execute(args);
342: }
343: }
344:
345: public LispObject execute(LispObject first, LispObject second)
346: throws ConditionThrowable {
347: if (minArgs == 2) {
348: final LispThread thread = LispThread.currentThread();
349: Environment oldDynEnv = thread.getDynamicEnvironment();
350: Environment ext = new Environment(environment);
351: bind(requiredParameters[0].var, first, ext);
352: bind(requiredParameters[1].var, second, ext);
353: if (arity != 2) {
354: if (optionalParameters != null)
355: bindOptionalParameterDefaults(ext, thread);
356: if (restVar != null)
357: bind(restVar, NIL, ext);
358: if (keywordParameters != null)
359: bindKeywordParameterDefaults(ext, thread);
360: }
361: if (auxVars != null)
362: bindAuxVars(ext, thread);
363: LispObject result = NIL;
364: LispObject prog = body;
365: while (prog != NIL) {
366: result = eval(prog.car(), ext, thread);
367: prog = prog.cdr();
368: }
369: thread.setDynamicEnvironment(oldDynEnv);
370: return result;
371: } else {
372: LispObject[] args = new LispObject[2];
373: args[0] = first;
374: args[1] = second;
375: return execute(args);
376: }
377: }
378:
379: public LispObject execute(LispObject first, LispObject second,
380: LispObject third) throws ConditionThrowable {
381: if (minArgs == 3) {
382: final LispThread thread = LispThread.currentThread();
383: Environment oldDynEnv = thread.getDynamicEnvironment();
384: Environment ext = new Environment(environment);
385: bind(requiredParameters[0].var, first, ext);
386: bind(requiredParameters[1].var, second, ext);
387: bind(requiredParameters[2].var, third, ext);
388: if (arity != 3) {
389: if (optionalParameters != null)
390: bindOptionalParameterDefaults(ext, thread);
391: if (restVar != null)
392: bind(restVar, NIL, ext);
393: if (keywordParameters != null)
394: bindKeywordParameterDefaults(ext, thread);
395: }
396: if (auxVars != null)
397: bindAuxVars(ext, thread);
398: LispObject result = NIL;
399: LispObject prog = body;
400: while (prog != NIL) {
401: result = eval(prog.car(), ext, thread);
402: prog = prog.cdr();
403: }
404: thread.setDynamicEnvironment(oldDynEnv);
405: return result;
406: } else {
407: LispObject[] args = new LispObject[3];
408: args[0] = first;
409: args[1] = second;
410: args[2] = third;
411: return execute(args);
412: }
413: }
414:
415: public LispObject execute(LispObject args, Environment env)
416: throws ConditionThrowable {
417: LispObject array[] = new LispObject[2];
418: array[0] = args;
419: array[1] = env;
420: return execute(array);
421: }
422:
423: public LispObject execute(LispObject[] args)
424: throws ConditionThrowable {
425: final LispThread thread = LispThread.currentThread();
426: Environment oldDynEnv = thread.getDynamicEnvironment();
427: Environment ext = new Environment(environment);
428: args = processArgs(args);
429: Debug.assertTrue(args.length == variables.length);
430: for (int i = 0; i < variables.length; i++)
431: bind(variables[i], args[i], ext);
432: if (auxVars != null)
433: bindAuxVars(ext, thread);
434: LispObject result = NIL;
435: LispObject prog = body;
436: while (prog != NIL) {
437: result = eval(prog.car(), ext, thread);
438: prog = prog.cdr();
439: }
440: thread.setDynamicEnvironment(oldDynEnv);
441: return result;
442: }
443:
444: protected LispObject[] processArgs(LispObject[] args)
445: throws ConditionThrowable {
446: final LispThread thread = LispThread.currentThread();
447: if (arity >= 0) {
448: // Fixed arity.
449: if (args.length != arity)
450: throw new ConditionThrowable(
451: new WrongNumberOfArgumentsException(this ));
452: return args;
453: }
454: // Not fixed arity.
455: if (args.length < minArgs)
456: throw new ConditionThrowable(
457: new WrongNumberOfArgumentsException(this ));
458: Environment oldDynEnv = thread.getDynamicEnvironment();
459: Environment ext = new Environment(environment);
460: LispObject[] array = new LispObject[variables.length];
461: int index = 0;
462: // Required parameters.
463: if (requiredParameters != null) {
464: for (int i = 0; i < minArgs; i++) {
465: bind(requiredParameters[i].var, args[i], ext);
466: array[index++] = args[i];
467: }
468: }
469: int i = minArgs;
470: int argsUsed = minArgs;
471: // Optional parameters.
472: if (optionalParameters != null) {
473: for (int j = 0; j < optionalParameters.length; j++) {
474: Parameter parameter = optionalParameters[j];
475: if (i < args.length) {
476: bind(parameter.var, args[i], ext);
477: array[index++] = args[i];
478: ++argsUsed;
479: if (parameter.svar != NIL) {
480: bind((Symbol) parameter.svar, T, ext);
481: array[index++] = T;
482: }
483: } else {
484: // We've run out of arguments.
485: LispObject initForm = parameter.initForm;
486: LispObject value = initForm != null ? eval(
487: initForm, ext, thread) : NIL;
488: bind(parameter.var, value, ext);
489: array[index++] = value;
490: if (parameter.svar != NIL) {
491: bind((Symbol) parameter.svar, NIL, ext);
492: array[index++] = NIL;
493: }
494: }
495: ++i;
496: }
497: }
498: // &rest parameter.
499: if (restVar != null) {
500: LispObject rest = NIL;
501: for (int j = args.length; j-- > argsUsed;)
502: rest = new Cons(args[j], rest);
503: bind(restVar, rest, ext);
504: array[index++] = rest;
505: }
506: // Keyword parameters.
507: if (keywordParameters != null) {
508: int argsLeft = args.length - argsUsed;
509: if (argsLeft == 0) {
510: // No keyword arguments were supplied.
511: // Bind all keyword parameters to their defaults.
512: for (int k = 0; k < keywordParameters.length; k++) {
513: Parameter parameter = keywordParameters[k];
514: LispObject initForm = parameter.initForm;
515: LispObject value = initForm != null ? eval(
516: initForm, ext, thread) : NIL;
517: bind(parameter.var, value, ext);
518: array[index++] = value;
519: if (parameter.svar != NIL) {
520: bind((Symbol) parameter.svar, NIL, ext);
521: array[index++] = NIL;
522: }
523: }
524: } else {
525: if ((argsLeft % 2) != 0)
526: throw new ConditionThrowable(new ProgramError(
527: "odd number of keyword arguments"));
528: LispObject[] valueArray = new LispObject[keywordParameters.length];
529: boolean[] boundpArray = new boolean[keywordParameters.length];
530: LispObject allowOtherKeysValue = null;
531: LispObject unrecognizedKeyword = null;
532: for (int j = argsUsed; j < args.length; j += 2) {
533: LispObject keyword = args[j];
534: if (keyword == Keyword.ALLOW_OTHER_KEYS) {
535: if (allowOtherKeysValue == null)
536: allowOtherKeysValue = args[j + 1];
537: }
538: // Find it.
539: int k;
540: for (k = keywordParameters.length; k-- > 0;) {
541: if (keywordParameters[k].keyword == keyword) {
542: // Found it!
543: if (!boundpArray[k]) {
544: Parameter parameter = keywordParameters[k];
545: Symbol symbol = parameter.var;
546: bind(symbol, args[j + 1], ext);
547: valueArray[k] = args[j + 1];
548: if (parameter.svar != NIL)
549: bind((Symbol) parameter.svar, T,
550: ext);
551: boundpArray[k] = true;
552: }
553: break;
554: }
555: }
556: if (k < 0) {
557: // Not found.
558: if (keyword != Keyword.ALLOW_OTHER_KEYS)
559: if (unrecognizedKeyword == null)
560: unrecognizedKeyword = keyword;
561: }
562: }
563: if (unrecognizedKeyword != null) {
564: if (!allowOtherKeys
565: && (allowOtherKeysValue == null || allowOtherKeysValue == NIL))
566: throw new ConditionThrowable(new ProgramError(
567: "unrecognized keyword argument "
568: + unrecognizedKeyword));
569: }
570: for (int n = 0; n < keywordParameters.length; n++) {
571: Parameter parameter = keywordParameters[n];
572: if (boundpArray[n]) {
573: // Parameter was bound above, so we don't have to bind
574: // it again here.
575: array[index++] = valueArray[n];
576: if (parameter.svar != NIL) {
577: // svar was bound above, so we don't have to bind
578: // it again here.
579: array[index++] = T;
580: }
581: } else {
582: // Not supplied.
583: LispObject initForm = parameter.initForm;
584: LispObject value = initForm != null ? eval(
585: initForm, ext, thread) : NIL;
586: bind(parameter.var, value, ext);
587: array[index++] = value;
588: if (parameter.svar != NIL) {
589: bind((Symbol) parameter.svar, NIL, ext);
590: array[index++] = NIL;
591: }
592: }
593: }
594: }
595: } else {
596: // No keyword parameters.
597: if (argsUsed + 2 <= args.length) {
598: // Check for :ALLOW-OTHER-KEYS.
599: LispObject allowOtherKeysValue = null;
600: LispObject keyword = args[argsUsed];
601: if (keyword == Keyword.ALLOW_OTHER_KEYS) {
602: allowOtherKeysValue = args[argsUsed + 1];
603: argsUsed += 2;
604: }
605: if (allowOtherKeysValue != null
606: && allowOtherKeysValue != NIL) {
607: // Skip keyword/value pairs.
608: while (argsUsed + 2 <= args.length)
609: argsUsed += 2;
610: }
611: }
612: if (argsUsed < args.length) {
613: if (restVar == null) {
614: throw new ConditionThrowable(
615: new WrongNumberOfArgumentsException(this ));
616: }
617: }
618: }
619: thread.setDynamicEnvironment(oldDynEnv);
620: return array;
621: }
622:
623: private final void bindOptionalParameterDefaults(Environment env,
624: LispThread thread) throws ConditionThrowable {
625: for (int i = 0; i < optionalParameters.length; i++) {
626: Parameter parameter = optionalParameters[i];
627: LispObject initForm = parameter.initForm;
628: bind(parameter.var, initForm != null ? eval(initForm, env,
629: thread) : NIL, env);
630: if (parameter.svar != NIL)
631: bind((Symbol) parameter.svar, NIL, env);
632: }
633: }
634:
635: private final void bindKeywordParameterDefaults(Environment env,
636: LispThread thread) throws ConditionThrowable {
637: for (int i = 0; i < keywordParameters.length; i++) {
638: Parameter parameter = keywordParameters[i];
639: LispObject initForm = parameter.initForm;
640: bind(parameter.var, initForm != null ? eval(initForm, env,
641: thread) : NIL, env);
642: if (parameter.svar != NIL)
643: bind((Symbol) parameter.svar, NIL, env);
644: }
645: }
646:
647: private final void bindAuxVars(Environment env, LispThread thread)
648: throws ConditionThrowable {
649: // Aux variable processing is analogous to LET* processing.
650: for (int i = 0; i < auxVars.length; i++) {
651: Parameter parameter = auxVars[i];
652: Symbol symbol = parameter.var;
653: LispObject initForm = parameter.initForm;
654: LispObject value = initForm == NIL ? NIL : eval(initForm,
655: env, thread);
656: bind(symbol, value, env);
657: }
658: }
659:
660: public String toString() {
661: StringBuffer sb = new StringBuffer("#<CLOSURE LAMBDA ");
662: sb
663: .append(lambdaList != NIL ? String.valueOf(lambdaList)
664: : "()");
665: try {
666: LispObject code = body;
667: while (code != NIL) {
668: sb.append(' ');
669: sb.append(code.car());
670: code = code.cdr();
671: }
672: } catch (ConditionThrowable t) {
673: Debug.trace(t);
674: }
675: sb.append('>');
676: return sb.toString();
677: }
678:
679: private static class Parameter {
680: private final Symbol var;
681: private final LispObject initForm;
682: private final LispObject svar;
683: private final int type;
684: private final Symbol keyword;
685:
686: public Parameter(Symbol var) {
687: this .var = var;
688: this .initForm = null;
689: this .svar = NIL;
690: this .type = REQUIRED;
691: this .keyword = null;
692: }
693:
694: public Parameter(Symbol var, LispObject initForm, int type) {
695: this .var = var;
696: this .initForm = initForm;
697: this .svar = NIL;
698: this .type = type;
699: keyword = type == KEYWORD ? PACKAGE_KEYWORD.intern(var
700: .getName()) : null;
701: }
702:
703: public Parameter(Symbol var, LispObject initForm,
704: LispObject svar, int type) throws ConditionThrowable {
705: this .var = var;
706: this .initForm = initForm;
707: this .svar = (svar != NIL) ? checkSymbol(svar) : NIL;
708: this .type = type;
709: keyword = type == KEYWORD ? PACKAGE_KEYWORD.intern(var
710: .getName()) : null;
711: }
712:
713: public Parameter(Symbol keyword, Symbol var,
714: LispObject initForm, LispObject svar)
715: throws ConditionThrowable {
716: this .var = var;
717: this .initForm = initForm;
718: this .svar = (svar != NIL) ? checkSymbol(svar) : NIL;
719: type = KEYWORD;
720: this .keyword = keyword;
721: }
722:
723: public String toString() {
724: if (type == REQUIRED)
725: return var.toString();
726: StringBuffer sb = new StringBuffer();
727: if (keyword != null) {
728: sb.append(keyword);
729: sb.append(' ');
730: }
731: sb.append(var.toString());
732: sb.append(' ');
733: sb.append(initForm);
734: sb.append(' ');
735: sb.append(type);
736: return sb.toString();
737: }
738: }
739: }
|