package net.jangaroo.jooc.backend; import net.jangaroo.jooc.JooSymbol; import net.jangaroo.jooc.Jooc; import net.jangaroo.jooc.ast.Annotation; import net.jangaroo.jooc.ast.AnnotationParameter; import net.jangaroo.jooc.ast.ApplyExpr; import net.jangaroo.jooc.ast.ArrayIndexExpr; import net.jangaroo.jooc.ast.ArrayLiteral; import net.jangaroo.jooc.ast.AsExpr; import net.jangaroo.jooc.ast.AssignmentOpExpr; import net.jangaroo.jooc.ast.AstNode; import net.jangaroo.jooc.ast.AstVisitor; import net.jangaroo.jooc.ast.BinaryOpExpr; import net.jangaroo.jooc.ast.BlockStatement; import net.jangaroo.jooc.ast.BreakStatement; import net.jangaroo.jooc.ast.CaseStatement; import net.jangaroo.jooc.ast.Catch; import net.jangaroo.jooc.ast.ClassBody; import net.jangaroo.jooc.ast.ClassDeclaration; import net.jangaroo.jooc.ast.CommaSeparatedList; import net.jangaroo.jooc.ast.CompilationUnit; import net.jangaroo.jooc.ast.ConditionalExpr; import net.jangaroo.jooc.ast.ContinueStatement; import net.jangaroo.jooc.ast.Declaration; import net.jangaroo.jooc.ast.DefaultStatement; import net.jangaroo.jooc.ast.Directive; import net.jangaroo.jooc.ast.DoStatement; import net.jangaroo.jooc.ast.DotExpr; import net.jangaroo.jooc.ast.EmptyDeclaration; import net.jangaroo.jooc.ast.EmptyStatement; import net.jangaroo.jooc.ast.Expr; import net.jangaroo.jooc.ast.Extends; import net.jangaroo.jooc.ast.ForInStatement; import net.jangaroo.jooc.ast.ForInitializer; import net.jangaroo.jooc.ast.ForStatement; import net.jangaroo.jooc.ast.FunctionDeclaration; import net.jangaroo.jooc.ast.FunctionExpr; import net.jangaroo.jooc.ast.Ide; import net.jangaroo.jooc.ast.IdeExpr; import net.jangaroo.jooc.ast.IdeWithTypeParam; import net.jangaroo.jooc.ast.IfStatement; import net.jangaroo.jooc.ast.Implements; import net.jangaroo.jooc.ast.ImportDirective; import net.jangaroo.jooc.ast.InfixOpExpr; import net.jangaroo.jooc.ast.Initializer; import net.jangaroo.jooc.ast.IsExpr; import net.jangaroo.jooc.ast.LabeledStatement; import net.jangaroo.jooc.ast.LiteralExpr; import net.jangaroo.jooc.ast.NamespaceDeclaration; import net.jangaroo.jooc.ast.NamespacedIde; import net.jangaroo.jooc.ast.NewExpr; import net.jangaroo.jooc.ast.ObjectField; import net.jangaroo.jooc.ast.ObjectLiteral; import net.jangaroo.jooc.ast.PackageDeclaration; import net.jangaroo.jooc.ast.Parameter; import net.jangaroo.jooc.ast.Parameters; import net.jangaroo.jooc.ast.ParenthesizedExpr; import net.jangaroo.jooc.ast.PostfixOpExpr; import net.jangaroo.jooc.ast.PredefinedTypeDeclaration; import net.jangaroo.jooc.ast.PrefixOpExpr; import net.jangaroo.jooc.ast.QualifiedIde; import net.jangaroo.jooc.ast.ReturnStatement; import net.jangaroo.jooc.ast.SemicolonTerminatedStatement; import net.jangaroo.jooc.ast.SuperConstructorCallStatement; import net.jangaroo.jooc.ast.SwitchStatement; import net.jangaroo.jooc.ast.ThrowStatement; import net.jangaroo.jooc.ast.TryStatement; import net.jangaroo.jooc.ast.Type; import net.jangaroo.jooc.ast.TypeRelation; import net.jangaroo.jooc.ast.TypedIdeDeclaration; import net.jangaroo.jooc.ast.UseNamespaceDirective; import net.jangaroo.jooc.ast.VariableDeclaration; import net.jangaroo.jooc.ast.VectorLiteral; import net.jangaroo.jooc.ast.WhileStatement; import net.jangaroo.jooc.model.ActionScriptModel; import net.jangaroo.jooc.model.AnnotatedModel; import net.jangaroo.jooc.model.AnnotationModel; import net.jangaroo.jooc.model.AnnotationPropertyModel; import net.jangaroo.jooc.model.ClassModel; import net.jangaroo.jooc.model.CompilationUnitModel; import net.jangaroo.jooc.model.DocumentedModel; import net.jangaroo.jooc.model.FieldModel; import net.jangaroo.jooc.model.MemberModel; import net.jangaroo.jooc.model.MethodModel; import net.jangaroo.jooc.model.MethodType; import net.jangaroo.jooc.model.NamespacedModel; import net.jangaroo.jooc.model.NamedModel; import net.jangaroo.jooc.model.NamespaceModel; import net.jangaroo.jooc.model.ParamModel; import net.jangaroo.jooc.model.TypedModel; import net.jangaroo.jooc.model.ValuedModel; import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.TreeSet; /** * Generates a model of the given compilation unit AST. * Feeding this model to an {@link ActionScriptCodeGeneratingModelVisitor} generates reduced ActionScript source code. * The reduced code is used as reference during compilation * and can be run through the asdoc tool. */ public class ApiModelGenerator { private boolean excludeClassByDefault = false; public ApiModelGenerator(boolean excludeClassByDefault) { this.excludeClassByDefault = excludeClassByDefault; } public boolean isExcludeClassByDefault() { return excludeClassByDefault; } public CompilationUnitModel generateModel(CompilationUnit compilationUnit) throws IOException { ApiModelGeneratingAstVisitor visitor = new ApiModelGeneratingAstVisitor(); compilationUnit.visit(visitor); return visitor.getCurrent(CompilationUnitModel.class); } private class ApiModelGeneratingAstVisitor implements AstVisitor { private Deque<ActionScriptModel> modelStack; private StringBuilder code; private StringBuilder asdoc = new StringBuilder(); private List<AnnotationModel> annotationModels = new ArrayList<AnnotationModel>(); @Override public void visitTypeRelation(TypeRelation typeRelation) throws IOException { startRecordingCode(); typeRelation.getType().visit(this); ((TypedModel)modelStack.peek()).setType(consumeRecordedCode()); } @Override public void visitAnnotationParameter(AnnotationParameter annotationParameter) throws IOException { AnnotationPropertyModel annotationPropertyModel = new AnnotationPropertyModel(); modelStack.push(annotationPropertyModel); visitIfNotNull(annotationParameter.getOptName()); startRecordingCode(); visitIfNotNull(annotationParameter.getValue()); annotationPropertyModel.setValue(consumeRecordedCode()); modelStack.pop(); ((AnnotationModel)modelStack.peek()).addProperty(annotationPropertyModel); } @Override public void visitExtends(Extends anExtends) throws IOException { getCurrent(ClassModel.class).setSuperclass(anExtends.getSuperClass().getQualifiedNameStr()); } @Override public void visitInitializer(Initializer initializer) throws IOException { if (initializer.getValue().isCompileTimeConstant()) { startRecordingCode(); initializer.getValue().visit(this); ((ValuedModel)modelStack.peek()).setValue(consumeRecordedCode()); } } @Override public void visitObjectField(ObjectField objectField) throws IOException { throw new UnsupportedOperationException(); } @Override public void visitForInitializer(ForInitializer forInitializer) throws IOException { throw new UnsupportedOperationException(); } @Override public void visitCompilationUnit(CompilationUnit compilationUnit) throws IOException { modelStack = new ArrayDeque<ActionScriptModel>(); code = null; CompilationUnitModel compilationUnitModel = new CompilationUnitModel(""); modelStack.push(compilationUnitModel); compilationUnit.getPackageDeclaration().visit(this); for (String publicApiDependency : new TreeSet<String>(compilationUnit.getPublicApiDependencies())) { compilationUnitModel.addImport(publicApiDependency); } visitAll(compilationUnit.getDirectives()); compilationUnit.getPrimaryDeclaration().visit(this); } @Override public void visitIde(Ide ide) throws IOException { if (code != null) { recordCode(ide.getQualifiedNameStr()); } else { ((NamedModel)modelStack.peek()).setName(ide.getQualifiedNameStr()); } } @Override public void visitQualifiedIde(QualifiedIde qualifiedIde) throws IOException { visitIde(qualifiedIde); } @Override public void visitIdeWithTypeParam(IdeWithTypeParam ideWithTypeParam) throws IOException { recordCode( ideWithTypeParam.getOriginalIde().getText() + ideWithTypeParam.getSymDotLt().getText()); ideWithTypeParam.getType().visit(this); recordCode(ideWithTypeParam.getSymGt().getText()); } @Override public void visitNamespacedIde(NamespacedIde namespacedIde) throws IOException { throw new UnsupportedOperationException(); } @Override public void visitImplements(Implements anImplements) throws IOException { CommaSeparatedList<Ide> superTypes = anImplements.getSuperTypes(); while (superTypes != null) { getCurrent(ClassModel.class).addInterface(superTypes.getHead().getQualifiedNameStr()); superTypes = superTypes.getTail(); } } @Override public void visitType(Type type) throws IOException { type.getIde().visit(this); } @Override public void visitObjectLiteral(ObjectLiteral objectLiteral) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } private IllegalStateException shouldOnlyBeCalledForCompileTimeConstants() { return new IllegalStateException("should only be called for compile time constants"); } @Override public void visitIdeExpression(IdeExpr ideExpr) throws IOException { ideExpr.getIde().visit(this); } @Override public <T extends Expr> void visitParenthesizedExpr(ParenthesizedExpr<T> parenthesizedExpr) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitArrayLiteral(ArrayLiteral arrayLiteral) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitAssignmentOpExpr(AssignmentOpExpr assignmentOpExpr) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitInfixOpExpr(InfixOpExpr infixOpExpr) throws IOException { visitBinaryOpExpr(infixOpExpr); } private void consumeRecordedAnnotations() { getCurrent(AnnotatedModel.class).setAnnotations(annotationModels); annotationModels = new ArrayList<AnnotationModel>(); } private void recordAsdoc(JooSymbol symbol) { if (symbol != null) { String whitespace = symbol.getWhitespace(); int startPos = whitespace.indexOf("/**"); if (startPos != -1) { int endPos = whitespace.indexOf("*/", startPos); if (asdoc.length() > 0) { asdoc.append('\n'); // avoid missing white-space between two ASDoc sections } asdoc.append(whitespace.substring(startPos + 2, endPos - 1)); } } } private void recordAsdoc(Declaration declaration) { for (JooSymbol symbol : declaration.getSymModifiers()) { recordAsdoc(symbol); } } private void consumeRecordedAsdoc() { ((DocumentedModel)modelStack.peek()).setAsdoc(trimAsdoc(asdoc.toString())); asdoc.setLength(0); } private void startRecordingCode() { code = new StringBuilder(); } private void recordCode(JooSymbol symbol) { recordCode(symbol.getText()); // TODO: what about white-space? } private void recordCode(String text) { code.append(text); } private String consumeRecordedCode() { String recordedCode = code.toString(); code = null; return recordedCode; } @Override public void visitAsExpr(AsExpr asExpr) throws IOException { visitInfixOpExpr(asExpr); } @Override public void visitArrayIndexExpr(ArrayIndexExpr arrayIndexExpr) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitFunctionExpr(FunctionExpr functionExpr) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitVectorLiteral(VectorLiteral vectorLiteral) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitApplyExpr(ApplyExpr applyExpr) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitNewExpr(NewExpr newExpr) throws IOException { throw shouldOnlyBeCalledForCompileTimeConstants(); } @Override public void visitClassBody(ClassBody classBody) throws IOException { for (Directive directive : classBody.getDirectives()) { directive.visit(this); } } @Override public void visitBlockStatement(BlockStatement blockStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitDefaultStatement(DefaultStatement defaultStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitLabeledStatement(LabeledStatement labeledStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitIfStatement(IfStatement ifStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitCaseStatement(CaseStatement caseStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitTryStatement(TryStatement tryStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitCatch(Catch aCatch) throws IOException { throw new IllegalStateException("should not occur, because we are omitting try statements"); } @Override public void visitForInStatement(ForInStatement forInStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitWhileStatement(WhileStatement whileStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitForStatement(ForStatement forStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitDoStatement(DoStatement doStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitSwitchStatement(SwitchStatement switchStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitSemicolonTerminatedStatement(SemicolonTerminatedStatement semicolonTerminatedStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitContinueStatement(ContinueStatement continueStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitBreakStatement(BreakStatement breakStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitThrowStatement(ThrowStatement throwStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitReturnStatement(ReturnStatement returnStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitEmptyStatement(EmptyStatement emptyStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitEmptyDeclaration(EmptyDeclaration emptyDeclaration) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } protected void visitIfNotNull(AstNode args) throws IOException { if (args != null) { args.visit(this); } } @Override public void visitParameter(Parameter parameter) throws IOException { ParamModel paramModel = new ParamModel(); paramModel.setRest(parameter.isRest()); modelStack.push(paramModel); parameter.getIde().visit(this); visitIfNotNull(parameter.getOptTypeRelation()); visitIfNotNull(parameter.getOptInitializer()); modelStack.pop(); ((MethodModel)modelStack.peek()).addParam(paramModel); } private void generateVisibility(Declaration declaration) { NamespacedModel namespacedModel = (NamespacedModel)modelStack.peek(); if (namespacedModel instanceof MemberModel) { ((MemberModel)namespacedModel).setStatic(declaration.isStatic()); } // Public API only, thus either "protected", "public", or custom namespace: if (declaration instanceof TypedIdeDeclaration) { Ide namespace = ((TypedIdeDeclaration)declaration).getNamespace(); if (namespace != null) { namespacedModel.setNamespace(namespace.getQualifiedNameStr()); return; } } namespacedModel.setNamespace(declaration.isProtected() ? NamespacedModel.PROTECTED : NamespacedModel.PUBLIC); } private void generateStaticFlag(Declaration declaration) { MemberModel memberModel = (MemberModel)modelStack.peek(); memberModel.setStatic(declaration.isStatic()); } private void generateMemberModifiers(Declaration declaration) { generateVisibility(declaration); generateStaticFlag(declaration); } @Override public void visitVariableDeclaration(VariableDeclaration variableDeclaration) throws IOException { boolean isTopLevelDeclaration = modelStack.peek() instanceof CompilationUnitModel; if (variableDeclaration.isPublicApi() || isTopLevelDeclaration) { FieldModel fieldModel = new FieldModel(); modelStack.push(fieldModel); consumeRecordedAnnotations(); if (isTopLevelDeclaration) { handleExcludeClassByDefault(fieldModel); } recordAsdoc(variableDeclaration); recordAsdoc(variableDeclaration.getOptSymConstOrVar()); consumeRecordedAsdoc(); generateMemberModifiers(variableDeclaration); fieldModel.setConst(variableDeclaration.isConst()); variableDeclaration.getIde().visit(this); TypeRelation optTypeRelation = variableDeclaration.getOptTypeRelation(); if (optTypeRelation != null) { optTypeRelation.visit(this); } if (variableDeclaration.isConst()) { visitIfNotNull(variableDeclaration.getOptInitializer()); } popMember(); visitIfNotNull(variableDeclaration.getOptNextVariableDeclaration()); } } @Override public void visitFunctionDeclaration(FunctionDeclaration functionDeclaration) throws IOException { boolean isTopLevelDeclaration = modelStack.peek() instanceof CompilationUnitModel; if (functionDeclaration.isPublicApi() || isTopLevelDeclaration) { MethodModel methodModel = new MethodModel(); modelStack.push(methodModel); consumeRecordedAnnotations(); if (isTopLevelDeclaration) { handleExcludeClassByDefault(methodModel); } recordAsdoc(functionDeclaration); recordAsdoc(functionDeclaration.getSymbol()); consumeRecordedAsdoc(); generateMemberModifiers(functionDeclaration); methodModel.setOverride(functionDeclaration.isOverride()); methodModel.setFinal(functionDeclaration.isFinal()); methodModel.setMethodType(functionDeclaration.isGetter() ? MethodType.GET : functionDeclaration.isSetter() ? MethodType.SET : null); functionDeclaration.getIde().visit(this); generateSignatureAsApiCode(functionDeclaration.getFun()); if (functionDeclaration.isConstructor() && !functionDeclaration.isNative()) { // ASDoc does not allow a native constructor if the super class constructor needs parameters! StringBuilder superCallCode = new StringBuilder(); superCallCode.append("super("); if (functionDeclaration.getClassDeclaration() != null) { ClassDeclaration superType = functionDeclaration.getClassDeclaration().getSuperTypeDeclaration(); if (superType != null) { FunctionDeclaration superConstructor = superType.getConstructor(); if (superConstructor != null) { Parameters superParameters = superConstructor.getParams(); boolean first = true; while (superParameters != null && superParameters.getHead().getOptInitializer() == null) { if (first) { first = false; } else { superCallCode.append(", "); } superCallCode.append(VariableDeclaration.getDefaultValue(superParameters.getHead().getOptTypeRelation())); superParameters = superParameters.getTail(); } } } } superCallCode.append(");"); ((MethodModel)modelStack.peek()).setBody(superCallCode.toString()); } popMember(); } } private void popMember() { MemberModel member = (MemberModel)modelStack.pop(); ActionScriptModel current = modelStack.peek(); if (current instanceof ClassModel) { ((ClassModel)current).addMember(member); } else if (current instanceof CompilationUnitModel) { ((CompilationUnitModel)current).setPrimaryDeclaration(member); } else { throw new IllegalArgumentException("Members outside class or package not allowed."); } } public void generateSignatureAsApiCode(FunctionExpr fun) throws IOException { visitIfNotNull(fun.getParams()); visitIfNotNull(fun.getOptTypeRelation()); } protected void visitAll(Iterable<? extends AstNode> nodes) throws IOException { for (AstNode node : nodes) { node.visit(this); } } @Override public void visitClassDeclaration(ClassDeclaration classDeclaration) throws IOException { ClassModel classModel = new ClassModel(); getCurrent(CompilationUnitModel.class).setPrimaryDeclaration(classModel); modelStack.push(classModel); recordAsdoc(classDeclaration); recordAsdoc(classDeclaration.getSymClass()); consumeRecordedAsdoc(); classModel.setFinal(classDeclaration.isFinal()); classModel.setDynamic(classDeclaration.isDynamic()); generateVisibility(classDeclaration); consumeRecordedAnnotations(); handleExcludeClassByDefault(classModel); classDeclaration.getIde().visit(this); classModel.setInterface(classDeclaration.isInterface()); visitIfNotNull(classDeclaration.getOptExtends()); visitIfNotNull(classDeclaration.getOptImplements()); classDeclaration.getBody().visit(this); modelStack.pop(); } private void handleExcludeClassByDefault(AnnotatedModel annotatedModel) { if (isExcludeClassByDefault()) { // Add an [ExcludeClass] annotation, unless boolean needsExcludeClassAnnotation = true; for (AnnotationModel annotationModel : annotatedModel.getAnnotations()) { String metaName = annotationModel.getName(); // ... an [PublicApi] or [ExcludeClass] annotation is already present. needsExcludeClassAnnotation = needsExcludeClassAnnotation && !Jooc.PUBLIC_API_INCLUSION_ANNOTATION_NAME.equals(metaName) && !Jooc.PUBLIC_API_EXCLUSION_ANNOTATION_NAME.equals(metaName); } if (needsExcludeClassAnnotation) { annotatedModel.addAnnotation(new AnnotationModel(Jooc.PUBLIC_API_EXCLUSION_ANNOTATION_NAME)); } } } @Override public void visitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) throws IOException { NamespaceModel namespaceModel = new NamespaceModel(); modelStack.push(namespaceModel); consumeRecordedAnnotations(); handleExcludeClassByDefault(namespaceModel); recordAsdoc(namespaceDeclaration); recordAsdoc(namespaceDeclaration.getSymNamespace()); consumeRecordedAsdoc(); generateVisibility(namespaceDeclaration); namespaceDeclaration.getIde().visit(this); visitIfNotNull(namespaceDeclaration.getOptInitializer()); modelStack.pop(); getCurrent(CompilationUnitModel.class).setPrimaryDeclaration(namespaceModel); } @Override public void visitPackageDeclaration(PackageDeclaration packageDeclaration) throws IOException { Ide packageIde = packageDeclaration.getIde(); if (packageIde != null) { getCurrent(CompilationUnitModel.class).setPackage(packageIde.getQualifiedNameStr()); } } @Override public void visitSuperConstructorCallStatement(SuperConstructorCallStatement superConstructorCallStatement) throws IOException { // This may appear as a class initializer, in which case we can simply skip it. } @Override public void visitAnnotation(Annotation annotation) throws IOException { AnnotationModel annotationModel = new AnnotationModel(); modelStack.push(annotationModel); recordAsdoc(annotation.getLeftBracket()); consumeRecordedAsdoc(); annotation.getIde().visit(this); visitIfNotNull(annotation.getOptAnnotationParameters()); modelStack.pop(); annotationModels.add(annotationModel); } @Override public void visitUseNamespaceDirective(UseNamespaceDirective useNamespaceDirective) throws IOException { // no api code generated } @Override public void visitImportDirective(ImportDirective importDirective) throws IOException { // no api code generated } @Override public void visitLiteralExpr(LiteralExpr literalExpr) throws IOException { recordCode(literalExpr.getValue()); } @Override public void visitPostfixOpExpr(PostfixOpExpr postfixOpExpr) throws IOException { postfixOpExpr.getArg().visit(this); recordCode(postfixOpExpr.getOp()); } @Override public void visitDotExpr(DotExpr dotExpr) throws IOException { dotExpr.getArg().visit(this); recordCode(dotExpr.getOp()); recordCode(dotExpr.getIde().getName()); } @Override public void visitPrefixOpExpr(PrefixOpExpr prefixOpExpr) throws IOException { recordCode(prefixOpExpr.getOp()); prefixOpExpr.getArg().visit(this); } @Override public void visitBinaryOpExpr(BinaryOpExpr binaryOpExpr) throws IOException { binaryOpExpr.getArg1().visit(this); recordCode(binaryOpExpr.getOp()); binaryOpExpr.getArg2().visit(this); } @Override public void visitIsExpr(IsExpr isExpr) throws IOException { visitBinaryOpExpr(isExpr); } @Override public void visitConditionalExpr(ConditionalExpr conditionalExpr) throws IOException { conditionalExpr.getCond().visit(this); recordCode(conditionalExpr.getSymQuestion()); conditionalExpr.getIfTrue().visit(this); recordCode(conditionalExpr.getSymColon()); conditionalExpr.getIfFalse().visit(this); } @Override public <T extends AstNode> void visitCommaSeparatedList(CommaSeparatedList<T> commaSeparatedList) throws IOException { visitIfNotNull(commaSeparatedList.getHead()); if (commaSeparatedList.getSymComma() != null) { visitIfNotNull(commaSeparatedList.getTail()); } } @Override public void visitParameters(Parameters parameters) throws IOException { visitCommaSeparatedList(parameters); } @Override public void visitPredefinedTypeDeclaration(PredefinedTypeDeclaration predefinedTypeDeclaration) throws IOException { throw new IllegalStateException("there should be no code generation for predefined types"); } private <T extends ActionScriptModel> T getCurrent(Class<T> targetClass) { return targetClass.cast(modelStack.peek()); } } private static String trimAsdoc(String asdoc) { int oldLength; do { oldLength = asdoc.length(); asdoc = asdoc.trim(); if (asdoc.startsWith("*")) { asdoc = asdoc.substring(1); } if (asdoc.endsWith("*")) { asdoc = asdoc.substring(0, asdoc.length() - 1); } } while (asdoc.length() < oldLength); return asdoc; } }