From 9803edc79a56432c90541edd08ee73c52781c05c Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 7 Sep 2024 21:05:50 -0500 Subject: [PATCH 01/71] Get properties working and add phase3 tests --- .../compiler/asmboxpiler/AsmHelper.java | 37 +- .../compiler/asmboxpiler/AsmTranspiler.java | 296 ++-- .../ortus/boxlang/runtime/BoxRuntime.java | 2 + .../TestCases/asm/phase3/AbstractClass.bx | 9 + .../TestCases/asm/phase3/AbstractClassCF.cfc | 9 + src/test/java/TestCases/asm/phase3/Animal.cfc | 36 + .../TestCases/asm/phase3/ApplicationTest.java | 242 ++++ .../TestCases/asm/phase3/CFImportTest.cfc | 13 + .../TestCases/asm/phase3/CFImportTest2.cfc | 13 + .../java/TestCases/asm/phase3/Chihuahua.cfc | 17 + src/test/java/TestCases/asm/phase3/Child.cfc | 12 + .../asm/phase3/ClassLeadingComment.cfc | 12 + .../java/TestCases/asm/phase3/ClassTest.java | 1261 +++++++++++++++++ .../asm/phase3/ClassTrailingComment.cfc | 7 + .../asm/phase3/ClassWrappedInScript.cfc | 4 + .../TestCases/asm/phase3/ConcreteClass.bx | 6 + .../TestCases/asm/phase3/ConcreteClassCF.cfc | 6 + src/test/java/TestCases/asm/phase3/Dog.cfc | 20 + .../java/TestCases/asm/phase3/DotExtends.cfc | 5 + .../TestCases/asm/phase3/DotExtendsParent.cfc | 5 + .../TestCases/asm/phase3/ExceptionTest.java | 77 + .../TestCases/asm/phase3/ExceptionThrower.cfs | 6 + .../java/TestCases/asm/phase3/FinalClass.bx | 5 + src/test/java/TestCases/asm/phase3/FindMe.bx | 5 + .../TestCases/asm/phase3/FunctionMeta.cfc | 16 + .../asm/phase3/GeneratedGetterChild.bx | 2 + .../asm/phase3/GeneratedGetterParent.bx | 9 + .../java/TestCases/asm/phase3/GetterTest.cfc | 10 + .../java/TestCases/asm/phase3/IBicycle.bx | 11 + .../TestCases/asm/phase3/IChildInterface.bx | 9 + .../java/TestCases/asm/phase3/IMotorcycle.bx | 9 + .../asm/phase3/IMultiChildInterface.bx | 12 + .../TestCases/asm/phase3/IParentInterface.bx | 12 + .../TestCases/asm/phase3/IUncleInterface.bx | 12 + .../asm/phase3/IllegalFinalExtends.bx | 3 + .../TestCases/asm/phase3/ImplicitAccessor.bx | 29 + .../asm/phase3/ImplicitConstructorTest.cfc | 6 + .../asm/phase3/ImplicitGeneratedAccessor.bx | 5 + .../TestCases/asm/phase3/InitMethodTest.bx | 9 + .../asm/phase3/InterfaceInheritenceTest.bx | 11 + .../phase3/InterfaceMultiInheritenceTest.bx | 23 + .../TestCases/asm/phase3/InterfaceStatic.bx | 16 + .../TestCases/asm/phase3/InterfaceTest.java | 284 ++++ .../java/TestCases/asm/phase3/JavaExtends.bx | 14 + .../java/TestCases/asm/phase3/JavaExtends2.bx | 7 + .../java/TestCases/asm/phase3/JavaExtends3.bx | 14 + .../TestCases/asm/phase3/JavaImplements.bx | 7 + src/test/java/TestCases/asm/phase3/Moped.bx | 20 + src/test/java/TestCases/asm/phase3/MyClass.bx | 50 + .../java/TestCases/asm/phase3/MyClassCF.cfc | 36 + .../TestCases/asm/phase3/MyInterfaceBL.bx | 19 + .../TestCases/asm/phase3/MyInterfaceCF.cfc | 19 + .../TestCases/asm/phase3/MyInterfaceCFTag.cfc | 19 + .../TestCases/asm/phase3/OnMissingMethod.cfc | 5 + src/test/java/TestCases/asm/phase3/Parent.cfc | 12 + .../java/TestCases/asm/phase3/PropertyTest.bx | 25 + .../TestCases/asm/phase3/PropertyTestCF.cfc | 18 + .../asm/phase3/PseudoConstructorNoOutput.cfc | 3 + .../asm/phase3/PseudoConstructorOutput.cfc | 3 + .../asm/phase3/RelativeInstantiation.bx | 5 + .../java/TestCases/asm/phase3/SeniorVespa.bx | 3 + src/test/java/TestCases/asm/phase3/Stack.cfc | 10 + .../java/TestCases/asm/phase3/StaticTest.bx | 33 + .../TestCases/asm/phase3/StaticTestCF.cfc | 22 + .../java/TestCases/asm/phase3/WheeledThing.bx | 13 + 65 files changed, 2852 insertions(+), 98 deletions(-) create mode 100644 src/test/java/TestCases/asm/phase3/AbstractClass.bx create mode 100644 src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc create mode 100644 src/test/java/TestCases/asm/phase3/Animal.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ApplicationTest.java create mode 100644 src/test/java/TestCases/asm/phase3/CFImportTest.cfc create mode 100644 src/test/java/TestCases/asm/phase3/CFImportTest2.cfc create mode 100644 src/test/java/TestCases/asm/phase3/Chihuahua.cfc create mode 100644 src/test/java/TestCases/asm/phase3/Child.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ClassTest.java create mode 100644 src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ConcreteClass.bx create mode 100644 src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc create mode 100644 src/test/java/TestCases/asm/phase3/Dog.cfc create mode 100644 src/test/java/TestCases/asm/phase3/DotExtends.cfc create mode 100644 src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ExceptionTest.java create mode 100644 src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs create mode 100644 src/test/java/TestCases/asm/phase3/FinalClass.bx create mode 100644 src/test/java/TestCases/asm/phase3/FindMe.bx create mode 100644 src/test/java/TestCases/asm/phase3/FunctionMeta.cfc create mode 100644 src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx create mode 100644 src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx create mode 100644 src/test/java/TestCases/asm/phase3/GetterTest.cfc create mode 100644 src/test/java/TestCases/asm/phase3/IBicycle.bx create mode 100644 src/test/java/TestCases/asm/phase3/IChildInterface.bx create mode 100644 src/test/java/TestCases/asm/phase3/IMotorcycle.bx create mode 100644 src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx create mode 100644 src/test/java/TestCases/asm/phase3/IParentInterface.bx create mode 100644 src/test/java/TestCases/asm/phase3/IUncleInterface.bx create mode 100644 src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx create mode 100644 src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx create mode 100644 src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc create mode 100644 src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx create mode 100644 src/test/java/TestCases/asm/phase3/InitMethodTest.bx create mode 100644 src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx create mode 100644 src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx create mode 100644 src/test/java/TestCases/asm/phase3/InterfaceStatic.bx create mode 100644 src/test/java/TestCases/asm/phase3/InterfaceTest.java create mode 100644 src/test/java/TestCases/asm/phase3/JavaExtends.bx create mode 100644 src/test/java/TestCases/asm/phase3/JavaExtends2.bx create mode 100644 src/test/java/TestCases/asm/phase3/JavaExtends3.bx create mode 100644 src/test/java/TestCases/asm/phase3/JavaImplements.bx create mode 100644 src/test/java/TestCases/asm/phase3/Moped.bx create mode 100644 src/test/java/TestCases/asm/phase3/MyClass.bx create mode 100644 src/test/java/TestCases/asm/phase3/MyClassCF.cfc create mode 100644 src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx create mode 100644 src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc create mode 100644 src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc create mode 100644 src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc create mode 100644 src/test/java/TestCases/asm/phase3/Parent.cfc create mode 100644 src/test/java/TestCases/asm/phase3/PropertyTest.bx create mode 100644 src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc create mode 100644 src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc create mode 100644 src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc create mode 100644 src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx create mode 100644 src/test/java/TestCases/asm/phase3/SeniorVespa.bx create mode 100644 src/test/java/TestCases/asm/phase3/Stack.cfc create mode 100644 src/test/java/TestCases/asm/phase3/StaticTest.bx create mode 100644 src/test/java/TestCases/asm/phase3/StaticTestCF.cfc create mode 100644 src/test/java/TestCases/asm/phase3/WheeledThing.bx diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 58d79c764..981f35db0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -195,6 +195,29 @@ public static void addFieldGetter( ClassVisitor classVisitor, Type type, String methodVisitor.visitEnd(); } + public static void addPrviateStaticFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value ) { + FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + field, + property.getDescriptor(), + null, + value ); + fieldVisitor.visitEnd(); + MethodVisitor methodVisitor = classVisitor.visitMethod( Opcodes.ACC_PUBLIC, + method, + Type.getMethodDescriptor( property ), + null, + null ); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + field, + property.getDescriptor() ); + methodVisitor.visitInsn( property.getOpcode( Opcodes.IRETURN ) ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + } + public static void addFieldGetterAndSetter( ClassVisitor classVisitor, Type type, String field, String getter, String setter, Type property, Object value, Consumer onAfterSet ) { addFieldGetter( classVisitor, type, field, getter, property, value ); @@ -339,9 +362,18 @@ public static List array( Type type, List values, BiFun for ( int i = 0; i < values.size(); i++ ) { nodes.add( new InsnNode( Opcodes.DUP ) ); nodes.add( new LdcInsnNode( i ) ); - nodes.addAll( transformer.apply( values.get( i ), i ) ); + + List toAdd = transformer.apply( values.get( i ), i ); + if ( toAdd.size() == 0 ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } + + nodes.addAll( toAdd ); + nodes.add( new LdcInsnNode( "DEBUG - ASMHelper 349" ) ); + nodes.add( new InsnNode( Opcodes.POP ) ); nodes.add( new InsnNode( Opcodes.AASTORE ) ); } + return nodes; } @@ -439,6 +471,8 @@ public static MethodNode dereferenceAndInvoke( String name, Type descriptor, Typ node.visitLdcInsn( index ); node.visitVarInsn( descriptor.getArgumentTypes()[ index ].getOpcode( Opcodes.ILOAD ), offset ); // TODO: boxing of primitives + node.visitLdcInsn( "DEBUG - ASMHelper 451" ); + node.visitInsn( Opcodes.POP ); node.visitInsn( Opcodes.AASTORE ); offset += descriptor.getArgumentTypes()[ index ].getSize(); } @@ -463,4 +497,5 @@ public static MethodNode dereferenceAndInvoke( String name, Type descriptor, Typ return node; } + } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index ad8aa22b5..ede5db865 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -5,6 +5,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Stream; import org.objectweb.asm.Opcodes; @@ -457,6 +459,7 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, Type.getInternalName( ArrayList.class ), "", Type.getMethodDescriptor( Type.VOID_TYPE ), false ); methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "interfaces", Type.getDescriptor( List.class ) ); + }, interfaces.toArray( Type[]::new ) ); interfaceMethods.forEach( methodNode -> methodNode.accept( classNode ) ); @@ -539,6 +542,18 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { null, 0 ).visitEnd(); + // classNode.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + // "compileTimeMethodNames", + // Type.getDescriptor( Set.class ), + // null, + // null ).visitEnd(); + AsmHelper.addPrviateStaticFieldGetter( classNode, + type, + "compileTimeMethodNames", + "getCompileTimeMethodNames", + Type.getType( Set.class ), + null ); + AsmHelper.addFieldGetter( classNode, type, "variablesScope", @@ -627,9 +642,43 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { Type.getType( Key.class ), Type.getType( Map.class ), Type.getType( Boolean.class ) ); AsmHelper.boxClassSupport( classNode, "registerInterface", Type.VOID_TYPE, Type.getType( BoxInterface.class ) ); + // these imports need to happen before any methods are processed - the actual nodes will be used later on in the static init section + List> imports = new ArrayList<>(); + for ( BoxImport statement : boxClass.getImports() ) { + imports.add( transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ) ); + } + List importNodes = AsmHelper.array( Type.getType( ImportDefinition.class ), Stream.concat( + imports.stream(), + getImports().stream().map( raw -> { + List nodes = new ArrayList<>(); + nodes.addAll( raw ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( ImportDefinition.class ), + "parse", + Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), + false ) ); + return nodes; + } ) + ).filter( l -> l.size() > 0 ).toList() ); + // end import node setup + AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, this, true, - () -> boxClass.getBody().stream().flatMap( statement -> transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) - .toList() + () -> { + return boxClass.getBody() + .stream() + .sorted( ( a, b ) -> { + if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { + return -1; + } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { + return 1; + } + + return 0; + + } ) + .flatMap( statement -> transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) + .toList(); + } ); AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, this, true, @@ -652,10 +701,6 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { "sourceType", Type.getDescriptor( BoxSourceType.class ) ); - List> imports = new ArrayList<>(); - for ( BoxImport statement : boxClass.getImports() ) { - imports.add( transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ) ); - } List annotations = transformAnnotations( boxClass.getAnnotations() ); List> properties = transformProperties( type, boxClass.getProperties(), sourceType ); @@ -702,19 +747,7 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { methodVisitor.visitLdcInsn( 1L ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "serialVersionUID", Type.getDescriptor( long.class ) ); - AsmHelper.array( Type.getType( ImportDefinition.class ), Stream.concat( - imports.stream(), - getImports().stream().map( raw -> { - List nodes = new ArrayList<>(); - nodes.addAll( raw ); - nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, - Type.getInternalName( ImportDefinition.class ), - "parse", - Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), - false ) ); - return nodes; - } ) - ).toList() ).forEach( node -> node.accept( methodVisitor ) ); + importNodes.forEach( node -> node.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName( List.class ), "of", @@ -757,6 +790,9 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { type.getInternalName(), "setterLookup", Type.getDescriptor( Map.class ) ); + + generateSetOfCompileTimeMethodNames( boxClass ).forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "compileTimeMethodNames", Type.getDescriptor( Set.class ) ); } ); return classNode; @@ -781,95 +817,36 @@ private List> transformProperties( Type declaringType, Li * normalize annotations to allow for * property String userName; */ - List finalAnnotations = new ArrayList(); + List finalAnnotations = normlizePropertyAnnotations( prop ); // Start wiith all inline annotatinos var annotations = prop.getPostAnnotations(); - // Add in any pre annotations that have a value, which allows type, name, or default to be set before - annotations.addAll( prop.getAnnotations().stream().filter( it -> it.getValue() != null ).toList() ); - int namePosition = annotations.stream().filter( it -> it.getValue() != null ).map( BoxAnnotation::getKey ) - .map( BoxFQN::getValue ).map( String::toLowerCase ) - .collect( java.util.stream.Collectors.toList() ).indexOf( "name" ); - int typePosition = annotations.stream().filter( it -> it.getValue() != null ).map( BoxAnnotation::getKey ) - .map( BoxFQN::getValue ).map( String::toLowerCase ) - .collect( java.util.stream.Collectors.toList() ).indexOf( "type" ); - int defaultPosition = annotations.stream().filter( it -> it.getValue() != null ).map( BoxAnnotation::getKey ) - .map( BoxFQN::getValue ).map( String::toLowerCase ) - .collect( java.util.stream.Collectors.toList() ).indexOf( "default" ); - int numberOfNonValuedKeys = ( int ) annotations.stream().map( BoxAnnotation::getValue ).filter( it -> it == null ).count(); - List nonValuedKeys = annotations.stream().filter( it -> it.getValue() == null ) - .collect( java.util.stream.Collectors.toList() ); - BoxAnnotation nameAnnotation = null; - BoxAnnotation typeAnnotation = null; - BoxAnnotation defaultAnnotation = null; - - if ( namePosition > -1 ) - nameAnnotation = annotations.get( namePosition ); - if ( typePosition > -1 ) - typeAnnotation = annotations.get( typePosition ); - if ( defaultPosition > -1 ) - defaultAnnotation = annotations.get( defaultPosition ); - /* - * If there is no name, if there is more than one nonvalued keys and no type, use the first nonvalued key - * as the type and second nonvalued key as the name. Otherwise, if there are more than one non-valued key, use the first as the name. - */ - if ( namePosition == -1 ) { - if ( numberOfNonValuedKeys > 1 && typePosition == -1 ) { - typeAnnotation = new BoxAnnotation( new BoxFQN( "type", null, null ), - new BoxStringLiteral( nonValuedKeys.get( 0 ).getKey().getValue(), null, null ), null, - null ); - nameAnnotation = new BoxAnnotation( new BoxFQN( "name", null, null ), - new BoxStringLiteral( nonValuedKeys.get( 1 ).getKey().getValue(), null, null ), null, - null ); - finalAnnotations.add( nameAnnotation ); - finalAnnotations.add( typeAnnotation ); - annotations.remove( nonValuedKeys.get( 0 ) ); - annotations.remove( nonValuedKeys.get( 1 ) ); - } else if ( numberOfNonValuedKeys > 0 ) { - nameAnnotation = new BoxAnnotation( new BoxFQN( "name", null, null ), - new BoxStringLiteral( nonValuedKeys.get( 0 ).getKey().getValue(), null, null ), null, - null ); - finalAnnotations.add( nameAnnotation ); - annotations.remove( nonValuedKeys.get( 0 ) ); - } else { - throw new ExpressionException( "Property [" + prop.getSourceText() + "] has no name", prop ); - } - } - // add type with value of any if not present - if ( typeAnnotation == null ) { - typeAnnotation = new BoxAnnotation( new BoxFQN( "type", null, null ), new BoxStringLiteral( "any", null, null ), null, - null ); - finalAnnotations.add( typeAnnotation ); - } - // add default with value of null if not present - if ( defaultPosition == -1 ) { - defaultAnnotation = new BoxAnnotation( new BoxFQN( "default", null, null ), new BoxNull( null, null ), null, - null ); - finalAnnotations.add( defaultAnnotation ); - } - // add remaining annotations - finalAnnotations.addAll( annotations ); - // Now that name, type, and default are finalized, add in any remaining non-valued keys - finalAnnotations.addAll( prop.getAnnotations().stream().filter( it -> it.getValue() == null ).toList() ); - - List annotationStruct = transformAnnotations( finalAnnotations ); - /* Process default value */ - List init; - if ( defaultAnnotation.getValue() != null ) { - init = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); - } else { - init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + BoxAnnotation nameAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "name" ) ) + .findFirst() + .orElseThrow( () -> new ExpressionException( "Property [" + prop.getSourceText() + "] missing name annotation", prop ) ); + BoxAnnotation typeAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "type" ) ) + .findFirst() + .orElseThrow( () -> new ExpressionException( "Property [" + prop.getSourceText() + "] missing type annotation", prop ) ); + BoxAnnotation defaultAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "default" ) ) + .findFirst() + .orElse( null ); + + // Process the default value + List init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + if ( defaultAnnotation != null && defaultAnnotation.getValue() != null ) { + init = transform( ( BoxNode ) defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); } + // name and type must be simple values String name; String type; - if ( nameAnnotation.getValue() instanceof BoxStringLiteral namelit ) { + if ( nameAnnotation != null && nameAnnotation.getValue() instanceof BoxStringLiteral namelit ) { name = namelit.getValue().trim(); if ( name.isEmpty() ) throw new ExpressionException( "Property [" + prop.getSourceText() + "] name cannot be empty", nameAnnotation ); } else { throw new ExpressionException( "Property [" + prop.getSourceText() + "] name must be a simple value", nameAnnotation ); } - if ( typeAnnotation.getValue() instanceof BoxStringLiteral typelit ) { + if ( typeAnnotation != null && typeAnnotation.getValue() instanceof BoxStringLiteral typelit ) { type = typelit.getValue().trim(); if ( type.isEmpty() ) throw new ExpressionException( "Property [" + prop.getSourceText() + "] type cannot be empty", typeAnnotation ); @@ -886,7 +863,12 @@ private List> transformProperties( Type declaringType, Li javaExpr.addAll( jNameKey ); javaExpr.add( new LdcInsnNode( type ) ); javaExpr.addAll( init ); - javaExpr.addAll( annotationStruct ); + // TODO replace with annotation once ready + javaExpr.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // javaExpr.addAll( annotationStruct ); javaExpr.addAll( documentationStruct ); javaExpr.add( new FieldInsnNode( Opcodes.GETSTATIC, @@ -973,6 +955,102 @@ private List> transformProperties( Type declaringType, Li } } + public static List normlizePropertyAnnotations( BoxProperty prop ) { + + /** + * normalize annotations to allow for + * property String userName; + * This means all inline and pre annotations are treated as post annotations + */ + List finalAnnotations = new ArrayList<>(); + // Start wiith all inline annotatinos + List annotations = prop.getPostAnnotations(); + // Add in any pre annotations that have a value, which allows type, name, or default to be set before + annotations.addAll( prop.getAnnotations().stream().filter( it -> it.getValue() != null ).toList() ); + + // Find the position of the name, type, and default annotations + int namePosition = annotations.stream() + .filter( it -> it.getKey().getValue().equalsIgnoreCase( "name" ) && it.getValue() != null ) + .findFirst() + .map( annotations::indexOf ).orElse( -1 ); + int typePosition = annotations.stream() + .filter( it -> it.getKey().getValue().equalsIgnoreCase( "type" ) && it.getValue() != null ) + .findFirst() + .map( annotations::indexOf ).orElse( -1 ); + int defaultPosition = annotations.stream() + .filter( it -> it.getKey().getValue().equalsIgnoreCase( "default" ) && it.getValue() != null ) + .findFirst() + .map( annotations::indexOf ).orElse( -1 ); + + // Count the number of non-valued keys to determine how to handle them by position later + int numberOfNonValuedKeys = ( int ) annotations.stream() + .map( BoxAnnotation::getValue ) + .filter( Objects::isNull ) + .count(); + List nonValuedKeys = annotations.stream() + .filter( it -> it.getValue() == null ) + .collect( java.util.stream.Collectors.toList() ); + + // Find the name, type, and default annotations + BoxAnnotation nameAnnotation = null; + BoxAnnotation typeAnnotation = null; + BoxAnnotation defaultAnnotation = null; + if ( namePosition > -1 ) + nameAnnotation = annotations.get( namePosition ); + if ( typePosition > -1 ) + typeAnnotation = annotations.get( typePosition ); + if ( defaultPosition > -1 ) + defaultAnnotation = annotations.get( defaultPosition ); + + /** + * If there is no name, if there is more than one nonvalued keys and no type, use the first nonvalued key + * as the type and second nonvalued key as the name. Otherwise, if there are more than one non-valued key, use the first as the name. + */ + if ( namePosition == -1 ) { + if ( numberOfNonValuedKeys > 1 && typePosition == -1 ) { + typeAnnotation = new BoxAnnotation( new BoxFQN( "type", null, null ), + new BoxStringLiteral( nonValuedKeys.get( 0 ).getKey().getValue(), null, null ), null, + null ); + nameAnnotation = new BoxAnnotation( new BoxFQN( "name", null, null ), + new BoxStringLiteral( nonValuedKeys.get( 1 ).getKey().getValue(), null, null ), null, + null ); + finalAnnotations.add( nameAnnotation ); + finalAnnotations.add( typeAnnotation ); + annotations.remove( nonValuedKeys.get( 0 ) ); + annotations.remove( nonValuedKeys.get( 1 ) ); + } else if ( numberOfNonValuedKeys > 0 ) { + nameAnnotation = new BoxAnnotation( new BoxFQN( "name", null, null ), + new BoxStringLiteral( nonValuedKeys.get( 0 ).getKey().getValue(), null, null ), null, + null ); + finalAnnotations.add( nameAnnotation ); + annotations.remove( nonValuedKeys.get( 0 ) ); + } else { + throw new ExpressionException( "Property [" + prop.getSourceText() + "] has no name", prop ); + } + } + + // add type with value of any if not present + if ( typeAnnotation == null ) { + typeAnnotation = new BoxAnnotation( new BoxFQN( "type", null, null ), new BoxStringLiteral( "any", null, null ), null, + null ); + finalAnnotations.add( typeAnnotation ); + } + + // add default with value of null if not present + if ( defaultPosition == -1 ) { + defaultAnnotation = new BoxAnnotation( new BoxFQN( "default", null, null ), new BoxNull( null, null ), null, + null ); + finalAnnotations.add( defaultAnnotation ); + } + + // add remaining annotations + finalAnnotations.addAll( annotations ); + // Now that name, type, and default are finalized, add in any remaining non-valued keys + finalAnnotations.addAll( prop.getAnnotations().stream().filter( it -> it.getValue() == null ).toList() ); + + return finalAnnotations; + } + private static String getBoxExprAsString( BoxExpression expr ) { if ( expr == null ) { return ""; @@ -986,4 +1064,28 @@ private static String getBoxExprAsString( BoxExpression expr ) { throw new BoxRuntimeException( "Unsupported BoxExpr type: " + expr.getClass().getSimpleName() ); } } + + private List generateSetOfCompileTimeMethodNames( BoxClass boxClass ) { + List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .map( BoxFunctionDeclaration::getName ) + .map( this::createKey ) + .collect( java.util.stream.Collectors.toList() ); + + List nodes = new ArrayList(); + + nodes.addAll( AsmHelper.array( Type.getType( Key.class ), methodKeyLists ) ); + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Set.class ), + "of", + Type.getMethodDescriptor( Type.getType( Set.class ), Type.getType( Object[].class ) ), + true + ) + ); + + return nodes; + + } } diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index f35d16e5b..b818da7df 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -502,9 +502,11 @@ private void startup() { private IBoxpiler chooseBoxpiler() { switch ( ( String ) this.configuration.experimental.getOrDefault( "compiler", "java" ) ) { case "asm" : + useASMBoxPiler(); return ASMBoxpiler.getInstance(); case "java" : default : + useJavaBoxpiler(); return JavaBoxpiler.getInstance(); } } diff --git a/src/test/java/TestCases/asm/phase3/AbstractClass.bx b/src/test/java/TestCases/asm/phase3/AbstractClass.bx new file mode 100644 index 000000000..af9af4554 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/AbstractClass.bx @@ -0,0 +1,9 @@ +abstract class { + + function normal() { + return "normal"; + } + + abstract function abstractMethod() + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc b/src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc new file mode 100644 index 000000000..dc3d808e4 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc @@ -0,0 +1,9 @@ +abstract component { + + function normal() { + return "normal"; + } + + abstract function abstractMethod() + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Animal.cfc b/src/test/java/TestCases/asm/phase3/Animal.cfc new file mode 100644 index 000000000..7fc908207 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Animal.cfc @@ -0,0 +1,36 @@ +component { + variables.results = []; + results.append('animal pseudo ' & getFileFromPath( getCurrentTemplatePath())) + variables.inAnimal = true; + variables.inDog = false; + + function init() { + results.append('Animal init ' & getFileFromPath( getCurrentTemplatePath())) + } + + function speak() { + throw( "speak method not implemented" ); + } + + function isWarmBlooded() { + // this needs to be a reference to the bottom most class + results.append( "animal this is: " & this.$bx.meta.name ) + // We need to see the variables scope of the bottom most class + results.append( "animal sees inDog as: " & inDog ) + return true; + } + + function getScientificName() { + // this needs to be a reference to the bottom most class + results.append( "super animal sees: " & this.$bx.meta.name ) + // We need to see the variables scope of the bottom most class + results.append( "super sees inDog as: " & inDog ) + + return "Animal Kingdom"; + } + + function getResults() { + return results; + } + + } diff --git a/src/test/java/TestCases/asm/phase3/ApplicationTest.java b/src/test/java/TestCases/asm/phase3/ApplicationTest.java new file mode 100644 index 000000000..989e30a58 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ApplicationTest.java @@ -0,0 +1,242 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package TestCases.asm.phase3; + +import static com.google.common.truth.Truth.assertThat; + +import java.nio.file.Path; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.application.Application; +import ortus.boxlang.runtime.application.BaseApplicationListener; +import ortus.boxlang.runtime.context.ApplicationBoxContext; +import ortus.boxlang.runtime.context.BaseBoxContext; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.RequestBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.dynamic.casters.DateTimeCaster; +import ortus.boxlang.runtime.scopes.ApplicationScope; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.SessionScope; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.IStruct; + +public class ApplicationTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + instance.useASMBoxPiler(); + } + + @AfterEach + public void teardownEach() { + instance.useJavaBoxpiler(); + } + + @DisplayName( "application basics" ) + @Test + public void testBasicApplication() { + // @formatter:off + instance.executeSource( + """ + application name="myAppsdfsdf" sessionmanagement="true"; + + result = application; + result2 = session; + startTime = ApplicationStartTime() + """, context ); + // @formatter:on + + assertThat( variables.get( result ) ).isInstanceOf( ApplicationScope.class ); + assertThat( variables.get( Key.of( "result2" ) ) ).isInstanceOf( SessionScope.class ); + + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); + Instant actual = DateTimeCaster.cast( variables.get( Key.of( "startTime" ) ) ).getWrapped().toInstant(); + Instant now = Instant.now(); + long differenceInSeconds = ChronoUnit.SECONDS.between( actual, now ); + + assertThat( app.getName().getName() ).isEqualTo( "myAppsdfsdf" ); + assertThat( app.getSessionsCache() ).isNotNull(); + assertThat( app.getApplicationScope() ).isNotNull(); + assertThat( app.getApplicationScope().getName().getName() ).isEqualTo( "application" ); + assertThat( app.getClassLoaders() ).isNotNull(); + assertThat( app.hasStarted() ).isTrue(); + assertThat( differenceInSeconds ).isAtMost( 1L ); + } + + @Test + public void testGetAppMeta() { + // @formatter:off + instance.executeSource( + """ + application name="myAppsdfsdf2" sessionmanagement="true"; + result = GetApplicationMetadata(); + """, context ); + // @formatter:on + + assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); + assertThat( variables.getAsStruct( result ).get( "name" ) ).isEqualTo( "myAppsdfsdf2" ); + assertThat( variables.getAsStruct( result ).get( "sessionmanagement" ).toString() ).isEqualTo( "true" ); + } + + @Test + public void testGetDefaultAppMeta() { + // @formatter:off + instance.executeSource( + """ + result = GetApplicationMetadata(); + """, context ); + // @formatter:on + + assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); + assertThat( variables.getAsStruct( result ).get( "name" ) ).isEqualTo( "" ); + assertThat( variables.getAsStruct( result ).get( "sessionmanagement" ).toString() ).isEqualTo( "false" ); + } + + @DisplayName( "java settings setup" ) + @Test + public void testJavaSettings() { + // @formatter:off + instance.executeSource( + """ + application name="myJavaApp" javaSettings={ + loadPaths = [ "/src/test/resources/libs" ], + reloadOnChange = true + }; + + import com.github.benmanes.caffeine.cache.Caffeine + targetInstance = Caffeine.newBuilder() + + import org.apache.commons.lang3.ClassUtils + targetInstance2 = ClassUtils.getClass() + """, context ); + // @formatter:on + + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); + assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); + } + + @DisplayName( "Ad-hoc config override" ) + @Test + public void testAdHocConfigOverride() { + + context.injectParentContext( new BaseBoxContext() { + + public IStruct getConfig() { + IStruct config = super.getConfig(); + config.put( "adHocConfig", "adHocConfigValue" ); + return config; + } + } ); + + assertThat( context.getConfigItem( Key.of( "adHocConfig" ) ) ).isEqualTo( "adHocConfigValue" ); + } + + @DisplayName( "Can resolve java settings paths with a full jar/class path" ) + @Test + public void testJavaSettingsPaths() { + // @formatter:off + instance.executeSource( + """ + application name="myJavaApp" javaSettings={ + loadPaths = [ "/src/test/resources/libs/helloworld.jar" ], + reloadOnChange = true + }; + """, context ); + // @formatter:on + + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); + assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); + } + + @DisplayName( "Can resolve java settings paths with a full jar/class path with bad pathing" ) + @Test + public void testJavaSettingsBadPaths() { + // @formatter:off + instance.executeSource( + """ + application name="myJavaApp" javaSettings={ + loadPaths = [ "\\src\\test\\resources\\libs\\helloworld.jar" ], + reloadOnChange = true + }; + """, context ); + // @formatter:on + + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); + + assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); + } + + @DisplayName( "Can resolve relative paths" ) + @Test + public void testJavaSettingsRelativePaths() { + + RequestBoxContext requestContext = context.getParentOfType( RequestBoxContext.class ); + BaseApplicationListener listener = requestContext.getApplicationListener(); + + // Mock the relative path + listener.getSettings() + .put( "source", Path.of( "src/test/resources/Application.bx" ).toAbsolutePath().toString() ); + + // @formatter:off + instance.executeSource( + """ + application name="myJavaApp" javaSettings={ + loadPaths = [ "libs/helloworld.jar" ], + reloadOnChange = true + }; + """, context ); + // @formatter:on + + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); + assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); + } + +} diff --git a/src/test/java/TestCases/asm/phase3/CFImportTest.cfc b/src/test/java/TestCases/asm/phase3/CFImportTest.cfc new file mode 100644 index 000000000..1bcae0686 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/CFImportTest.cfc @@ -0,0 +1,13 @@ +import src.test.java.TestCases.phase3.Stack; + +component { + function init() { + stack = new Stack(); + } + + function doSomething() { + if (!stack.empty() ) { + // do stuff + } + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/CFImportTest2.cfc b/src/test/java/TestCases/asm/phase3/CFImportTest2.cfc new file mode 100644 index 000000000..6aff56356 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/CFImportTest2.cfc @@ -0,0 +1,13 @@ +import "src.test.java.TestCases.phase3.Stack"; + +component { + function init() { + stack = new Stack(); + } + + function doSomething() { + if (!stack.empty() ) { + // do stuff + } + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Chihuahua.cfc b/src/test/java/TestCases/asm/phase3/Chihuahua.cfc new file mode 100644 index 000000000..f1619b84d --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Chihuahua.cfc @@ -0,0 +1,17 @@ +component extends="Dog" { + results.append('Chihuahua pseudo ' & getFileFromPath( getCurrentTemplatePath())) + + function init() { + super.init(); + results.append('Chihuahua init ' & getFileFromPath( getCurrentTemplatePath())) + } + + function speak() { + return "Yip Yip!"; + } + + function getScientificName() { + return "barkus annoyus " & super.getScientificName(); + } + + } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Child.cfc b/src/test/java/TestCases/asm/phase3/Child.cfc new file mode 100644 index 000000000..ffc8ed46e --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Child.cfc @@ -0,0 +1,12 @@ +component extends="Parent" { + + function init() { + return super.init(); + } + + private void function setupFrameworkDefaults() { + request.calls.append( "running child setupFrameworkDefaults()" ); + super.setupFrameworkDefaults(); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc b/src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc new file mode 100644 index 000000000..e624afcaf --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java new file mode 100644 index 000000000..fa16b3abb --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -0,0 +1,1261 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package TestCases.asm.phase3; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.runnables.IClassRunnable; +import ortus.boxlang.runtime.runnables.RunnableLoader; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.RequestScope; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.Array; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.exceptions.AbstractClassException; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.types.meta.ClassMeta; + +public class ClassTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + static Key foo = new Key( "foo" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + instance.useASMBoxPiler(); + } + + @AfterEach + public void teardownEach() { + instance.useJavaBoxpiler(); + } + + @DisplayName( "Test can create vanilla module config" ) + @Test + void testVanillaModuleConfig() { + + IClassRunnable cfc = ( IClassRunnable ) DynamicObject.of( RunnableLoader.getInstance().loadClass( + """ + /** + * This is the module descriptor and entry point for your module in the Runtime. + * The unique name of the moduel is the name of the directory on the modules folder. + * A BoxLang Mapping will be created for you with the name of the module. + * + * A Module can have the following folders that will be automatically registered: + * + bifs - Custom BIFs that will be registered into the runtime + * + interceptors - Custom Interceptors that will be registered into the runtime via the configure() method + * + libs - Custom Java libraries that your module leverages + * + tags - Custom tags that will be registered into the runtime + * + * Every Module will have it's own ClassLoader that will be used to load the module libs and dependencies. + */ + component{ + + /** + * -------------------------------------------------------------------------- + * Module Properties + * -------------------------------------------------------------------------- + * Here is where you define the properties of your module that the module service + * will use to register and activate your module + */ + + /** + * Your module version. Try to use semantic versioning + * @mandatory + */ + this.version = "1.0.0"; + + /** + * The BoxLang mapping for your module. All BoxLang modules are registered with an internal + * mapping prefix of : bxModules.{this.mapping}, /bxmodules/{this.mapping}. Ex: bxModules.test, /bxmodules/test + */ + this.mapping = "test"; + + /** + * Who built the module + */ + this.author = "Luis Majano"; + + /** + * The module description + */ + this.description = "This module does amazing things"; + + /** + * The module web URL + */ + this.webURL = "https://www.ortussolutions.com"; + + /** + * This boolean flag tells the module service to skip the module registration/activation process. + */ + this.disabled = false; + + /** + * -------------------------------------------------------------------------- + * Module Methods + * -------------------------------------------------------------------------- + */ + + /** + * Called by the ModuleService on module registration + * + * @moduleRecord - The module record registered in the ModuleService + * @runtime - The Runtime instance + */ + function configure( moduleRecord, runtime ){ + /** + * Every module has a settings configuration object + */ + settings = { + loadedOn : now(), + loadedBy : "Luis Majano" + }; + + /** + * The module interceptors to register into the runtime + */ + interceptors = [ + // { class="path.to.Interceptor", properties={} } + { class="bxModules.test.interceptors.Listener", properties={} } + ]; + + /** + * A list of custom interception points to register into the runtime + */ + customInterceptionPoints = [ "onBxTestModule" ]; + } + + /** + * Called by the ModuleService on module activation + * + * @moduleRecord - The module record registered in the ModuleService + * @runtime - The Runtime instance + */ + function onLoad( moduleRecord, runtime ){ + + } + + /** + * Called by the ModuleService on module deactivation + * + * @moduleRecord - The module record registered in the ModuleService + * @runtime - The Runtime instance + */ + function onUnload( moduleRecord, runtime ){ + + } + + /** + * -------------------------------------------------------------------------- + * Module Events + * -------------------------------------------------------------------------- + * You can listen to any Runtime events by creating the methods + * that match the approved Runtime Interception Points + */ + } + + """, context, BoxSourceType.CFSCRIPT ) ) + .invokeConstructor( context ) + .getTargetInstance(); + } + + @DisplayName( "Test a basic boxlang class" ) + @Test + public void testBasicBLClass() { + + // @formatter:off + IClassRunnable bxClass = ( IClassRunnable ) DynamicObject.of( RunnableLoader.getInstance().loadClass( + """ + import foo; + import java.lang.System; + + /** + * This is my class description + * + * @brad wood + * @luis + */ + @foo "bar" + class accessors=true singleton gavin="pickin" inject { + + property numeric age default=1; + property numeric test; + property testAlone; + + variables.setup=true; + System.out.println( "word" ); + request.foo="bar"; + println( request.asString()) + isInitted = false; + println( "current template is " & getCurrentTemplatePath() ); + printLn( foo() ) + + function init() { + isInitted = true; + } + + function foo() { + return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; + } + + private function bar() { + return "whee"; + } + + function getThis() { + return this; + } + + function runThisFoo() { + return this.foo(); + } + } + """, context, BoxSourceType.BOXSCRIPT ) ) + .invokeConstructor( context ) + .getTargetInstance(); + // @formatter:on + + // Test shorthand properties work + assertThat( + bxClass.dereferenceAndInvoke( context, Key.of( "getAge" ), new Object[] {}, false ) + ).isEqualTo( 1 ); + assertThat( + bxClass.dereferenceAndInvoke( context, Key.of( "getTest" ), new Object[] {}, false ) + ).isEqualTo( null ); + assertThat( + bxClass.dereferenceAndInvoke( context, Key.of( "getTestAlone" ), new Object[] {}, false ) + ).isEqualTo( null ); + var mdProperties = bxClass.getMetaData().get( Key.of( "properties" ) ); + + // execute public method + Object funcResult = bxClass.dereferenceAndInvoke( context, Key.of( "foo" ), new Object[] {}, false ); + + // private methods error + Throwable t = assertThrows( BoxRuntimeException.class, + () -> bxClass.dereferenceAndInvoke( context, Key.of( "bar" ), new Object[] {}, false ) ); + assertThat( t.getMessage().contains( "bar" ) ).isTrue(); + + // Can call public method that accesses private method, and variables, and request scope + assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); + assertThat( context.getScope( RequestScope.name ).get( Key.of( "foo" ) ) ).isEqualTo( "bar" ); + + // This scope is reference to actual CFC instance + funcResult = bxClass.dereferenceAndInvoke( context, Key.of( "getThis" ), new Object[] {}, false ); + assertThat( funcResult ).isEqualTo( bxClass ); + + // Can call public methods on this + funcResult = bxClass.dereferenceAndInvoke( context, Key.of( "runThisFoo" ), new Object[] {}, false ); + assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); + } + + @DisplayName( "basic class" ) + @Test + public void testBasicCFClass() { + // @formatter:off + IClassRunnable cfc = ( IClassRunnable ) DynamicObject.of( RunnableLoader.getInstance().loadClass( + """ + /** + * This is my class description + * + * @brad wood + * @luis + */ + component singleton gavin="pickin" inject foo="bar" { + + variables.setup=true; + createObject('java','java.lang.System').out.println( "word" ); + request.foo="bar"; + println( request.asString()) + isInitted = false; + println( "current template is " & getCurrentTemplatePath() ); + printLn( foo() ) + + function init() { + isInitted = true; + } + + function foo() { + return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; + } + + private function bar() { + return "whee" ; + } + + function getThis() { + return this; + } + + function runThisFoo() { + return this.foo() ; + } + } + + + """, context, BoxSourceType.CFSCRIPT ) ) + .invokeConstructor( context ) + .getTargetInstance(); + // @formatter:on + + // execute public method + Object funcResult = cfc.dereferenceAndInvoke( context, Key.of( "foo" ), new Object[] {}, false ); + + // private methods error + Throwable t = assertThrows( BoxRuntimeException.class, + () -> cfc.dereferenceAndInvoke( context, Key.of( "bar" ), new Object[] {}, false ) ); + assertThat( t.getMessage().contains( "bar" ) ).isTrue(); + + // Can call public method that accesses private method, and variables, and request scope + assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); + assertThat( context.getScope( RequestScope.name ).get( Key.of( "foo" ) ) ).isEqualTo( "bar" ); + + // This scope is reference to actual CFC instance + funcResult = cfc.dereferenceAndInvoke( context, Key.of( "getThis" ), new Object[] {}, false ); + assertThat( funcResult ).isEqualTo( cfc ); + + // Can call public methods on this + funcResult = cfc.dereferenceAndInvoke( context, Key.of( "runThisFoo" ), new Object[] {}, false ); + assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); + } + + @DisplayName( "basic class file" ) + @Test + public void testBasicClassFile() { + // @formatter:off + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.MyClass(); + // execute public method + result = cfc.foo(); + + // private methods error + try { + cfc.bar() + assert false; + } catch( BoxRuntimeException e ) { + assert e.message contains "bar"; + } + + // Can call public method that accesses private method, and variables, and request scope + assert result == "I work! whee true true bar true"; + assert request.foo == "bar"; + + // This scope is reference to actual CFC instance + assert cfc.$bx.$class.getName() == cfc.getThis().$bx.$class.getName(); + + // Can call public methods on this + assert cfc.runThisFoo() == "I work! whee true true bar true"; + """, context ); + // @formatter:on + } + + @DisplayName( "legacy meta" ) + @Test + public void testlegacyMeta() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.MyClass(); + """, context ); + + var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); + var meta = cfc.getMetaData(); + assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClass" ); + assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); + assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClass" ); + assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClass.bx" ) ).isTrue(); + // assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); + assertThat( meta.get( Key.of( "properties" ) ) ).isInstanceOf( Array.class ); + assertThat( meta.getAsArray( Key.of( "properties" ) ) ).hasSize( 1 ); + Struct prop = ( Struct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); + assertThat( prop ).doesNotContainKey( Key.of( "defaultValue" ) ); + + assertThat( meta.get( Key.of( "functions" ) ) instanceof Array ).isTrue(); + assertThat( meta.getAsArray( Key.of( "functions" ) ).size() ).isEqualTo( 5 ); + assertThat( meta.get( Key.of( "extends" ) ) ).isNull(); + assertThat( meta.get( Key.of( "output" ) ) ).isEqualTo( false ); + assertThat( meta.get( Key.of( "persisent" ) ) ).isEqualTo( false ); + assertThat( meta.get( Key.of( "accessors" ) ) ).isEqualTo( true ); + } + + @DisplayName( "legacy meta CF" ) + @Test + public void testlegacyMetaCF() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.MyClassCF(); + """, context ); + + var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); + var meta = cfc.getMetaData(); + assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClassCF" ); + assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); + assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClassCF" ); + assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClassCF.cfc" ) ).isTrue(); + // assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); + assertThat( meta.get( Key.of( "properties" ) ) ).isInstanceOf( Array.class ); + assertThat( meta.get( Key.of( "functions" ) ) instanceof Array ).isTrue(); + assertThat( meta.getAsArray( Key.of( "functions" ) ).size() ).isEqualTo( 5 ); + assertThat( meta.get( Key.of( "extends" ) ) ).isNull(); + assertThat( meta.get( Key.of( "output" ) ) ).isEqualTo( true ); + assertThat( meta.get( Key.of( "persisent" ) ) ).isEqualTo( false ); + assertThat( meta.get( Key.of( "accessors" ) ) ).isEqualTo( false ); + } + + @DisplayName( "It should call onMissingMethod with pos args" ) + @Test + public void testOnMissingMethodPos() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.OnMissingMethod(); + result = cfc.someFunc( "first", "second" ); + """, context ); + + String res = variables.getAsString( result ); + assertThat( res ).isEqualTo( "someFuncsecond" ); + } + + @DisplayName( "It should call onMissingMethod with named args" ) + @Test + public void testOnMissingMethodNamed() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.OnMissingMethod(); + result = cfc.someFunc( foo="first", bar="second" ); + """, context ); + + String res = variables.getAsString( result ); + assertThat( res ).isEqualTo( "someFuncsecond" ); + } + + @DisplayName( "box meta" ) + @Test + public void testBoxMeta() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.MyClass(); + """, context ); + + var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); + var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); + var meta = boxMeta.meta; + assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); + assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClass" ); + assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClass.bx" ) ).isTrue(); + assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); + assertThat( meta.get( Key.of( "properties" ) ) instanceof Array ).isTrue(); + assertThat( meta.get( Key.of( "functions" ) ) instanceof Array ).isTrue(); + + assertThat( meta.get( Key.of( "extends" ) ) instanceof IStruct ).isTrue(); + + assertThat( meta.getAsArray( Key.of( "functions" ) ).size() ).isEqualTo( 5 ); + var fun1 = meta.getAsArray( Key.of( "functions" ) ).get( 0 ); + assertThat( fun1 ).isInstanceOf( Struct.class ); + assertThat( ( ( IStruct ) fun1 ).containsKey( Key.of( "name" ) ) ).isTrue(); + System.out.println( meta.getAsArray( Key.of( "functions" ) ).asString() ); + + assertThat( meta.get( Key.of( "documentation" ) ) instanceof IStruct ).isTrue(); + var docs = meta.getAsStruct( Key.of( "documentation" ) ); + assertThat( docs.getAsString( Key.of( "brad" ) ).trim() ).isEqualTo( "wood" ); + assertThat( docs.get( Key.of( "luis" ) ) ).isEqualTo( "" ); + assertThat( docs.getAsString( Key.of( "hint" ) ).trim() ).isEqualTo( "This is my class description continued on this line \nand this one as well." ); + + assertThat( meta.get( Key.of( "annotations" ) ) instanceof IStruct ).isTrue(); + var annos = meta.getAsStruct( Key.of( "annotations" ) ); + assertThat( annos.getAsString( Key.of( "foo" ) ).trim() ).isEqualTo( "bar" ); + // assertThat( annos.getAsString( Key.of( "implements" ) ).trim() ).isEqualTo( "Luis,Jorge" ); + assertThat( annos.getAsString( Key.of( "singleton" ) ).trim() ).isEqualTo( "" ); + assertThat( annos.getAsString( Key.of( "gavin" ) ).trim() ).isEqualTo( "pickin" ); + assertThat( annos.getAsString( Key.of( "inject" ) ).trim() ).isEqualTo( "" ); + + } + + @DisplayName( "properties" ) + @Test + public void testProperties() { + // @formatter:off + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.PropertyTest(); + nameGet = cfc.getMyProperty(); + invalidSetErrored=false; + try { + // property is typed as string, an array should blow up + setResult = cfc.setMyProperty( [] ); + } catch( any e ) { + invalidSetErrored=true; + } + setResult = cfc.setMyProperty( "anotherValue" ); + nameGet2 = cfc.getMyProperty(); + test1 = cfc.getShortcutWithDefault() + test2 = cfc.getTypedShortcutWithDefault() + """, context ); + // @formatter:on + + var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); + assertThat( variables.get( Key.of( "nameGet" ) ) ).isEqualTo( "myDefaultValue" ); + assertThat( variables.get( Key.of( "nameGet2" ) ) ).isEqualTo( "anotherValue" ); + assertThat( variables.get( Key.of( "setResult" ) ) ).isEqualTo( cfc ); + assertThat( variables.get( Key.of( "invalidSetErrored" ) ) ).isEqualTo( true ); + assertThat( variables.get( Key.of( "test1" ) ) ).isEqualTo( "myDefaultValue" ); + assertThat( variables.get( Key.of( "test2" ) ) ).isEqualTo( "myDefaultValue2" ); + + var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); + var meta = boxMeta.meta; + + assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 6 ); + + var prop1 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); + assertThat( prop1.get( "name" ) ).isEqualTo( "myProperty" ); + assertThat( prop1.get( "defaultValue" ) ).isEqualTo( "myDefaultValue" ); + assertThat( prop1.get( "type" ) ).isEqualTo( "string" ); + + var prop1Annotations = prop1.getAsStruct( Key.of( "annotations" ) ); + assertThat( prop1Annotations.size() ).isEqualTo( 5 ); + + assertThat( prop1Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); + assertThat( prop1Annotations.get( Key.of( "preAnno" ) ) ).isEqualTo( "" ); + + assertThat( prop1Annotations.containsKey( Key.of( "inject" ) ) ).isTrue(); + assertThat( prop1Annotations.get( Key.of( "inject" ) ) ).isEqualTo( "" ); + + var prop2 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 1 ); + assertThat( prop2.get( "name" ) ).isEqualTo( "anotherprop" ); + assertThat( prop2.get( "defaultValue" ) ).isEqualTo( null ); + assertThat( prop2.get( "type" ) ).isEqualTo( "string" ); + + var prop2Annotations = prop2.getAsStruct( Key.of( "annotations" ) ); + assertThat( prop2Annotations.size() ).isEqualTo( 4 ); + + assertThat( prop2Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); + assertThat( prop2Annotations.get( Key.of( "preAnno" ) ) instanceof Array ).isTrue(); + Array preAnno = prop2Annotations.getAsArray( Key.of( "preAnno" ) ); + assertThat( preAnno.size() ).isEqualTo( 2 ); + assertThat( preAnno.get( 0 ) ).isEqualTo( "myValue" ); + assertThat( preAnno.get( 1 ) ).isEqualTo( "anothervalue" ); + + var prop3 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 2 ); + assertThat( prop3.get( "name" ) ).isEqualTo( "theName" ); + assertThat( prop3.get( "defaultValue" ) ).isEqualTo( null ); + assertThat( prop3.get( "type" ) ).isEqualTo( "any" ); + + var prop3Annotations = prop3.getAsStruct( Key.of( "annotations" ) ); + assertThat( prop3Annotations.size() ).isEqualTo( 4 ); + assertThat( prop3Annotations.containsKey( Key.of( "ID" ) ) ).isTrue(); + assertThat( prop3Annotations.get( Key.of( "ID" ) ) ).isEqualTo( "" ); + + var prop4 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 3 ); + assertThat( prop4.get( "name" ) ).isEqualTo( "name" ); + assertThat( prop4.get( "defaultValue" ) ).isEqualTo( null ); + assertThat( prop4.get( "type" ) ).isEqualTo( "string" ); + + var prop2Docs = prop2.getAsStruct( Key.of( "documentation" ) ); + assertThat( prop2Docs.size() ).isEqualTo( 3 ); + assertThat( prop2Docs.getAsString( Key.of( "brad" ) ).trim() ).isEqualTo( "wood" ); + assertThat( prop2Docs.getAsString( Key.of( "luis" ) ).trim() ).isEqualTo( "" ); + assertThat( prop2Docs.getAsString( Key.of( "hint" ) ).trim() ).isEqualTo( "This is my property" ); + } + + @DisplayName( "properties" ) + @Test + public void testPropertiesCF() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.PropertyTestCF(); + nameGet = cfc.getMyProperty(); + setResult = cfc.SetMyProperty( "anotherValue" ); + nameGet2 = cfc.getMyProperty(); + test1 = cfc.getShortcutWithDefault() + test2 = cfc.getTypedShortcutWithDefault() + """, context ); + + var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); + + assertThat( variables.get( Key.of( "nameGet" ) ) ).isEqualTo( "myDefaultValue" ); + assertThat( variables.get( Key.of( "nameGet2" ) ) ).isEqualTo( "anotherValue" ); + assertThat( variables.get( Key.of( "setResult" ) ) ).isEqualTo( cfc ); + assertThat( variables.get( Key.of( "test1" ) ) ).isEqualTo( "myDefaultValue" ); + assertThat( variables.get( Key.of( "test2" ) ) ).isEqualTo( "myDefaultValue2" ); + + var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); + var meta = boxMeta.meta; + + assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 4 ); + + var prop1 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); + assertThat( prop1.get( "name" ) ).isEqualTo( "myProperty" ); + assertThat( prop1.get( "defaultValue" ) ).isEqualTo( "myDefaultValue" ); + assertThat( prop1.get( "type" ) ).isEqualTo( "string" ); + + var prop1Annotations = prop1.getAsStruct( Key.of( "annotations" ) ); + assertThat( prop1Annotations.size() ).isEqualTo( 5 ); + + assertThat( prop1Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); + assertThat( prop1Annotations.get( Key.of( "preAnno" ) ) ).isEqualTo( "" ); + + assertThat( prop1Annotations.containsKey( Key.of( "inject" ) ) ).isTrue(); + assertThat( prop1Annotations.get( Key.of( "inject" ) ) ).isEqualTo( "" ); + + var prop2 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 1 ); + assertThat( prop2.get( "name" ) ).isEqualTo( "anotherprop" ); + assertThat( prop2.get( "defaultValue" ) ).isEqualTo( null ); + assertThat( prop2.get( "type" ) ).isEqualTo( "string" ); + + var prop2Annotations = prop2.getAsStruct( Key.of( "annotations" ) ); + assertThat( prop2Annotations.size() ).isEqualTo( 6 ); + + assertThat( prop2Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); + assertThat( prop2Annotations.get( Key.of( "preAnno" ) ) instanceof Array ).isTrue(); + Array preAnno = prop2Annotations.getAsArray( Key.of( "preAnno" ) ); + assertThat( preAnno.size() ).isEqualTo( 2 ); + assertThat( preAnno.get( 0 ) ).isEqualTo( "myValue" ); + assertThat( preAnno.get( 1 ) ).isEqualTo( "anothervalue" ); + + var prop2Docs = prop2.getAsStruct( Key.of( "documentation" ) ); + assertThat( prop2Docs.size() ).isEqualTo( 3 ); + assertThat( prop2Docs.getAsString( Key.of( "brad" ) ).trim() ).isEqualTo( "wood" ); + assertThat( prop2Docs.getAsString( Key.of( "luis" ) ).trim() ).isEqualTo( "" ); + assertThat( prop2Docs.getAsString( Key.of( "hint" ) ).trim() ).isEqualTo( "This is my property" ); + + } + + @DisplayName( "Implicit Constructor named" ) + @Test + public void testImplicitConstructorNamed() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.ImplicitConstructorTest( name="brad", age=43, favoriteColor="blue" ); + name = cfc.getName(); + age = cfc.getAge(); + favoriteColor = cfc.getFavoriteColor(); + """, context ); + + assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 43 ); + assertThat( variables.get( Key.of( "favoriteColor" ) ) ).isEqualTo( "blue" ); + + } + + @DisplayName( "Implicit Constructor named argumentCollection" ) + @Test + public void testImplicitConstructorNamedArgumentCollection() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.ImplicitConstructorTest( argumentCollection={ name="brad", age=43, favoriteColor="blue" } ); + name = cfc.getName(); + age = cfc.getAge(); + favoriteColor = cfc.getFavoriteColor(); + """, context ); + + assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 43 ); + assertThat( variables.get( Key.of( "favoriteColor" ) ) ).isEqualTo( "blue" ); + + } + + @DisplayName( "Implicit Constructor positional" ) + @Test + public void testImplicitConstructorPositional() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.ImplicitConstructorTest( {name="brad", age=43, favoriteColor="blue" }); + name = cfc.getName(); + age = cfc.getAge(); + favoriteColor = cfc.getFavoriteColor(); + """, context ); + + assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 43 ); + assertThat( variables.get( Key.of( "favoriteColor" ) ) ).isEqualTo( "blue" ); + + } + + @DisplayName( "InitMethod Test" ) + @Test + public void testInitMethod() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.InitMethodTest( ); + + result = cfc.getInittedProperly(); + """, context ); + + assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( true ); + + } + + @DisplayName( "PseudoConstructor can output" ) + @Test + public void testPseudoConstructorOutput() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.PseudoConstructorOutput(); + result = getBoxContext().getBuffer().toString() + + """, context ); + + assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "PseudoConstructorOutput" ); + + } + + @DisplayName( "PseudoConstructor will not output" ) + @Test + public void testPseudoConstructorNoOutput() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.PseudoConstructorNoOutput(); + result = getBoxContext().getBuffer().toString() + + """, context ); + + assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "" ); + + } + + @DisplayName( "can extend" ) + @Test + public void testCanExtend() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.Chihuahua(); + result = cfc.speak() + warm = cfc.isWarmBlooded() + name = cfc.getScientificName() + results = cfc.getResults() + """, context ); + + // Polymorphism invokes overridden method + assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "Yip Yip!" ); + // inherited method from base class + assertThat( variables.get( Key.of( "warm" ) ) ).isEqualTo( true ); + // Delegate to super.method() in parent class + assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "barkus annoyus Canis lupus Animal Kingdom" ); + + // This array represents a specific order of operations that occur during the instantiation of our object hierachy + // as well as specific values that need to be present to ensure correct behaviors + assertThat( variables.getAsArray( Key.of( "results" ) ).toArray() ).isEqualTo( new Object[] { + // top most super class is instantiated first. getCurrentTemplate() shows that file + "animal pseudo Animal.cfc", + // Then the next super class is instantiated. getCurrentTemplate() shows that file + "Dog pseudo Dog.cfc", + // The variables scope in the Doc pseudo constructor is the "same" variables scope as the Animal pseudo constructor that ran before it + "dog sees variables.inAnimal as: true", + // And lastly, the concrete class is instantiated. getCurrentTemplate() shows that file + "Chihuahua pseudo Chihuahua.cfc", + // I'm calling super.init() first, so animal inits first. getCurrentTemplate() shows the current class. INCOMPAT WITH CF which returns concrete + // class! + "Animal init Animal.cfc", + // Then dog inits as we work backwards. getCurrentTemplate() shows the current class. INCOMPAT WITH CF which returns concrete class! + "Dog init Dog.cfc", + // Then the concrete class inits. getCurrentTemplate() shows the concrete class. + "Chihuahua init Chihuahua.cfc", + // A method inherited from a base class, sees "this" as the concrete class. + "animal this is: src.test.java.testcases.phase3.Chihuahua", + // A method inherited from a base class, sees the top level "variables" scope. + "animal sees inDog as: true", + // A method delegated to as super.foo() sees "this" as the concrete class. + "super animal sees: src.test.java.testcases.phase3.Chihuahua", + // A method delegated to as super.foo() sees the top level "variables" scope. + "super sees inDog as: true", + } ); + + var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); + var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); + var meta = boxMeta.meta; + + assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.Chihuahua" ); + + IStruct extendsMeta = meta.getAsStruct( Key.of( "extends" ) ); + assertThat( extendsMeta.getAsString( Key.of( "name" ) ).endsWith( ".Dog" ) ).isTrue(); + + extendsMeta = extendsMeta.getAsStruct( Key.of( "extends" ) ); + assertThat( extendsMeta.getAsString( Key.of( "name" ) ).endsWith( ".Animal" ) ).isTrue(); + + extendsMeta = extendsMeta.getAsStruct( Key.of( "extends" ) ); + assertThat( extendsMeta ).hasSize( 0 ); + + } + + @DisplayName( "class as struct" ) + @Test + public void testClassAsStruct() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.MyClass(); + result = isStruct( cfc ) + cfc.foo = "bar" + result2 = structGet( "cfc.foo") + keyArray = structKeyArray( cfc ) + + """, context ); + + assertThat( variables.get( result ) ).isEqualTo( true ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "bar" ); + assertThat( variables.get( Key.of( "keyArray" ) ) ).isInstanceOf( Array.class ); + + } + + @Test + void testJavaMeta() { + // @formatter:off + instance.executeSource( + """ + jClass = createObject( "java", "java.lang.System" ) + result = jClass.$bx.meta + clazz = jClass.$bx.$class + println( clazz ) + """, context ); + // @formatter:on + assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); + assertThat( variables.get( Key.of( "clazz" ) ) ).isEqualTo( System.class ); + } + + @Test + public void testFunctionMeta() { + // @formatter:off + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.FunctionMeta(); + result = cfc.$bx.meta + println( result ) + """, context ); + // @formatter:on + assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); + } + + @Test + public void testSuperHeadlessFunctionInvocationToChild() { + + instance.executeSource( + """ + request.calls = []; + cfc = new src.test.java.TestCases.phase3.Child(); + result = request.calls; + """, context ); + + assertThat( variables.getAsArray( result ) ).hasSize( 2 ); + assertThat( variables.getAsArray( result ).get( 0 ) ).isEqualTo( "running child setupFrameworkDefaults()" ); + assertThat( variables.getAsArray( result ).get( 1 ) ).isEqualTo( "running parent setupFrameworkDefaults()" ); + } + + @Test + public void testClassWrappedInScriptIsland() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.ClassWrappedInScript(); + """, context ); + + } + + @Test + public void testClassIgnoreLeadingComment() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.ClassLeadingComment(); + """, context ); + + } + + @Test + public void testClassIgnoreTrailingComment() { + + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.ClassTrailingComment(); + """, context ); + + } + + @Test + public void testCFImport() { + + instance.executeSource( + """ + foo = new src.test.java.TestCases.phase3.CFImportTest(); + foo.doSomething(); + """, context ); + + } + + @Test + public void testCFImport2() { + // This version quotes the class being imported + instance.executeSource( + """ + foo = new src.test.java.TestCases.phase3.CFImportTest2(); + foo.doSomething(); + """, context ); + + } + + @Test + public void testInlineJavaImplements() { + instance.executeSource( + """ + import java:java.lang.Thread; + jRunnable = new src.test.java.TestCases.phase3.JavaImplements(); + assert jRunnable instanceof "java.lang.Runnable" + jThread = new java:Thread( jRunnable ); + jThread.start(); + """, context ); + + } + + @Test + public void testInlineJavaExtends() { + instance.executeSource( + """ + import java.util.Timer; + myTask = new src.test.java.TestCases.phase3.JavaExtends(); + assert myTask instanceof "java.util.TimerTask" + + jtimer = new Timer(); + jtimer.schedule(myTask, 1000); + myTask.cancel() + """, context ); + + } + + @Test + public void testInlineJavaExtendsField() { + instance.executeSource( + """ + myContext = new src.test.java.TestCases.phase3.JavaExtends2(); + assert myContext instanceof "ortus.boxlang.runtime.context.IBoxContext" + + println( myContext.getTemplatesYo() ) + """, context ); + + } + + @Test + public void testInlineJavaExtendsFieldPublic() { + instance.executeSource( + """ + myBIF = new src.test.java.TestCases.phase3.JavaExtends3(); + assert myBIF instanceof "ortus.boxlang.runtime.bifs.BIF" + println( myBIF.__isMemberExecution ) + println( myBIF.runtime ) + myBIF.printStuff() + """, context ); + + } + + @Test + public void testImplicitAccessor() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.ImplicitAccessor(); + clazz.name="brad"; + clazz.age=44; + name = clazz.name; + age = clazz.age; + methodsCalled = clazz.getMethodsCalled(); + """, context ); + assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 44 ); + assertThat( variables.get( Key.of( "methodsCalled" ) ) ).isEqualTo( "setNamesetAgegetNamegetAge" ); + } + + @Test + public void testImplicitGeneratedAccessor() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.ImplicitGeneratedAccessor(); + clazz.name="brad"; + clazz.age=44; + name = clazz.name; + age = clazz.age; + // prove they're going in the variable scope, not this scope + keyExistsName = structKeyExists( clazz, "name") + keyExistsAge = structKeyExists( clazz, "age") + """, context ); + assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 44 ); + assertThat( variables.get( Key.of( "keyExistsName" ) ) ).isEqualTo( false ); + assertThat( variables.get( Key.of( "keyExistsAge" ) ) ).isEqualTo( false ); + } + + @Test + public void testStaticInstance() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.StaticTestCF(); + result1 = clazz.foo; + result2 = clazz.myStaticFunc(); + result3 = clazz.myInstanceFunc(); + result4 = clazz.scoped; + result5 = clazz.unscoped; + result6 = clazz.again; + """, context, BoxSourceType.BOXSCRIPT ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 42 ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static42" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "instancestatic42" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); + } + + @Test + public void testStaticStatic() { + instance.executeSource( """ + result1 = src.test.java.TestCases.phase3.StaticTest::foo; + result2 = src.test.java.TestCases.phase3.StaticTest::myStaticFunc(); + result4 = src.test.java.TestCases.phase3.StaticTest::scoped; + result5 = src.test.java.TestCases.phase3.StaticTest::unscoped; + result6 = src.test.java.TestCases.phase3.StaticTest::again; + myStaticUDF = src.test.java.TestCases.phase3.StaticTest::sayHello; + result7 = myStaticUDF(); + result8 = src.test.java.TestCases.phase3.StaticTest::123; + """, context, BoxSourceType.BOXSCRIPT ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); + assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "Hello" ); + assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( 456 ); + } + + @Test + public void testStaticInstanceCF() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.StaticTestCF(); + result1 = clazz.foo; + result2 = clazz.myStaticFunc(); + result3 = clazz.myInstanceFunc(); + result4 = clazz.scoped; + result5 = clazz.unscoped; + result6 = clazz.again; + """, context, BoxSourceType.CFSCRIPT ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 42 ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static42" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "instancestatic42" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); + } + + @Test + public void testStaticStaticCF() { + instance.executeSource( + """ + result1 = src.test.java.TestCases.phase3.StaticTest::foo; + result2 = src.test.java.TestCases.phase3.StaticTest::myStaticFunc(); + result4 = src.test.java.TestCases.phase3.StaticTest::scoped; + result5 = src.test.java.TestCases.phase3.StaticTest::unscoped; + result6 = src.test.java.TestCases.phase3.StaticTest::again; + result7 = src.test.java.TestCases.phase3.StaticTest::123; + """, context, BoxSourceType.CFSCRIPT ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); + assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( 456 ); + } + + @Test + public void testStaticImport() { + instance.executeSource( + """ + import src.test.java.TestCases.phase3.StaticTest; + + result1 = StaticTest::foo; + result2 = StaticTest::myStaticFunc(); + result4 = StaticTest::scoped; + result5 = StaticTest::unscoped; + result6 = StaticTest::again; + """, context, BoxSourceType.BOXSCRIPT ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); + } + + @Test + public void testStaticImportDot() { + instance.executeSource( + """ + import src.test.java.TestCases.phase3.StaticTest; + + result1 = StaticTest.foo; + result2 = StaticTest.myStaticFunc(); + result4 = StaticTest.scoped; + result5 = StaticTest.unscoped; + result6 = StaticTest.again; + // instance + myInstance = new StaticTest(); + result7 = myInstance.foo; + result8 = StaticTest.foo; + result9 = myInstance.myInstanceFunc2() + """, context, BoxSourceType.BOXSCRIPT ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); + assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( 42 ); + assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( 42 ); + assertThat( variables.get( Key.of( "result9" ) ) ).isInstanceOf( Array.class ); + Array result9 = variables.getAsArray( Key.of( "result9" ) ); + assertThat( result9.size() ).isEqualTo( 3 ); + assertThat( result9.get( 0 ) ).isEqualTo( "brad" ); + assertThat( result9.get( 1 ) ).isEqualTo( "wood" ); + assertThat( result9.get( 2 ) ).isEqualTo( 42 ); + } + + @Test + public void testDotExtends() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.DotExtends(); + result = clazz.childUDF() + """, context ); + assertThat( variables.get( result ) ).isEqualTo( "childUDFparent" ); + } + + @Test + public void testRelativeInstantiation() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.RelativeInstantiation(); + result = clazz.findSibling() + """, context ); + assertThat( variables.get( result ) ).isEqualTo( "bar" ); + } + + @Test + public void testAbstractClass() { + Throwable t = assertThrows( AbstractClassException.class, () -> instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.AbstractClass(); + """, context ) ); + assertThat( t.getMessage() ).contains( "Cannot instantiate an abstract class" ); + + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.ConcreteClass(); + result1 = clazz.normal() + result2 = clazz.abstractMethod() + """, context ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "normal" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "abstractMethod" ); + } + + @Test + public void testAbstractClassCF() { + Throwable t = assertThrows( AbstractClassException.class, () -> instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.AbstractClassCF(); + """, context ) ); + assertThat( t.getMessage() ).contains( "Cannot instantiate an abstract class" ); + + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.ConcreteClassCF(); + result1 = clazz.normal() + result2 = clazz.abstractMethod() + """, context ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "normal" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "abstractMethod" ); + } + + @Test + public void testCFGetterType() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.GetterTest(); + result = clazz.getMyDate() + """, context ); + assertThat( variables.get( result ) ).isEqualTo( "" ); + } + + @Test + public void testGetterOverrideInParent() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.GeneratedGetterChild(); + result = clazz.getFoo() + """, context ); + assertThat( variables.get( result ) ).isEqualTo( "overriden" ); + } + + @Test + public void testFinalClass() { + instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.FinalClass(); + """, context ); + Throwable t = assertThrows( BoxRuntimeException.class, + () -> instance.executeSource( + """ + clazz = new src.test.java.TestCases.phase3.IllegalFinalExtends(); + """, context ) ); + assertThat( t.getMessage() ).contains( "Cannot extend final class" ); + } + +} diff --git a/src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc b/src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc new file mode 100644 index 000000000..a7606f5f0 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc @@ -0,0 +1,7 @@ + + + + + diff --git a/src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc b/src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc new file mode 100644 index 000000000..813fa9c83 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc @@ -0,0 +1,4 @@ + + component { + } + \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ConcreteClass.bx b/src/test/java/TestCases/asm/phase3/ConcreteClass.bx new file mode 100644 index 000000000..da881c5b8 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ConcreteClass.bx @@ -0,0 +1,6 @@ +class extends="AbstractClass" { + + function abstractMethod() { + return "abstractMethod"; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc b/src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc new file mode 100644 index 000000000..06b946605 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc @@ -0,0 +1,6 @@ +component extends="AbstractClassCF" { + + function abstractMethod() { + return "abstractMethod"; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Dog.cfc b/src/test/java/TestCases/asm/phase3/Dog.cfc new file mode 100644 index 000000000..efdc62ab0 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Dog.cfc @@ -0,0 +1,20 @@ +component extends="Animal" { + results.append('Dog pseudo ' & getFileFromPath( getCurrentTemplatePath())) + variables.inDog = true; + // Our variables scope contains the variables from the parent component + results.append( "dog sees variables.inAnimal as: " & variables.inAnimal ) + + function init() { + super.init(); + results.append('Dog init ' & getFileFromPath( getCurrentTemplatePath())) + } + + function speak() { + return "Woof!"; + } + + function getScientificName() { + return "Canis lupus " & super.getScientificName(); + } + + } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/DotExtends.cfc b/src/test/java/TestCases/asm/phase3/DotExtends.cfc new file mode 100644 index 000000000..28111cff1 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/DotExtends.cfc @@ -0,0 +1,5 @@ +component extends="../../TestCases/phase3/DotExtendsParent" { + function childUDF() { + return "childUDF" & super.parentUDF(); + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc b/src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc new file mode 100644 index 000000000..55b42456b --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc @@ -0,0 +1,5 @@ +component { + function parentUDF() { + return "parent"; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ExceptionTest.java b/src/test/java/TestCases/asm/phase3/ExceptionTest.java new file mode 100644 index 000000000..c53f033e2 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ExceptionTest.java @@ -0,0 +1,77 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package TestCases.asm.phase3; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; + +public class ExceptionTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + static Key foo = new Key( "foo" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + instance.useASMBoxPiler(); + } + + @AfterEach + public void teardownEach() { + instance.useJavaBoxpiler(); + } + + @Test + public void testBoxMeta() { + + instance.executeStatement( + """ + include "src/test/java/TestCases/phase3/ExceptionThrower.cfs"; + """, context ); + + assertThat( "" ).isEqualTo( "" ); + + } + +} diff --git a/src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs b/src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs new file mode 100644 index 000000000..9bb637d16 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs @@ -0,0 +1,6 @@ +try { + 1/0 +} catch( any e ) { + e.printStackTrace() + println( e.tagContext ) +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/FinalClass.bx b/src/test/java/TestCases/asm/phase3/FinalClass.bx new file mode 100644 index 000000000..dc3b92ed8 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/FinalClass.bx @@ -0,0 +1,5 @@ +final class { + + final this.CONSTANT = "constant"; + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/FindMe.bx b/src/test/java/TestCases/asm/phase3/FindMe.bx new file mode 100644 index 000000000..9394e50f5 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/FindMe.bx @@ -0,0 +1,5 @@ +class { + function foo() { + return "bar"; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/FunctionMeta.cfc b/src/test/java/TestCases/asm/phase3/FunctionMeta.cfc new file mode 100644 index 000000000..1376b2f56 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/FunctionMeta.cfc @@ -0,0 +1,16 @@ +component { + + /** + * The bit that can be used to set all tasks created by this scheduler to always run on one server + */ + property name="serverFixation" type="boolean"; + + /** + * This is my function hint + * @brad wood + * @param1.luis majano + */ + function foo( param1 ) { + + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx b/src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx new file mode 100644 index 000000000..015a3cdaa --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx @@ -0,0 +1,2 @@ +class accessors=true extends="GeneratedGetterParent" { +} diff --git a/src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx b/src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx new file mode 100644 index 000000000..80852e652 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx @@ -0,0 +1,9 @@ +class accessors=true { + + property name="foo" type=string default="default value"; + + // overridden getter + string function getFoo() { + return "overriden"; + } +} diff --git a/src/test/java/TestCases/asm/phase3/GetterTest.cfc b/src/test/java/TestCases/asm/phase3/GetterTest.cfc new file mode 100644 index 000000000..b85cf9750 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/GetterTest.cfc @@ -0,0 +1,10 @@ +component accessors="true" { + + property name="myDate" type="date"; + + function init() { + variables.myDate = ""; + } + + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IBicycle.bx b/src/test/java/TestCases/asm/phase3/IBicycle.bx new file mode 100644 index 000000000..a67cdd373 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IBicycle.bx @@ -0,0 +1,11 @@ +interface { + + public String function pedal( required numeric speed=3 ); + + function pedalLax( speed ); + + default function hasInnerTube() { + return true; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IChildInterface.bx b/src/test/java/TestCases/asm/phase3/IChildInterface.bx new file mode 100644 index 000000000..c746358a5 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IChildInterface.bx @@ -0,0 +1,9 @@ +interface extends="IParentInterface" { + default function childDefaultMethod() { + return "childDefaultMethod"; + } + default function parentOverrideMe() { + return "parentOverrideMeChild"; + } + function childMethod(); +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IMotorcycle.bx b/src/test/java/TestCases/asm/phase3/IMotorcycle.bx new file mode 100644 index 000000000..a3aa0c08d --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IMotorcycle.bx @@ -0,0 +1,9 @@ +interface { + + function shift( required numeric gear ); + + default function needsFuel() { + return false; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx b/src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx new file mode 100644 index 000000000..d73790082 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx @@ -0,0 +1,12 @@ +interface extends="IParentInterface,IUncleInterface" { + default function childDefaultMethod() { + return "childDefaultMethod"; + } + default function parentOverrideMe() { + return "parentOverrideMeChild"; + } + default function uncleOverrideMe() { + return "uncleOverrideMeChild"; + } + function childMethod(); +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IParentInterface.bx b/src/test/java/TestCases/asm/phase3/IParentInterface.bx new file mode 100644 index 000000000..4f3a973b8 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IParentInterface.bx @@ -0,0 +1,12 @@ +interface { + default function parentDefaultMethod() { + return "parentDefaultMethod"; + } + default function parentOverrideMe() { + return "parentOverrideMeParent"; + } + default function parentOverrideMe2() { + return "parentOverrideMeParent2"; + } + function parentMethod(); +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IUncleInterface.bx b/src/test/java/TestCases/asm/phase3/IUncleInterface.bx new file mode 100644 index 000000000..c05e1f421 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IUncleInterface.bx @@ -0,0 +1,12 @@ +interface { + default function uncleDefaultMethod() { + return "uncleDefaultMethod"; + } + default function uncleOverrideMe() { + return "uncleOverrideMeUncle"; + } + default function uncleOverrideMe2() { + return "uncleOverrideMeUncle2"; + } + function uncleMethod(); +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx b/src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx new file mode 100644 index 000000000..1442a25b7 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx @@ -0,0 +1,3 @@ +class extends=FinalClass { + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx b/src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx new file mode 100644 index 000000000..fc2af56a6 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx @@ -0,0 +1,29 @@ +class invokeImplicitAccessor=true { + property string name; + property integer age; + + methodsCalled = ""; + function getName() { + methodsCalled &= "getName"; + return variables.name; + } + + function setName(string name) { + methodsCalled &= "setName"; + variables.name = name; + } + + function getAge() { + methodsCalled &= "getAge"; + return variables.age; + } + + function setAge(integer age) { + methodsCalled &= "setAge"; + variables.age = age; + } + + function getMethodsCalled() { + return methodsCalled; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc b/src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc new file mode 100644 index 000000000..4186eec1b --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc @@ -0,0 +1,6 @@ +component accessors=true { + property name="name"; + property name="age"; + property name="favoriteColor"; + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx b/src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx new file mode 100644 index 000000000..9e6d5e274 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx @@ -0,0 +1,5 @@ +class invokeImplicitAccessor=true accessors=true { + property string name; + property integer age; + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InitMethodTest.bx b/src/test/java/TestCases/asm/phase3/InitMethodTest.bx new file mode 100644 index 000000000..60a656d12 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/InitMethodTest.bx @@ -0,0 +1,9 @@ +class initMethod=birth accessors=true { + property inittedProperly default=false; + function init() { + throw "you'd better not call me!"; + } + function birth() { + inittedProperly=true + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx b/src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx new file mode 100644 index 000000000..1abcc38b4 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx @@ -0,0 +1,11 @@ +class implements="IChildInterface" { + + public Any function childMethod() { + return "childMethod"; + } + + public Any function parentMethod() { + return "parentMethod"; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx b/src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx new file mode 100644 index 000000000..74c0dedf9 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx @@ -0,0 +1,23 @@ +class implements="IMultiChildInterface" { + + public Any function childMethod() { + return "childMethod"; + } + + public Any function parentMethod() { + return "parentMethod"; + } + + public Any function uncleMethod() { + return "uncleMethod"; + } + + default function parentOverrideMe2() { + return "parentOverrideMe2Class"; + } + + default function uncleOverrideMe2() { + return "uncleOverrideMe2Class"; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceStatic.bx b/src/test/java/TestCases/asm/phase3/InterfaceStatic.bx new file mode 100644 index 000000000..3c9e628e0 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/InterfaceStatic.bx @@ -0,0 +1,16 @@ +interface { + + static { + static.myVar = "brad"; + yourVar = "luis"; + } + + static function foo() { + return static.myVar; + } + + static function callStatic() { + return foo(); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceTest.java b/src/test/java/TestCases/asm/phase3/InterfaceTest.java new file mode 100644 index 000000000..cc301d42a --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/InterfaceTest.java @@ -0,0 +1,284 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package TestCases.asm.phase3; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.runnables.BoxInterface; +import ortus.boxlang.runtime.runnables.RunnableLoader; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; + +public class InterfaceTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + static Key foo = new Key( "foo" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + instance.useASMBoxPiler(); + } + + @AfterEach + public void teardownEach() { + instance.useJavaBoxpiler(); + } + + @DisplayName( "basic CF interface" ) + @Test + public void testBasicCFInterface() { + + BoxInterface inter = ( BoxInterface ) DynamicObject.of( RunnableLoader.getInstance().loadClass( + """ + /** + * This is my interface description + * + * @brad wood + * @luis + */ + interface singleton gavin="pickin" inject foo="bar" { + + function init(); + + function foo(); + + private function bar(); + + default function myDefaultMethod() { + return this.foo(); + } + + } + + """, context, BoxSourceType.CFSCRIPT ), context ).unWrapBoxLangClass(); + + assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); + assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); + + } + + @DisplayName( "basic interface file CF" ) + @Test + public void testBasicInterfaceFileCF() { + + instance.executeStatement( + """ + result = createObject( "component", "src.test.java.TestCases.phase3.MyInterfaceCF" ); + """, context ); + assertThat( variables.get( result ) ).isInstanceOf( BoxInterface.class ); + BoxInterface inter = variables.getAsBoxInterface( result ); + assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); + assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); + + } + + @DisplayName( "basic interface file BL" ) + @Test + public void testBasicInterfaceFileBL() { + + instance.executeStatement( + """ + result = createObject( "component", "src.test.java.TestCases.phase3.MyInterfaceBL" ); + """, context ); + assertThat( variables.get( result ) ).isInstanceOf( BoxInterface.class ); + BoxInterface inter = variables.getAsBoxInterface( result ); + assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); + assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); + + } + + @DisplayName( "basic interface file CF Tag" ) + @Test + public void testBasicClassFileCFTag() { + + instance.executeStatement( + """ + result = createObject( "component", "src.test.java.TestCases.phase3.MyInterfaceCFTag" ); + """, context ); + assertThat( variables.get( result ) ).isInstanceOf( BoxInterface.class ); + BoxInterface inter = variables.getAsBoxInterface( result ); + assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); + assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); + + } + + @DisplayName( "moped example" ) + @Test + public void testMopedExample() { + + instance.executeStatement( + """ + boxClass = new src.test.java.TestCases.phase3.Moped(); + + // implemented method from IBicycle + result1 = boxClass.pedal( 3 ) + // impelemented method from IMotorcycle + result2 = boxClass.shift( 4 ) + // Default method from IBicycle + result3 = boxClass.hasInnerTube() + // Default method from IMotorcycle but overridden by moped + result4 = boxClass.needsFuel() + """, context ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "Pedal speed 3" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "Shift to gear 4" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( true ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( true ); + } + + @DisplayName( "onMissingMethod example" ) + @Test + public void testOnMissingMethod() { + + instance.executeStatement( + """ + boxClass = new src.test.java.TestCases.phase3.WheeledThing(); + + // implemented method from IBicycle + result1 = boxClass.pedal( 3 ) + // impelemented method from IMotorcycle + result2 = boxClass.shift( 4 ) + // Default method from IBicycle + result3 = boxClass.hasInnerTube() + // Default method from IMotorcycle but overridden by moped + result4 = boxClass.needsFuel() + """, context ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "Pedal speed 3" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "Shift to gear 4" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( true ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( false ); + + } + + @DisplayName( "interface inheritence example" ) + @Test + public void testInterfaceInheritence() { + + instance.executeStatement( + """ + boxClass = new src.test.java.TestCases.phase3.InterfaceInheritenceTest(); + result1 = boxClass.childMethod() + result2 = boxClass.parentMethod() + result3 = boxClass.childDefaultMethod() + result4 = boxClass.parentDefaultMethod() + result5 = boxClass.parentOverrideMe() + assert boxClass instanceof "InterfaceInheritenceTest"; + assert boxClass instanceof "IChildInterface"; + assert boxClass instanceof "IParentInterface"; + """, context ); + + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "childMethod" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "parentMethod" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "childDefaultMethod" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "parentDefaultMethod" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "parentOverrideMeChild" ); + } + + @DisplayName( "interface multi-inheritence example" ) + @Test + public void testInterfaceMultiInheritence() { + + instance.executeStatement( + """ + boxClass = new src.test.java.TestCases.phase3.InterfaceMultiInheritenceTest(); + result1 = boxClass.childMethod() + result2 = boxClass.parentMethod() + result3 = boxClass.uncleMethod() + result4 = boxClass.childDefaultMethod() + result5 = boxClass.parentDefaultMethod() + result6 = boxClass.uncleDefaultMethod() + result7 = boxClass.parentOverrideMe() + result8 = boxClass.uncleOverrideMe() + result9 = boxClass.parentOverrideMe2() + result10 = boxClass.uncleOverrideMe2() + assert boxClass instanceof "InterfaceMultiInheritenceTest"; + assert boxClass instanceof "IMultiChildInterface"; + assert boxClass instanceof "IParentInterface"; + assert boxClass instanceof "IUncleInterface"; + // println( createOBject("src.test.java.TestCases.phase3.IMultiChildInterface" ).$bx.invokeTargetMethod( getBoxContext(), "getMetaData", [].toArray() ) ) + """, + context ); + + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "childMethod" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "parentMethod" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "uncleMethod" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "childDefaultMethod" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "parentDefaultMethod" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "uncleDefaultMethod" ); + assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "parentOverrideMeChild" ); + assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( "uncleOverrideMeChild" ); + assertThat( variables.get( Key.of( "result9" ) ) ).isEqualTo( "parentOverrideMe2Class" ); + assertThat( variables.get( Key.of( "result10" ) ) ).isEqualTo( "uncleOverrideMe2Class" ); + + } + + @DisplayName( "interface inheritence static" ) + @Test + public void testInterfaceStatic() { + + instance.executeStatement( + """ + import src.test.java.TestCases.phase3.InterfaceStatic as ist; + result1 = ist.foo() + result2 = ist.myVar; + result3 = ist.yourVar; + result4 = ist.callStatic(); + result5 = ist::yourVar; + result6 = ist::callStatic(); + result7 = src.test.java.TestCases.phase3.InterfaceStatic::yourVar; + result8 = src.test.java.TestCases.phase3.InterfaceStatic::callStatic(); + """, context ); + assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "luis" ); + assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "luis" ); + assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "brad" ); + assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "luis" ); + assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( "brad" ); + + } + +} diff --git a/src/test/java/TestCases/asm/phase3/JavaExtends.bx b/src/test/java/TestCases/asm/phase3/JavaExtends.bx new file mode 100644 index 000000000..4baaea6cc --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/JavaExtends.bx @@ -0,0 +1,14 @@ +class extends="java:java.util.TimerTask" { + + @overrideJava + void function run() { + println("Hello from a custom TimerTask!" ); + println( super.scheduledExecutionTime() ); + } + + @overrideJava + public long function scheduledExecutionTime() { + throw "no, not me!"; + } + +} diff --git a/src/test/java/TestCases/asm/phase3/JavaExtends2.bx b/src/test/java/TestCases/asm/phase3/JavaExtends2.bx new file mode 100644 index 000000000..673799e63 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/JavaExtends2.bx @@ -0,0 +1,7 @@ +class extends="java:ortus.boxlang.runtime.context.BaseBoxContext" { + + function getTemplatesYo() { + return super.templates; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/JavaExtends3.bx b/src/test/java/TestCases/asm/phase3/JavaExtends3.bx new file mode 100644 index 000000000..2aef9391f --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/JavaExtends3.bx @@ -0,0 +1,14 @@ +class extends="java:ortus.boxlang.runtime.bifs.BIF" { + + @overrideJava + Object function _invoke(ortus.boxlang.runtime.context.IBoxContext c ,ortus.boxlang.runtime.scopes.ArgumentsScope args) { + + } + + function printStuff() { + println( this.__isMemberExecution ) + println( this.runtime ) + println( super.__isMemberExecution ) + println( super.runtime ) + } +} diff --git a/src/test/java/TestCases/asm/phase3/JavaImplements.bx b/src/test/java/TestCases/asm/phase3/JavaImplements.bx new file mode 100644 index 000000000..ba69db7f7 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/JavaImplements.bx @@ -0,0 +1,7 @@ +class implements="java:java.lang.Runnable" { + + function run() { + println("Hello from a thread!"); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Moped.bx b/src/test/java/TestCases/asm/phase3/Moped.bx new file mode 100644 index 000000000..7816cd1f4 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Moped.bx @@ -0,0 +1,20 @@ +class implements="IBicycle,IMotorcycle" { + + String function pedal( required numeric speed=3 ) { + return "Pedal speed #speed#" + } + + // This overachieving mehod has more details than the interfaces requires + Boolean function pedalLax( required numeric speed=3 ) { + } + + public Any function shift(required numeric gear) { + return "Shift to gear #gear#" + } + + @Override + default function needsFuel() { + return true; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyClass.bx b/src/test/java/TestCases/asm/phase3/MyClass.bx new file mode 100644 index 000000000..35a26f027 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/MyClass.bx @@ -0,0 +1,50 @@ +// get foo +import foo; +// get system +import java.lang.System; + +/** + * This is my class description + * continued on this line + * + * and this one + * as well. + * + * @brad wood + * @luis + */ +@foo "bar" +class singleton gavin="pickin" inject { + + property name; + + variables.setup=true; // ending comment + System.out.println( "word" ); + request.foo="bar"; + isInitted = false; + println( "current template is " & getCurrentTemplatePath() ); + printLn( foo() ) + + // my init func + function init() { + isInitted = true; + } + + function foo() { + /* return this value */ + return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; + } + + private function bar() { + return "whee"; + } + + function getThis() { + return this; + } + + function runThisFoo() { + return this.foo(); + } + + } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyClassCF.cfc b/src/test/java/TestCases/asm/phase3/MyClassCF.cfc new file mode 100644 index 000000000..7aa68b7bb --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/MyClassCF.cfc @@ -0,0 +1,36 @@ +/** + * This is my class description + * + * @brad wood + * @luis + */ +component singleton gavin="pickin" inject foo="bar" { + + variables.setup=true; + createObject('java','java.lang.System').out.println( "word" ); + request.foo="bar"; + isInitted = false; + println( "current template is " & getCurrentTemplatePath() ); + printLn( foo() ) + + function init() { + isInitted = true; + } + + function foo() { + return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; + } + + private function bar() { + return "whee"; + } + + function getThis() { + return this; + } + + function runThisFoo() { + return this.foo(); + } + + } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx b/src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx new file mode 100644 index 000000000..411c83543 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx @@ -0,0 +1,19 @@ +/** +* This is my interface description +* +* @brad wood +* @luis +*/ +interface singleton gavin="pickin" inject foo="bar" { + + function init(); + + function foo(); + + private function bar(); + + default function myDefaultMethod() { + return this.foo(); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc b/src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc new file mode 100644 index 000000000..411c83543 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc @@ -0,0 +1,19 @@ +/** +* This is my interface description +* +* @brad wood +* @luis +*/ +interface singleton gavin="pickin" inject foo="bar" { + + function init(); + + function foo(); + + private function bar(); + + default function myDefaultMethod() { + return this.foo(); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc b/src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc new file mode 100644 index 000000000..255d4cda0 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc b/src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc new file mode 100644 index 000000000..73e5af916 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc @@ -0,0 +1,5 @@ +component { + public any function onMissingMethod( string missingMethodName, any missingMethodArguments ){ + return missingMethodName & missingMethodArguments[2]; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Parent.cfc b/src/test/java/TestCases/asm/phase3/Parent.cfc new file mode 100644 index 000000000..0e435dab7 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Parent.cfc @@ -0,0 +1,12 @@ +component { + + function init() { + setupFrameworkDefaults(); + return this; + } + + private void function setupFrameworkDefaults() { + request.calls.append( "running parent setupFrameworkDefaults()" ); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/PropertyTest.bx b/src/test/java/TestCases/asm/phase3/PropertyTest.bx new file mode 100644 index 000000000..e27a9870e --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/PropertyTest.bx @@ -0,0 +1,25 @@ +class accessors=true { + @preAnno + property name="myProperty" default="myDefaultValue" type=string inject; + + /** + * This is my property + * @brad wood + * @luis + */ + @preanno "myValue" "anothervalue" + property string anotherprop; + + @ID + property theName; + + property string name; + + property shortcutWithDefault default="myDefaultValue"; + + property String typedShortcutWithDefault default="myDefaultValue2"; + + function init() { + getMyProperty(); + } +} diff --git a/src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc b/src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc new file mode 100644 index 000000000..420a38801 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc @@ -0,0 +1,18 @@ +component accessors=true { + property name="myProperty" default="myDefaultValue" type=string inject preAnno; + + /** + * This is my property + * @brad wood + * @luis + */ + property string anotherprop preanno=["myValue", "anothervalue"]; + + property shortcutWithDefault default="myDefaultValue"; + + property String typedShortcutWithDefault default="myDefaultValue2"; + + function init() { + getMyProperty(); + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc b/src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc new file mode 100644 index 000000000..2c844bad6 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc @@ -0,0 +1,3 @@ +component output=false { + echo( "PseudoConstructorOutput" ); +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc b/src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc new file mode 100644 index 000000000..fe290b038 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc @@ -0,0 +1,3 @@ +component output=true { + echo( "PseudoConstructorOutput" ); +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx b/src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx new file mode 100644 index 000000000..4e7e792d5 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx @@ -0,0 +1,5 @@ +class { + function findSibling() { + return new FindMe().foo(); + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/SeniorVespa.bx b/src/test/java/TestCases/asm/phase3/SeniorVespa.bx new file mode 100644 index 000000000..1e1c8036b --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/SeniorVespa.bx @@ -0,0 +1,3 @@ +class extends="Moped" { + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Stack.cfc b/src/test/java/TestCases/asm/phase3/Stack.cfc new file mode 100644 index 000000000..f3f5b33cd --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/Stack.cfc @@ -0,0 +1,10 @@ +component { + function init() { + stack = []; + return this; + } + + function empty() { + return stack.len() == 0; + } +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/StaticTest.bx b/src/test/java/TestCases/asm/phase3/StaticTest.bx new file mode 100644 index 000000000..51240db75 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/StaticTest.bx @@ -0,0 +1,33 @@ + class { + static { + static.scoped = "brad"; + unscoped = "wood" + static foo = 9000; + '123' = 456; + } + + static.foo = 42; + + static { + static.again = "luis" + } + + static function myStaticFunc() { + return "static" & static.foo; + } + + function myInstanceFunc() { + return "instance" & myStaticFunc(); + } + + array function myInstanceFunc2() { + return [static.scoped, + static.unscoped, + static.foo]; + } + + static function sayHello() { + return "Hello"; + } + + } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/StaticTestCF.cfc b/src/test/java/TestCases/asm/phase3/StaticTestCF.cfc new file mode 100644 index 000000000..ebfad9dc3 --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/StaticTestCF.cfc @@ -0,0 +1,22 @@ +component { + static { + static.scoped = "brad"; + unscoped = "wood" + static foo = 9000; + } + + static.foo = 42; + + static { + static.again = "luis" + } + + static function myStaticFunc() { + return "static" & static.foo; + } + + function myInstanceFunc() { + return "instance" & myStaticFunc(); + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/WheeledThing.bx b/src/test/java/TestCases/asm/phase3/WheeledThing.bx new file mode 100644 index 000000000..bbbecf7cc --- /dev/null +++ b/src/test/java/TestCases/asm/phase3/WheeledThing.bx @@ -0,0 +1,13 @@ +class implements="IBicycle,IMotorcycle" { + + function onMissingMethod( name, args ) { + switch( arguments.name ) { + case "pedal": + return "Pedal speed #args[1]#" + break; + case "shift": + return "Shift to gear #args[1]#" + break; + } + } +} \ No newline at end of file From 56163b1b867916d482b553f6ddb31820c0aeb4df Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 7 Sep 2024 21:17:52 -0500 Subject: [PATCH 02/71] Make BoxAssertTransformer respect ReturnValueContext --- .../transformer/statement/BoxAssertTransformer.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java index a249d4e63..31b306b54 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java @@ -20,6 +20,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; @@ -43,12 +44,17 @@ public List transform( BoxNode node, TransformerContext contex BoxAssert boxAssert = ( BoxAssert ) node; List nodes = new ArrayList<>(); nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); - nodes.addAll( transpiler.transform( boxAssert.getExpression(), TransformerContext.RIGHT, ReturnValueContext.EMPTY ) ); + nodes.addAll( transpiler.transform( boxAssert.getExpression(), TransformerContext.RIGHT, ReturnValueContext.VALUE ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( Assert.class ), "invoke", Type.getMethodDescriptor( Type.getType( Boolean.class ), Type.getType( IBoxContext.class ), Type.getType( Object.class ) ), false ) ); + + if ( returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + return nodes; } } From 2d0f07d01c28da61f30b9fab7454e789ae86e8ad Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 7 Sep 2024 21:26:52 -0500 Subject: [PATCH 03/71] Add class documentation --- .../compiler/asmboxpiler/AsmTranspiler.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index ede5db865..9c267b1b3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -142,7 +142,6 @@ import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.IType; import ortus.boxlang.runtime.types.Property; -import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; import ortus.boxlang.runtime.types.meta.BoxMeta; @@ -701,8 +700,9 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { "sourceType", Type.getDescriptor( BoxSourceType.class ) ); - List annotations = transformAnnotations( boxClass.getAnnotations() ); - List> properties = transformProperties( type, boxClass.getProperties(), sourceType ); + List annotations = transformAnnotations( boxClass.getAnnotations() ); + List documenation = transformDocumentation( boxClass.getDocumentation() ); + List> properties = transformProperties( type, boxClass.getProperties(), sourceType ); methodVisitor.visitLdcInsn( getKeys().size() ); methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); @@ -764,10 +764,7 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { "annotations", Type.getDescriptor( IStruct.class ) ); - methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, - Type.getInternalName( Struct.class ), - "EMPTY", - Type.getDescriptor( IStruct.class ) ); + documenation.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "documentation", @@ -863,12 +860,7 @@ private List> transformProperties( Type declaringType, Li javaExpr.addAll( jNameKey ); javaExpr.add( new LdcInsnNode( type ) ); javaExpr.addAll( init ); - // TODO replace with annotation once ready - javaExpr.add( new FieldInsnNode( Opcodes.GETSTATIC, - Type.getInternalName( Struct.class ), - "EMPTY", - Type.getDescriptor( IStruct.class ) ) ); - // javaExpr.addAll( annotationStruct ); + javaExpr.addAll( transformAnnotations( annotations ) ); javaExpr.addAll( documentationStruct ); javaExpr.add( new FieldInsnNode( Opcodes.GETSTATIC, From f944f5aab55fefbe8e476cc1a384daa6c588ec1c Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 7 Sep 2024 21:32:39 -0500 Subject: [PATCH 04/71] Update property annotation transformation --- .../ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 9c267b1b3..4eac47967 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -816,7 +816,7 @@ private List> transformProperties( Type declaringType, Li */ List finalAnnotations = normlizePropertyAnnotations( prop ); // Start wiith all inline annotatinos - var annotations = prop.getPostAnnotations(); + BoxAnnotation nameAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "name" ) ) .findFirst() .orElseThrow( () -> new ExpressionException( "Property [" + prop.getSourceText() + "] missing name annotation", prop ) ); @@ -860,7 +860,7 @@ private List> transformProperties( Type declaringType, Li javaExpr.addAll( jNameKey ); javaExpr.add( new LdcInsnNode( type ) ); javaExpr.addAll( init ); - javaExpr.addAll( transformAnnotations( annotations ) ); + javaExpr.addAll( transformAnnotations( finalAnnotations ) ); javaExpr.addAll( documentationStruct ); javaExpr.add( new FieldInsnNode( Opcodes.GETSTATIC, From f4dca1081cdd54965a39cce70ffd89756d3e7f37 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 7 Sep 2024 22:01:58 -0500 Subject: [PATCH 05/71] Update BoxNewTransformer to handle implicit constructor with named arguments --- .../compiler/asmboxpiler/AsmHelper.java | 62 +++++++++++++++++++ .../expression/BoxNewTransformer.java | 11 +--- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 981f35db0..b6565f431 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; @@ -19,6 +20,7 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; @@ -26,17 +28,77 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxStatement; +import ortus.boxlang.compiler.ast.expression.BoxArgument; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.runnables.BoxClassSupport; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.util.ResolvedFilePath; public class AsmHelper { + public static List callDynamicObjectInvokeConstructor( Transpiler transpiler, List args, TransformerContext context ) { + List nodes = new ArrayList(); + + // handle positional args + if ( args.size() == 0 || args.get( 0 ).getName() == null ) { + nodes.addAll( + AsmHelper.array( Type.getType( Object.class ), args, + ( argument, i ) -> transpiler.transform( args.get( i ), context, ReturnValueContext.VALUE ) ) + ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( DynamicObject.class ), + "invokeConstructor", + Type.getMethodDescriptor( Type.getType( DynamicObject.class ), + Type.getType( IBoxContext.class ), + Type.getType( Object[].class ) ), + false ) ); + + return nodes; + } + + List> keyValues = args.stream() + .map( arg -> { + List> kv = List.of( + transpiler.createKey( arg.getName() ), + transpiler.transform( arg, context, ReturnValueContext.VALUE ) + ); + + return kv; + } ) + .flatMap( x -> x.stream() ) + .collect( Collectors.toList() ); + + nodes.addAll( AsmHelper.array( Type.getType( Object.class ), keyValues ) ); + + nodes.add( + new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( Struct.class ), + "of", + Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), + false + ) + ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( DynamicObject.class ), + "invokeConstructor", + Type.getMethodDescriptor( Type.getType( DynamicObject.class ), + Type.getType( IBoxContext.class ), + Type.getType( Map.class ) ), + false ) ); + + return nodes; + + } + public static void init( ClassVisitor classVisitor, boolean singleton, Type type, Type superClass, Consumer onConstruction, Type... interfaces ) { classVisitor.visit( diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java index 48df92baa..368ba34e1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java @@ -83,17 +83,8 @@ public List transform( BoxNode node, TransformerContext contex false ) ); nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); - nodes.addAll( - AsmHelper.array( Type.getType( Object.class ), boxNew.getArguments(), - ( argument, i ) -> transpiler.transform( boxNew.getArguments().get( i ), context ) ) ); - nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, - Type.getInternalName( DynamicObject.class ), - "invokeConstructor", - Type.getMethodDescriptor( Type.getType( DynamicObject.class ), - Type.getType( IBoxContext.class ), - Type.getType( Object[].class ) ), - false ) ); + nodes.addAll( AsmHelper.callDynamicObjectInvokeConstructor( transpiler, boxNew.getArguments(), context ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, Type.getInternalName( DynamicObject.class ), From bc6bdcf282288d44284d84117cb9407d71fb7cb6 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 7 Sep 2024 22:56:27 -0500 Subject: [PATCH 06/71] Add getAllAbstractMethods and such --- .../compiler/asmboxpiler/AsmTranspiler.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 4eac47967..61ba21754 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.stream.Stream; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; @@ -138,10 +139,14 @@ import ortus.boxlang.runtime.scopes.StaticScope; import ortus.boxlang.runtime.scopes.ThisScope; import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.AbstractFunction; +import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.Array; +import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.IType; import ortus.boxlang.runtime.types.Property; +import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; import ortus.boxlang.runtime.types.meta.BoxMeta; @@ -343,6 +348,7 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { } else { boxClassName = rawBoxClassName; } + setProperty( "boxClassName", boxClassName ); String mappingName = getProperty( "mappingName" ); String mappingPath = getProperty( "mappingPath" ); String relativePath = getProperty( "relativePath" ); @@ -553,6 +559,28 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { Type.getType( Set.class ), null ); + AsmHelper.addPrviateStaticFieldGetter( classNode, + type, + "abstractMethods", + "getAbstractMethods", + Type.getType( Map.class ), + null ); + // TODO this is on the right track but needs need to match the body of the java version + MethodVisitor getAllAbstractMethodsMethodVisitor = classNode.visitMethod( Opcodes.ACC_PUBLIC, + "getAllAbstractMethods", + Type.getMethodDescriptor( Type.getType( Map.class ) ), + null, + null ); + getAllAbstractMethodsMethodVisitor.visitCode(); + getAllAbstractMethodsMethodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + getAllAbstractMethodsMethodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "abstractMethods", + Type.getType( Map.class ).getDescriptor() ); + getAllAbstractMethodsMethodVisitor.visitInsn( Type.getType( Map.class ).getOpcode( Opcodes.IRETURN ) ); + getAllAbstractMethodsMethodVisitor.visitMaxs( 0, 0 ); + getAllAbstractMethodsMethodVisitor.visitEnd(); + AsmHelper.addFieldGetter( classNode, type, "variablesScope", @@ -790,6 +818,9 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { generateSetOfCompileTimeMethodNames( boxClass ).forEach( node -> node.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "compileTimeMethodNames", Type.getDescriptor( Set.class ) ); + + generateMapOfAbstractMethodNames( boxClass ).forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); } ); return classNode; @@ -1057,6 +1088,102 @@ private static String getBoxExprAsString( BoxExpression expr ) { } } + private List createAbstractFunction( BoxFunctionDeclaration func ) { + List nodes = new ArrayList(); + + // public AbstractFunction( Key name, Argument[] arguments, String returnType, Access access, IStruct annotations, IStruct documentation, + // String sourceObjectName, String sourceObjectType ) { + // this.name = name; + // this.arguments = arguments; + // this.returnType = returnType; + // this.access = access; + // this.annotations = annotations; + // this.documentation = documentation; + // this.sourceObjectName = sourceObjectName; + // this.sourceObjectType = sourceObjectType; + // } + + nodes.add( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( AbstractFunction.class ) ) ); + nodes.add( new InsnNode( Opcodes.DUP ) ); + + // args + // Key name + nodes.addAll( createKey( func.getName() ) ); + // Argument[] arguments + List> argList = func.getArgs() + .stream() + .map( arg -> transform( arg, TransformerContext.NONE ) ) + .toList(); + nodes.addAll( AsmHelper.array( Type.getType( Argument.class ), argList ) ); + // String returnType + nodes.addAll( transform( func.getType(), TransformerContext.NONE ) ); + // Access access + nodes.add( + new FieldInsnNode( + Opcodes.GETSTATIC, + Type.getDescriptor( Function.Access.class ), + func.getAccessModifier().name().toUpperCase(), + Type.getDescriptor( Function.Access.class ) + ) + ); + // IStruct annotations + // TODO + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // IStruct documentation + // TODO + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // String sourceObjectName + nodes.add( new LdcInsnNode( getProperty( "boxClassName" ) ) ); + // String sourceObjectType + nodes.add( new LdcInsnNode( "class" ) ); + + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESPECIAL, + Type.getInternalName( ClassVariablesScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ), + false + ) + ); + + return nodes; + } + + private List generateMapOfAbstractMethodNames( BoxClass boxClass ) { + List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .filter( func -> func.getBody() == null ) + .map( func -> { + List> absFunc = List.of( + createKey( func.getName() ), + createAbstractFunction( func ) + ); + + return absFunc; + } ) + .flatMap( x -> x.stream() ) + .collect( java.util.stream.Collectors.toList() ); + + List nodes = new ArrayList(); + + nodes.addAll( AsmHelper.array( Type.getType( Key.class ), methodKeyLists ) ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( MapHelper.class ), + "LinkedHashMapOfProperties", + Type.getMethodDescriptor( Type.getType( Map.class ), Type.getType( Object[].class ) ), + false ) ); + + return nodes; + } + private List generateSetOfCompileTimeMethodNames( BoxClass boxClass ) { List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) .stream() From 916c9f1e1ca004d4cf501153f65260ab4e0a3542 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Mon, 9 Sep 2024 12:18:11 -0500 Subject: [PATCH 07/71] Fix java interaction --- .../boxlang/compiler/asmboxpiler/AsmHelper.java | 2 +- .../compiler/asmboxpiler/AsmTranspiler.java | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index b6565f431..52617465c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -507,7 +507,7 @@ public static MethodNode dereferenceAndInvoke( String name, Type descriptor, Typ "getInstance", Type.getMethodDescriptor( Type.getType( BoxRuntime.class ) ), false ); - node.visitMethodInsn( Opcodes.INVOKESTATIC, + node.visitMethodInsn( Opcodes.INVOKEVIRTUAL, Type.getInternalName( BoxRuntime.class ), "getRuntimeContext", Type.getMethodDescriptor( Type.getType( IBoxContext.class ) ), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 61ba21754..816e725db 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -411,8 +411,20 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { fqn = boxReturnType.getFqn(); } } - Type returnType = Type - .getType( "L" + ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ).replace( '.', '/' ) + ";" ); + // Type returnType = Type + // .getType( "L" + ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ).replace( '.', '/' ) + ";" ); + + Type returnType = switch ( ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ) ) { + case "void" -> Type.VOID_TYPE; + case "long" -> Type + .getType( Long.class ); + default -> Type.getType( "L" + + ( boxType + .equals( BoxType.Fqn ) ? fqn + : boxType.getSymbol() ) + .replace( '.', '/' ) + + ";" ); + }; List parameters = func.getArgs(); Type[] parameterTypes = new Type[ parameters.size() ]; for ( int i = 0; i < parameters.size(); i++ ) { From bf6f7a5ddac8858c96b195a7f356c6cc55756bb3 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Mon, 9 Sep 2024 14:29:30 -0500 Subject: [PATCH 08/71] Migrate BoxClassTransformer to its own class and implement missing methods --- .../compiler/asmboxpiler/AsmTranspiler.java | 525 +--------- .../compiler/asmboxpiler/Transpiler.java | 3 + .../statement/BoxClassTransformer.java | 960 ++++++++++++++++++ 3 files changed, 967 insertions(+), 521 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 816e725db..5395139ad 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -1,15 +1,12 @@ package ortus.boxlang.compiler.asmboxpiler; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Stream; -import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; @@ -18,7 +15,6 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -59,6 +55,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxUnaryOperationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxAssertTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxBufferOutputTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxClassTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxComponentTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxDoTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxForInTransformer; @@ -71,7 +68,6 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxWhileTransformer; import ortus.boxlang.compiler.ast.BoxClass; import ortus.boxlang.compiler.ast.BoxExpression; -import ortus.boxlang.compiler.ast.BoxInterface; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxScript; import ortus.boxlang.compiler.ast.Source; @@ -118,40 +114,27 @@ import ortus.boxlang.compiler.ast.statement.BoxProperty; import ortus.boxlang.compiler.ast.statement.BoxRethrow; import ortus.boxlang.compiler.ast.statement.BoxReturn; -import ortus.boxlang.compiler.ast.statement.BoxReturnType; import ortus.boxlang.compiler.ast.statement.BoxStatementBlock; import ortus.boxlang.compiler.ast.statement.BoxSwitch; import ortus.boxlang.compiler.ast.statement.BoxThrow; import ortus.boxlang.compiler.ast.statement.BoxTry; -import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.compiler.ast.statement.BoxWhile; import ortus.boxlang.compiler.ast.statement.component.BoxComponent; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.dynamic.IReferenceable; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; -import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService; import ortus.boxlang.runtime.loader.ImportDefinition; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.ClassVariablesScope; import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.StaticScope; -import ortus.boxlang.runtime.scopes.ThisScope; -import ortus.boxlang.runtime.scopes.VariablesScope; import ortus.boxlang.runtime.types.AbstractFunction; import ortus.boxlang.runtime.types.Argument; -import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.IType; import ortus.boxlang.runtime.types.Property; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; -import ortus.boxlang.runtime.types.meta.BoxMeta; -import ortus.boxlang.runtime.types.util.BLCollector; -import ortus.boxlang.runtime.types.util.ListUtil; import ortus.boxlang.runtime.types.util.MapHelper; import ortus.boxlang.runtime.util.ResolvedFilePath; @@ -334,508 +317,7 @@ public ClassNode transpile( BoxScript boxScript ) throws BoxRuntimeException { @Override public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { - Source source = boxClass.getPosition().getSource(); - String sourceType = getProperty( "sourceType" ); - - String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() - : "unknown"; - String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; - String boxPackageName = getProperty( "boxPackageName" ); - String rawBoxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ), boxClassName; - // trim leading . if exists - if ( rawBoxClassName.startsWith( "." ) ) { - boxClassName = rawBoxClassName.substring( 1 ); - } else { - boxClassName = rawBoxClassName; - } - setProperty( "boxClassName", boxClassName ); - String mappingName = getProperty( "mappingName" ); - String mappingPath = getProperty( "mappingPath" ); - String relativePath = getProperty( "relativePath" ); - - Type type = Type.getType( "L" + getProperty( "packageName" ).replace( '.', '/' ) - + "/" + getProperty( "classname" ) + ";" ); - - List interfaces = new ArrayList<>(); - interfaces.add( Type.getType( IClassRunnable.class ) ); - interfaces.add( Type.getType( IReferenceable.class ) ); - interfaces.add( Type.getType( IType.class ) ); - interfaces.add( Type.getType( Serializable.class ) ); - List interfaceMethods = List.of(); - BoxExpression implementsValue = boxClass.getAnnotations().stream() - .filter( it -> it.getKey().getValue().equalsIgnoreCase( "implements" ) ) - .findFirst() - .map( BoxAnnotation::getValue ) - .orElse( null ); - if ( implementsValue instanceof BoxStringLiteral str ) { - String implementsStringList = str.getValue(); - // Collect and trim all strings starting with "java:" - Array implementsArray = ListUtil.asList( implementsStringList, "," ).stream() - .map( String::valueOf ) - .map( String::trim ) - .filter( it -> it.toLowerCase().startsWith( "java:" ) ) - .map( it -> it.substring( 5 ) ) - .collect( BLCollector.toArray() ); - var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( new ScriptingRequestBoxContext(), implementsArray ); - // TODO: Remove methods that already have a @overrideJava UDF definition to avoid duplicates - interfaces.addAll( interfaceProxyDefinition.interfaces().stream().map( iface -> Type.getType( "L" + iface.replace( '.', '/' ) + ";" ) ).toList() ); - interfaceMethods = interfaceProxyDefinition.methods().stream() - .map( method -> AsmHelper.dereferenceAndInvoke( method.getName(), Type.getType( method ), type ) ) - .toList(); - } - - Type superclass = Type.getType( Object.class ); - boolean isJavaExtends; - List extendsMethods = List.of(); - BoxExpression extendsValue = boxClass.getAnnotations().stream() - .filter( it -> it.getKey().getValue().equalsIgnoreCase( "extends" ) ) - .findFirst() - .map( BoxAnnotation::getValue ) - .orElse( null ); - if ( extendsValue instanceof BoxStringLiteral str ) { - String extendsStringValue = str.getValue().trim(); - if ( extendsStringValue.toLowerCase().startsWith( "java:" ) ) { - superclass = Type.getType( "L" + extendsStringValue.substring( 5 ).replace( '.', '/' ) + ";" ); - isJavaExtends = true; - // search for UDFs that need a proxy created - extendsMethods = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) - .stream() - .filter( it -> it.getAnnotations().stream().anyMatch( anno -> anno.getKey().getValue().equalsIgnoreCase( EXTENDS_ANNOTATION_MARKER ) ) ) - .map( func -> { - BoxReturnType boxReturnType = func.getType(); - BoxType boxType = BoxType.Any; - String fqn = null; - if ( boxReturnType != null ) { - boxType = boxReturnType.getType(); - if ( boxType.equals( BoxType.Fqn ) ) { - fqn = boxReturnType.getFqn(); - } - } - // Type returnType = Type - // .getType( "L" + ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ).replace( '.', '/' ) + ";" ); - - Type returnType = switch ( ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ) ) { - case "void" -> Type.VOID_TYPE; - case "long" -> Type - .getType( Long.class ); - default -> Type.getType( "L" - + ( boxType - .equals( BoxType.Fqn ) ? fqn - : boxType.getSymbol() ) - .replace( '.', '/' ) - + ";" ); - }; - List parameters = func.getArgs(); - Type[] parameterTypes = new Type[ parameters.size() ]; - for ( int i = 0; i < parameters.size(); i++ ) { - BoxArgumentDeclaration parameter = parameters.get( i ); - parameterTypes[ i ] = Type.getType( "L" + parameter.getType().replace( '.', '/' ) + ";" ); - - } - return AsmHelper.dereferenceAndInvoke( func.getName(), Type.getMethodType( returnType, parameterTypes ), type ); - } ) - .toList(); - } else { - isJavaExtends = false; - } - } else { - isJavaExtends = false; - } - - ClassNode classNode = new ClassNode(); - - AsmHelper.init( classNode, false, type, superclass, methodVisitor -> { - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ClassVariablesScope.class ) ); - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( ClassVariablesScope.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ), - false ); - methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "variablesScope", Type.getDescriptor( VariablesScope.class ) ); - - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ThisScope.class ) ); - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( ThisScope.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); - methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "thisScope", Type.getDescriptor( ThisScope.class ) ); - - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - createKey( boxClassName ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "name", Type.getDescriptor( Key.class ) ); - - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ArrayList.class ) ); - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, Type.getInternalName( ArrayList.class ), "", Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); - methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "interfaces", Type.getDescriptor( List.class ) ); - - }, interfaces.toArray( Type[]::new ) ); - - interfaceMethods.forEach( methodNode -> methodNode.accept( classNode ) ); - extendsMethods.forEach( methodNode -> methodNode.accept( classNode ) ); - - classNode.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, "serialVersionUID", Type.getDescriptor( long.class ), null, 1L ) - .visitEnd(); - - AsmHelper.addStaticFieldGetter( classNode, - type, - "imports", - "getImports", - Type.getType( List.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "path", - "getRunnablePath", - Type.getType( ResolvedFilePath.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "sourceType", - "getSourceType", - Type.getType( BoxSourceType.class ), - null ); - AsmHelper.addStaticFieldGetterWithStaticGetter( classNode, - type, - "annotations", - "getAnnotations", - "getAnnotationsStatic", - Type.getType( IStruct.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "documentation", - "getDocumentation", - Type.getType( IStruct.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "properties", - "getProperties", - Type.getType( Map.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "getterLookup", - "getGetterLookup", - Type.getType( Map.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "setterLookup", - "getSetterLookup", - Type.getType( Map.class ), - null ); - AsmHelper.addStaticFieldGetter( classNode, - type, - "isJavaExtends", - "isJavaExtends", - Type.BOOLEAN_TYPE, - isJavaExtends ? 1 : 0 ); - AsmHelper.addStaticFieldGetterWithStaticGetter( classNode, - type, - "staticScope", - "getStaticScope", - "getStaticScopeStatic", - Type.getType( StaticScope.class ), - null ); - - classNode.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, - "keys", - Type.getDescriptor( Key[].class ), - null, - null ).visitEnd(); - classNode.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, - "staticInitialized", - Type.getDescriptor( boolean.class ), - null, - 0 ).visitEnd(); - - // classNode.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, - // "compileTimeMethodNames", - // Type.getDescriptor( Set.class ), - // null, - // null ).visitEnd(); - AsmHelper.addPrviateStaticFieldGetter( classNode, - type, - "compileTimeMethodNames", - "getCompileTimeMethodNames", - Type.getType( Set.class ), - null ); - - AsmHelper.addPrviateStaticFieldGetter( classNode, - type, - "abstractMethods", - "getAbstractMethods", - Type.getType( Map.class ), - null ); - // TODO this is on the right track but needs need to match the body of the java version - MethodVisitor getAllAbstractMethodsMethodVisitor = classNode.visitMethod( Opcodes.ACC_PUBLIC, - "getAllAbstractMethods", - Type.getMethodDescriptor( Type.getType( Map.class ) ), - null, - null ); - getAllAbstractMethodsMethodVisitor.visitCode(); - getAllAbstractMethodsMethodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - getAllAbstractMethodsMethodVisitor.visitFieldInsn( Opcodes.GETSTATIC, - type.getInternalName(), - "abstractMethods", - Type.getType( Map.class ).getDescriptor() ); - getAllAbstractMethodsMethodVisitor.visitInsn( Type.getType( Map.class ).getOpcode( Opcodes.IRETURN ) ); - getAllAbstractMethodsMethodVisitor.visitMaxs( 0, 0 ); - getAllAbstractMethodsMethodVisitor.visitEnd(); - - AsmHelper.addFieldGetter( classNode, - type, - "variablesScope", - "getVariablesScope", - Type.getType( VariablesScope.class ), - null ); - AsmHelper.addFieldGetter( classNode, - type, - "thisScope", - "getThisScope", - Type.getType( ThisScope.class ), - null ); - AsmHelper.addFieldGetter( classNode, - type, - "name", - "getName", - Type.getType( Key.class ), - null ); - AsmHelper.addFieldGetter( classNode, - type, - "interfaces", - "getInterfaces", - Type.getType( List.class ), - null ); - AsmHelper.addFieldGetterAndSetter( classNode, - type, - "_super", - "getSuper", - "_setSuper", - Type.getType( IClassRunnable.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, - type, - "child", - "getChild", - "setChild", - Type.getType( IClassRunnable.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, - type, - "canOutput", - "getCanOutput", - "setCanOutput", - Type.getType( Boolean.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, - type, - "$bx", - "_getbx", - "_setbx", - Type.getType( BoxMeta.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, - type, - "canInvokeImplicitAccessor", - "getCanInvokeImplicitAccessor", - "setCanInvokeImplicitAccessor", - Type.getType( Boolean.class ), - null, - methodVisitor -> { - } ); - - AsmHelper.boxClassSupport( classNode, "pseudoConstructor", Type.VOID_TYPE, Type.getType( IBoxContext.class ) ); - AsmHelper.boxClassSupport( classNode, "canOutput", Type.getType( Boolean.class ) ); - AsmHelper.boxClassSupport( classNode, "getBoxMeta", Type.getType( BoxMeta.class ) ); - AsmHelper.boxClassSupport( classNode, "getMetaData", Type.getType( IStruct.class ) ); - AsmHelper.boxClassSupport( classNode, "asString", Type.getType( String.class ) ); - AsmHelper.boxClassSupport( classNode, "canInvokeImplicitAccessor", Type.getType( Boolean.class ), Type.getType( IBoxContext.class ) ); - AsmHelper.boxClassSupport( classNode, "setSuper", Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ); - AsmHelper.boxClassSupport( classNode, "getBottomClass", Type.getType( IClassRunnable.class ) ); - AsmHelper.boxClassSupport( classNode, "assign", Type.getType( Object.class ), Type.getType( IBoxContext.class ), Type.getType( Key.class ), - Type.getType( Object.class ) ); - AsmHelper.boxClassSupport( classNode, "dereference", Type.getType( Object.class ), Type.getType( IBoxContext.class ), Type.getType( Key.class ), - Type.getType( Boolean.class ) ); - AsmHelper.boxClassSupport( classNode, "dereferenceAndInvoke", Type.getType( Object.class ), Type.getType( IBoxContext.class ), - Type.getType( Key.class ), Type.getType( Object[].class ), Type.getType( Boolean.class ) ); - AsmHelper.boxClassSupport( classNode, "dereferenceAndInvoke", Type.getType( Object.class ), Type.getType( IBoxContext.class ), - Type.getType( Key.class ), Type.getType( Map.class ), Type.getType( Boolean.class ) ); - AsmHelper.boxClassSupport( classNode, "registerInterface", Type.VOID_TYPE, Type.getType( BoxInterface.class ) ); - - // these imports need to happen before any methods are processed - the actual nodes will be used later on in the static init section - List> imports = new ArrayList<>(); - for ( BoxImport statement : boxClass.getImports() ) { - imports.add( transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ) ); - } - List importNodes = AsmHelper.array( Type.getType( ImportDefinition.class ), Stream.concat( - imports.stream(), - getImports().stream().map( raw -> { - List nodes = new ArrayList<>(); - nodes.addAll( raw ); - nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, - Type.getInternalName( ImportDefinition.class ), - "parse", - Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), - false ) ); - return nodes; - } ) - ).filter( l -> l.size() > 0 ).toList() ); - // end import node setup - - AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, this, true, - () -> { - return boxClass.getBody() - .stream() - .sorted( ( a, b ) -> { - if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { - return -1; - } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { - return 1; - } - - return 0; - - } ) - .flatMap( statement -> transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) - .toList(); - } - ); - - AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, this, true, - List::of - ); - - AsmHelper.complete( classNode, type, methodVisitor -> { - AsmHelper.resolvedFilePath( methodVisitor, mappingName, mappingPath, relativePath, filePath ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "path", - Type.getDescriptor( ResolvedFilePath.class ) ); - - methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, - Type.getInternalName( BoxSourceType.class ), - sourceType, - Type.getDescriptor( BoxSourceType.class ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "sourceType", - Type.getDescriptor( BoxSourceType.class ) ); - - List annotations = transformAnnotations( boxClass.getAnnotations() ); - List documenation = transformDocumentation( boxClass.getDocumentation() ); - List> properties = transformProperties( type, boxClass.getProperties(), sourceType ); - - methodVisitor.visitLdcInsn( getKeys().size() ); - methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); - int index = 0; - for ( BoxExpression expression : getKeys().values() ) { - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitLdcInsn( index++ ); - transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ).forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( Key.class ), - "of", - Type.getMethodDescriptor( Type.getType( Key.class ), Type.getType( Object.class ) ), - false ); - methodVisitor.visitInsn( Opcodes.AASTORE ); - } - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "keys", - Type.getDescriptor( Key[].class ) ); - - methodVisitor.visitLdcInsn( 0 ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "staticInitialized", - Type.getDescriptor( boolean.class ) ); - - methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( StaticScope.class ) ); - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( StaticScope.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "staticScope", - Type.getDescriptor( StaticScope.class ) ); - - methodVisitor.visitLdcInsn( isJavaExtends ? 1 : 0 ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "isJavaExtends", Type.getDescriptor( boolean.class ) ); - - methodVisitor.visitLdcInsn( 1L ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "serialVersionUID", Type.getDescriptor( long.class ) ); - - importNodes.forEach( node -> node.accept( methodVisitor ) ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( List.class ), - "of", - Type.getMethodDescriptor( Type.getType( List.class ), Type.getType( Object[].class ) ), - true ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "imports", - Type.getDescriptor( List.class ) ); - - annotations.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "annotations", - Type.getDescriptor( IStruct.class ) ); - - documenation.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "documentation", - Type.getDescriptor( IStruct.class ) ); - - properties.get( 0 ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "properties", - Type.getDescriptor( Map.class ) ); - - properties.get( 1 ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "getterLookup", - Type.getDescriptor( Map.class ) ); - - properties.get( 2 ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "setterLookup", - Type.getDescriptor( Map.class ) ); - - generateSetOfCompileTimeMethodNames( boxClass ).forEach( node -> node.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "compileTimeMethodNames", Type.getDescriptor( Set.class ) ); - - generateMapOfAbstractMethodNames( boxClass ).forEach( node -> node.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); - } ); - - return classNode; + return BoxClassTransformer.transpile( this, boxClass ); } @Override @@ -847,7 +329,8 @@ public List transform( BoxNode node, TransformerContext contex throw new IllegalStateException( "unsupported: " + node.getClass().getSimpleName() + " : " + node.getSourceText() ); } - private List> transformProperties( Type declaringType, List properties, String sourceType ) { + @Override + public List> transformProperties( Type declaringType, List properties, String sourceType ) { List> members = new ArrayList<>(); List> getterLookup = new ArrayList<>(); List> setterLookup = new ArrayList<>(); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index a7f8711dd..20a77fea1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -28,6 +28,7 @@ import ortus.boxlang.compiler.ast.expression.BoxStringLiteral; import ortus.boxlang.compiler.ast.statement.BoxAnnotation; import ortus.boxlang.compiler.ast.statement.BoxDocumentationAnnotation; +import ortus.boxlang.compiler.ast.statement.BoxProperty; import ortus.boxlang.runtime.loader.ImportDefinition; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; @@ -163,6 +164,8 @@ public List createKey( BoxExpression expr ) { } } + public abstract List> transformProperties( Type declaringType, List properties, String sourceType ); + public List createKey( String expr ) { return createKey( new BoxStringLiteral( expr, null, expr ) ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java new file mode 100644 index 000000000..b697fbc75 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -0,0 +1,960 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.statement; + +import java.io.Serializable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxClass; +import ortus.boxlang.compiler.ast.BoxExpression; +import ortus.boxlang.compiler.ast.BoxInterface; +import ortus.boxlang.compiler.ast.Source; +import ortus.boxlang.compiler.ast.SourceFile; +import ortus.boxlang.compiler.ast.expression.BoxStringLiteral; +import ortus.boxlang.compiler.ast.statement.BoxAnnotation; +import ortus.boxlang.compiler.ast.statement.BoxArgumentDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxImport; +import ortus.boxlang.compiler.ast.statement.BoxReturnType; +import ortus.boxlang.compiler.ast.statement.BoxType; +import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.dynamic.IReferenceable; +import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService; +import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.runnables.IClassRunnable; +import ortus.boxlang.runtime.scopes.ClassVariablesScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.StaticScope; +import ortus.boxlang.runtime.scopes.ThisScope; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.AbstractFunction; +import ortus.boxlang.runtime.types.Argument; +import ortus.boxlang.runtime.types.Array; +import ortus.boxlang.runtime.types.Function; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.IType; +import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.types.meta.BoxMeta; +import ortus.boxlang.runtime.types.util.BLCollector; +import ortus.boxlang.runtime.types.util.ListUtil; +import ortus.boxlang.runtime.types.util.MapHelper; +import ortus.boxlang.runtime.util.ResolvedFilePath; + +public class BoxClassTransformer { + + public static final Type CLASS_TYPE = Type.getType( Class.class ); + public static final Type CLASS_ARRAY_TYPE = Type.getType( Class[].class ); + + private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; + + public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) throws BoxRuntimeException { + Source source = boxClass.getPosition().getSource(); + String sourceType = transpiler.getProperty( "sourceType" ); + + String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() + : "unknown"; + String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; + String boxPackageName = transpiler.getProperty( "boxPackageName" ); + String rawBoxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ), boxClassName; + // trim leading . if exists + if ( rawBoxClassName.startsWith( "." ) ) { + boxClassName = rawBoxClassName.substring( 1 ); + } else { + boxClassName = rawBoxClassName; + } + transpiler.setProperty( "boxClassName", boxClassName ); + String mappingName = transpiler.getProperty( "mappingName" ); + String mappingPath = transpiler.getProperty( "mappingPath" ); + String relativePath = transpiler.getProperty( "relativePath" ); + + Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + + "/" + transpiler.getProperty( "classname" ) + ";" ); + + List interfaces = new ArrayList<>(); + interfaces.add( Type.getType( IClassRunnable.class ) ); + interfaces.add( Type.getType( IReferenceable.class ) ); + interfaces.add( Type.getType( IType.class ) ); + interfaces.add( Type.getType( Serializable.class ) ); + List interfaceMethods = List.of(); + BoxExpression implementsValue = boxClass.getAnnotations().stream() + .filter( it -> it.getKey().getValue().equalsIgnoreCase( "implements" ) ) + .findFirst() + .map( BoxAnnotation::getValue ) + .orElse( null ); + if ( implementsValue instanceof BoxStringLiteral str ) { + String implementsStringList = str.getValue(); + // Collect and trim all strings starting with "java:" + Array implementsArray = ListUtil.asList( implementsStringList, "," ).stream() + .map( String::valueOf ) + .map( String::trim ) + .filter( it -> it.toLowerCase().startsWith( "java:" ) ) + .map( it -> it.substring( 5 ) ) + .collect( BLCollector.toArray() ); + var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( new ScriptingRequestBoxContext(), implementsArray ); + // TODO: Remove methods that already have a @overrideJava UDF definition to avoid duplicates + interfaces.addAll( interfaceProxyDefinition.interfaces().stream().map( iface -> Type.getType( "L" + iface.replace( '.', '/' ) + ";" ) ).toList() ); + interfaceMethods = interfaceProxyDefinition.methods().stream() + .map( method -> AsmHelper.dereferenceAndInvoke( method.getName(), Type.getType( method ), type ) ) + .toList(); + } + + Type superclass = Type.getType( Object.class ); + boolean isJavaExtends; + List extendsMethods = List.of(); + BoxExpression extendsValue = boxClass.getAnnotations().stream() + .filter( it -> it.getKey().getValue().equalsIgnoreCase( "extends" ) ) + .findFirst() + .map( BoxAnnotation::getValue ) + .orElse( null ); + if ( extendsValue instanceof BoxStringLiteral str ) { + String extendsStringValue = str.getValue().trim(); + if ( extendsStringValue.toLowerCase().startsWith( "java:" ) ) { + superclass = Type.getType( "L" + extendsStringValue.substring( 5 ).replace( '.', '/' ) + ";" ); + isJavaExtends = true; + // search for UDFs that need a proxy created + extendsMethods = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .filter( it -> it.getAnnotations().stream().anyMatch( anno -> anno.getKey().getValue().equalsIgnoreCase( EXTENDS_ANNOTATION_MARKER ) ) ) + .map( func -> { + BoxReturnType boxReturnType = func.getType(); + BoxType boxType = BoxType.Any; + String fqn = null; + if ( boxReturnType != null ) { + boxType = boxReturnType.getType(); + if ( boxType.equals( BoxType.Fqn ) ) { + fqn = boxReturnType.getFqn(); + } + } + // Type returnType = Type + // .getType( "L" + ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ).replace( '.', '/' ) + ";" ); + + Type returnType = switch ( ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ) ) { + case "void" -> Type.VOID_TYPE; + case "long" -> Type + .getType( Long.class ); + default -> Type.getType( "L" + + ( boxType + .equals( BoxType.Fqn ) ? fqn + : boxType.getSymbol() ) + .replace( '.', '/' ) + + ";" ); + }; + List parameters = func.getArgs(); + Type[] parameterTypes = new Type[ parameters.size() ]; + for ( int i = 0; i < parameters.size(); i++ ) { + BoxArgumentDeclaration parameter = parameters.get( i ); + parameterTypes[ i ] = Type.getType( "L" + parameter.getType().replace( '.', '/' ) + ";" ); + + } + return AsmHelper.dereferenceAndInvoke( func.getName(), Type.getMethodType( returnType, parameterTypes ), type ); + } ) + .toList(); + } else { + isJavaExtends = false; + } + } else { + isJavaExtends = false; + } + + ClassNode classNode = new ClassNode(); + + AsmHelper.init( classNode, false, type, superclass, methodVisitor -> { + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ClassVariablesScope.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( ClassVariablesScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "variablesScope", Type.getDescriptor( VariablesScope.class ) ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ThisScope.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( ThisScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "thisScope", Type.getDescriptor( ThisScope.class ) ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + transpiler.createKey( boxClassName ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "name", Type.getDescriptor( Key.class ) ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ArrayList.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, Type.getInternalName( ArrayList.class ), "", Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, type.getInternalName(), "interfaces", Type.getDescriptor( List.class ) ); + + }, interfaces.toArray( Type[]::new ) ); + + interfaceMethods.forEach( methodNode -> methodNode.accept( classNode ) ); + extendsMethods.forEach( methodNode -> methodNode.accept( classNode ) ); + + classNode.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, "serialVersionUID", Type.getDescriptor( long.class ), null, 1L ) + .visitEnd(); + + AsmHelper.addStaticFieldGetter( classNode, + type, + "imports", + "getImports", + Type.getType( List.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "path", + "getRunnablePath", + Type.getType( ResolvedFilePath.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "sourceType", + "getSourceType", + Type.getType( BoxSourceType.class ), + null ); + AsmHelper.addStaticFieldGetterWithStaticGetter( classNode, + type, + "annotations", + "getAnnotations", + "getAnnotationsStatic", + Type.getType( IStruct.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "documentation", + "getDocumentation", + Type.getType( IStruct.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "properties", + "getProperties", + Type.getType( Map.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "getterLookup", + "getGetterLookup", + Type.getType( Map.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "setterLookup", + "getSetterLookup", + Type.getType( Map.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "isJavaExtends", + "isJavaExtends", + Type.BOOLEAN_TYPE, + isJavaExtends ? 1 : 0 ); + AsmHelper.addStaticFieldGetterWithStaticGetter( classNode, + type, + "staticScope", + "getStaticScope", + "getStaticScopeStatic", + Type.getType( StaticScope.class ), + null ); + + classNode.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, + "keys", + Type.getDescriptor( Key[].class ), + null, + null ).visitEnd(); + classNode.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "staticInitialized", + Type.getDescriptor( boolean.class ), + null, + 0 ).visitEnd(); + + // classNode.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + // "compileTimeMethodNames", + // Type.getDescriptor( Set.class ), + // null, + // null ).visitEnd(); + AsmHelper.addPrviateStaticFieldGetter( classNode, + type, + "compileTimeMethodNames", + "getCompileTimeMethodNames", + Type.getType( Set.class ), + null ); + + AsmHelper.addPrviateStaticFieldGetter( classNode, + type, + "abstractMethods", + "getAbstractMethods", + Type.getType( Map.class ), + null ); + // TODO this is on the right track but needs need to match the body of the java version + MethodVisitor getAllAbstractMethodsMethodVisitor = classNode.visitMethod( Opcodes.ACC_PUBLIC, + "getAllAbstractMethods", + Type.getMethodDescriptor( Type.getType( Map.class ) ), + null, + null ); + getAllAbstractMethodsMethodVisitor.visitCode(); + getAllAbstractMethodsMethodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + getAllAbstractMethodsMethodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "abstractMethods", + Type.getType( Map.class ).getDescriptor() ); + getAllAbstractMethodsMethodVisitor.visitInsn( Type.getType( Map.class ).getOpcode( Opcodes.IRETURN ) ); + getAllAbstractMethodsMethodVisitor.visitMaxs( 0, 0 ); + getAllAbstractMethodsMethodVisitor.visitEnd(); + + defineLookupPrivateMethod( transpiler, classNode, type ); + defineLookupPrivateField( transpiler, classNode, type ); + + AsmHelper.addFieldGetter( classNode, + type, + "variablesScope", + "getVariablesScope", + Type.getType( VariablesScope.class ), + null ); + AsmHelper.addFieldGetter( classNode, + type, + "thisScope", + "getThisScope", + Type.getType( ThisScope.class ), + null ); + AsmHelper.addFieldGetter( classNode, + type, + "name", + "getName", + Type.getType( Key.class ), + null ); + AsmHelper.addFieldGetter( classNode, + type, + "interfaces", + "getInterfaces", + Type.getType( List.class ), + null ); + AsmHelper.addFieldGetterAndSetter( classNode, + type, + "_super", + "getSuper", + "_setSuper", + Type.getType( IClassRunnable.class ), + null, + methodVisitor -> { + } ); + AsmHelper.addFieldGetterAndSetter( classNode, + type, + "child", + "getChild", + "setChild", + Type.getType( IClassRunnable.class ), + null, + methodVisitor -> { + } ); + AsmHelper.addFieldGetterAndSetter( classNode, + type, + "canOutput", + "getCanOutput", + "setCanOutput", + Type.getType( Boolean.class ), + null, + methodVisitor -> { + } ); + AsmHelper.addFieldGetterAndSetter( classNode, + type, + "$bx", + "_getbx", + "_setbx", + Type.getType( BoxMeta.class ), + null, + methodVisitor -> { + } ); + AsmHelper.addFieldGetterAndSetter( classNode, + type, + "canInvokeImplicitAccessor", + "getCanInvokeImplicitAccessor", + "setCanInvokeImplicitAccessor", + Type.getType( Boolean.class ), + null, + methodVisitor -> { + } ); + + AsmHelper.boxClassSupport( classNode, "pseudoConstructor", Type.VOID_TYPE, Type.getType( IBoxContext.class ) ); + AsmHelper.boxClassSupport( classNode, "canOutput", Type.getType( Boolean.class ) ); + AsmHelper.boxClassSupport( classNode, "getBoxMeta", Type.getType( BoxMeta.class ) ); + AsmHelper.boxClassSupport( classNode, "getMetaData", Type.getType( IStruct.class ) ); + AsmHelper.boxClassSupport( classNode, "asString", Type.getType( String.class ) ); + AsmHelper.boxClassSupport( classNode, "canInvokeImplicitAccessor", Type.getType( Boolean.class ), Type.getType( IBoxContext.class ) ); + AsmHelper.boxClassSupport( classNode, "setSuper", Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ); + AsmHelper.boxClassSupport( classNode, "getBottomClass", Type.getType( IClassRunnable.class ) ); + AsmHelper.boxClassSupport( classNode, "assign", Type.getType( Object.class ), Type.getType( IBoxContext.class ), Type.getType( Key.class ), + Type.getType( Object.class ) ); + AsmHelper.boxClassSupport( classNode, "dereference", Type.getType( Object.class ), Type.getType( IBoxContext.class ), Type.getType( Key.class ), + Type.getType( Boolean.class ) ); + AsmHelper.boxClassSupport( classNode, "dereferenceAndInvoke", Type.getType( Object.class ), Type.getType( IBoxContext.class ), + Type.getType( Key.class ), Type.getType( Object[].class ), Type.getType( Boolean.class ) ); + AsmHelper.boxClassSupport( classNode, "dereferenceAndInvoke", Type.getType( Object.class ), Type.getType( IBoxContext.class ), + Type.getType( Key.class ), Type.getType( Map.class ), Type.getType( Boolean.class ) ); + AsmHelper.boxClassSupport( classNode, "registerInterface", Type.VOID_TYPE, Type.getType( BoxInterface.class ) ); + + // these imports need to happen before any methods are processed - the actual nodes will be used later on in the static init section + List> imports = new ArrayList<>(); + for ( BoxImport statement : boxClass.getImports() ) { + imports.add( transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ) ); + } + List importNodes = AsmHelper.array( Type.getType( ImportDefinition.class ), Stream.concat( + imports.stream(), + transpiler.getImports().stream().map( raw -> { + List nodes = new ArrayList<>(); + nodes.addAll( raw ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( ImportDefinition.class ), + "parse", + Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), + false ) ); + return nodes; + } ) + ).filter( l -> l.size() > 0 ).toList() ); + // end import node setup + + AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, true, + () -> { + return boxClass.getBody() + .stream() + .sorted( ( a, b ) -> { + if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { + return -1; + } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { + return 1; + } + + return 0; + + } ) + .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) + .toList(); + } + ); + + AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, true, + List::of + ); + + AsmHelper.complete( classNode, type, methodVisitor -> { + AsmHelper.resolvedFilePath( methodVisitor, mappingName, mappingPath, relativePath, filePath ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "path", + Type.getDescriptor( ResolvedFilePath.class ) ); + + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + Type.getInternalName( BoxSourceType.class ), + sourceType, + Type.getDescriptor( BoxSourceType.class ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "sourceType", + Type.getDescriptor( BoxSourceType.class ) ); + + List annotations = transpiler.transformAnnotations( boxClass.getAnnotations() ); + List documenation = transpiler.transformDocumentation( boxClass.getDocumentation() ); + List> properties = transpiler.transformProperties( type, boxClass.getProperties(), sourceType ); + + methodVisitor.visitLdcInsn( transpiler.getKeys().size() ); + methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); + int index = 0; + for ( BoxExpression expression : transpiler.getKeys().values() ) { + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitLdcInsn( index++ ); + transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ) + .forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, + Type.getInternalName( Key.class ), + "of", + Type.getMethodDescriptor( Type.getType( Key.class ), Type.getType( Object.class ) ), + false ); + methodVisitor.visitInsn( Opcodes.AASTORE ); + } + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "keys", + Type.getDescriptor( Key[].class ) ); + + methodVisitor.visitLdcInsn( 0 ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "staticInitialized", + Type.getDescriptor( boolean.class ) ); + + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( StaticScope.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( StaticScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "staticScope", + Type.getDescriptor( StaticScope.class ) ); + + methodVisitor.visitLdcInsn( isJavaExtends ? 1 : 0 ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "isJavaExtends", Type.getDescriptor( boolean.class ) ); + + methodVisitor.visitLdcInsn( 1L ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "serialVersionUID", Type.getDescriptor( long.class ) ); + + importNodes.forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, + Type.getInternalName( List.class ), + "of", + Type.getMethodDescriptor( Type.getType( List.class ), Type.getType( Object[].class ) ), + true ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "imports", + Type.getDescriptor( List.class ) ); + + annotations.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "annotations", + Type.getDescriptor( IStruct.class ) ); + + documenation.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "documentation", + Type.getDescriptor( IStruct.class ) ); + + properties.get( 0 ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "properties", + Type.getDescriptor( Map.class ) ); + + properties.get( 1 ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "getterLookup", + Type.getDescriptor( Map.class ) ); + + properties.get( 2 ).forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "setterLookup", + Type.getDescriptor( Map.class ) ); + + generateSetOfCompileTimeMethodNames( transpiler, boxClass ).forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "compileTimeMethodNames", Type.getDescriptor( Set.class ) ); + + generateMapOfAbstractMethodNames( transpiler, boxClass ).forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); + } ); + + return classNode; + } + + private static List generateMapOfAbstractMethodNames( Transpiler transpiler, BoxClass boxClass ) { + List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .filter( func -> func.getBody() == null ) + .map( func -> { + List> absFunc = List.of( + transpiler.createKey( func.getName() ), + createAbstractFunction( transpiler, func ) + ); + + return absFunc; + } ) + .flatMap( x -> x.stream() ) + .collect( java.util.stream.Collectors.toList() ); + + List nodes = new ArrayList(); + + nodes.addAll( AsmHelper.array( Type.getType( Key.class ), methodKeyLists ) ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( MapHelper.class ), + "LinkedHashMapOfProperties", + Type.getMethodDescriptor( Type.getType( Map.class ), Type.getType( Object[].class ) ), + false ) ); + + return nodes; + } + + private static List generateSetOfCompileTimeMethodNames( Transpiler transpiler, BoxClass boxClass ) { + List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .map( BoxFunctionDeclaration::getName ) + .map( transpiler::createKey ) + .collect( java.util.stream.Collectors.toList() ); + + List nodes = new ArrayList(); + + nodes.addAll( AsmHelper.array( Type.getType( Key.class ), methodKeyLists ) ); + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Set.class ), + "of", + Type.getMethodDescriptor( Type.getType( Set.class ), Type.getType( Object[].class ) ), + true + ) + ); + + return nodes; + + } + + private static List createAbstractFunction( Transpiler transpiler, BoxFunctionDeclaration func ) { + List nodes = new ArrayList(); + + // public AbstractFunction( Key name, Argument[] arguments, String returnType, Access access, IStruct annotations, IStruct documentation, + // String sourceObjectName, String sourceObjectType ) { + // this.name = name; + // this.arguments = arguments; + // this.returnType = returnType; + // this.access = access; + // this.annotations = annotations; + // this.documentation = documentation; + // this.sourceObjectName = sourceObjectName; + // this.sourceObjectType = sourceObjectType; + // } + + nodes.add( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( AbstractFunction.class ) ) ); + nodes.add( new InsnNode( Opcodes.DUP ) ); + + // args + // Key name + nodes.addAll( transpiler.createKey( func.getName() ) ); + // Argument[] arguments + List> argList = func.getArgs() + .stream() + .map( arg -> transpiler.transform( arg, TransformerContext.NONE ) ) + .toList(); + nodes.addAll( AsmHelper.array( Type.getType( Argument.class ), argList ) ); + // String returnType + nodes.addAll( transpiler.transform( func.getType(), TransformerContext.NONE ) ); + // Access access + nodes.add( + new FieldInsnNode( + Opcodes.GETSTATIC, + Type.getDescriptor( Function.Access.class ), + func.getAccessModifier().name().toUpperCase(), + Type.getDescriptor( Function.Access.class ) + ) + ); + // IStruct annotations + // TODO + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // IStruct documentation + // TODO + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // String sourceObjectName + nodes.add( new LdcInsnNode( transpiler.getProperty( "boxClassName" ) ) ); + // String sourceObjectType + nodes.add( new LdcInsnNode( "class" ) ); + + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESPECIAL, + Type.getInternalName( ClassVariablesScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ), + false + ) + ); + + return nodes; + } + + private static void defineLookupPrivateMethod( Transpiler transpiler, ClassNode classNode, Type thisType ) { + /** + * This code MUST be inside the class to allow for the lookupPrivate method to work + * This proxy is called from the dynamic interop service when calling a super method + * while using java extends, and it will return the method handle for the corresponding + * method in the super class. + */ + // public MethodHandle lookupPrivateMethod( Method method ) { + // try { + // return MethodHandles.lookup().findSpecial( + // method.getDeclaringClass(), + // method.getName(), + // MethodType.methodType(method.getReturnType(), method.getParameterTypes()), + // this.getClass() + // ); + // } catch (NoSuchMethodException | IllegalAccessException e) { + // throw new BoxRuntimeException( "Error getting Java super class method " + method.getName(), e ); + // } + // } + Type returnType = Type.getType( MethodHandle.class ); + MethodVisitor methodVisitor = classNode.visitMethod( + Opcodes.ACC_PUBLIC, + "lookupPrivateMethod", + Type.getMethodDescriptor( returnType, Type.getType( Method.class ) ), + null, + null ); + methodVisitor.visitCode(); + + Label tryStart = new Label(); + + methodVisitor.visitLabel( tryStart ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getType( MethodHandles.class ).getInternalName(), + "lookup", + Type.getMethodDescriptor( Type.getType( Lookup.class ) ), + false + ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getType( Member.class ).getInternalName(), + "getDeclaringClass", + Type.getMethodDescriptor( CLASS_TYPE ), + true + ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getType( Member.class ).getInternalName(), + "getName", + Type.getMethodDescriptor( Type.getType( String.class ) ), + true + ); + + // start MethodType.methodType(method.getReturnType(), method.getParameterTypes()), + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + Type.getType( Method.class ).getInternalName(), + "getReturnType", + Type.getMethodDescriptor( CLASS_TYPE ), + false + ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + Type.getType( Method.class ).getInternalName(), + "getParameterTypes", + Type.getMethodDescriptor( CLASS_ARRAY_TYPE ), + false + ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getType( MethodType.class ).getInternalName(), + "methodType", + Type.getMethodDescriptor( Type.getType( MethodType.class ), CLASS_TYPE, CLASS_ARRAY_TYPE ), + false + ); + // end MethodType.methodType(method.getReturnType(), method.getParameterTypes()), + + methodVisitor.visitLdcInsn( thisType ); + // methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, thisType.getInternalName(), "class", CLASS_TYPE.getDescriptor() ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + Type.getType( Lookup.class ).getInternalName(), + "findSpecial", + Type.getMethodDescriptor( Type.getType( MethodHandle.class ), CLASS_TYPE, Type.getType( String.class ), Type.getType( MethodType.class ), + CLASS_TYPE ), + false + ); + + methodVisitor.visitInsn( Opcodes.ARETURN ); + + Label tryEnd = new Label(); + Label handler = new Label(); + + methodVisitor.visitLabel( tryEnd ); + methodVisitor.visitLabel( handler ); + + methodVisitor.visitVarInsn( Opcodes.ASTORE, 2 ); + + methodVisitor.visitTryCatchBlock( tryStart, tryEnd, handler, Type.getInternalName( NoSuchMethodException.class ) ); + methodVisitor.visitTryCatchBlock( tryStart, tryEnd, handler, Type.getInternalName( IllegalAccessException.class ) ); + + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( BoxRuntimeException.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + + methodVisitor.visitLdcInsn( "Error getting Java super class method " ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getType( Member.class ).getInternalName(), + "getName", + Type.getMethodDescriptor( Type.getType( String.class ) ), + true + ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( String.class ), + "concat", + Type.getMethodDescriptor( Type.getType( String.class ), Type.getType( String.class ) ), + false + ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); + + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( BoxRuntimeException.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( String.class ), Type.getType( Throwable.class ) ), + false ); + + methodVisitor.visitInsn( Opcodes.ATHROW ); + + methodVisitor.visitMaxs( 0, 0 ); + + methodVisitor.visitEnd(); + } + + private static void defineLookupPrivateField( Transpiler transpiler, ClassNode classNode, Type thisType ) { + /** + * Same as above + */ + // public MethodHandle lookupPrivateField( Field field ) { + // try { + // return MethodHandles.lookup().unreflectGetter( field ); + // } catch ( IllegalAccessException e) { + // throw new BoxRuntimeException( "Error getting Java super class field " + field.getName(), e ); + // } + // } + Type returnType = Type.getType( MethodHandle.class ); + MethodVisitor methodVisitor = classNode.visitMethod( + Opcodes.ACC_PUBLIC, + "lookupPrivateField", + Type.getMethodDescriptor( returnType, Type.getType( Field.class ) ), + null, + null ); + methodVisitor.visitCode(); + + Label tryStart = new Label(); + + methodVisitor.visitLabel( tryStart ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getType( MethodHandles.class ).getInternalName(), + "lookup", + Type.getMethodDescriptor( Type.getType( Lookup.class ) ), + false + ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + Type.getType( Lookup.class ).getInternalName(), + "unreflectGetter", + Type.getMethodDescriptor( Type.getType( MethodHandle.class ), Type.getType( Field.class ) ), + false + ); + + methodVisitor.visitInsn( Opcodes.ARETURN ); + + Label tryEnd = new Label(); + Label handler = new Label(); + + methodVisitor.visitLabel( tryEnd ); + methodVisitor.visitLabel( handler ); + + methodVisitor.visitVarInsn( Opcodes.ASTORE, 2 ); + + methodVisitor.visitTryCatchBlock( tryStart, tryEnd, handler, Type.getInternalName( NoSuchMethodException.class ) ); + methodVisitor.visitTryCatchBlock( tryStart, tryEnd, handler, Type.getInternalName( IllegalAccessException.class ) ); + + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( BoxRuntimeException.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + + methodVisitor.visitLdcInsn( "Error getting Java super class field " ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getType( Field.class ).getInternalName(), + "getName", + Type.getMethodDescriptor( Type.getType( String.class ) ), + true + ); + + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( String.class ), + "concat", + Type.getMethodDescriptor( Type.getType( String.class ), Type.getType( String.class ) ), + false + ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); + + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( BoxRuntimeException.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( String.class ), Type.getType( Throwable.class ) ), + false ); + + methodVisitor.visitInsn( Opcodes.ATHROW ); + + methodVisitor.visitMaxs( 0, 0 ); + + methodVisitor.visitEnd(); + } + +} From 06e1a83b7b80c7a204c4540ece11c902bdc5aba9 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Mon, 9 Sep 2024 14:37:54 -0500 Subject: [PATCH 09/71] Add support for Object shorthand when overriding a java function --- .../transformer/statement/BoxClassTransformer.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index b697fbc75..6df62308f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -168,18 +168,20 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th fqn = boxReturnType.getFqn(); } } + String returnTypeString = ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ); + if ( returnTypeString.equalsIgnoreCase( "Object" ) ) { + returnTypeString = "java.lang.Object"; + } // Type returnType = Type // .getType( "L" + ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ).replace( '.', '/' ) + ";" ); - - Type returnType = switch ( ( boxType.equals( BoxType.Fqn ) ? fqn : boxType.getSymbol() ) ) { + // TODO this needs to be improved substantially + Type returnType = switch ( returnTypeString ) { case "void" -> Type.VOID_TYPE; case "long" -> Type .getType( Long.class ); default -> Type.getType( "L" - + ( boxType - .equals( BoxType.Fqn ) ? fqn - : boxType.getSymbol() ) - .replace( '.', '/' ) + + returnTypeString.replace( '.', + '/' ) + ";" ); }; List parameters = func.getArgs(); From 837fd649776f3a82fb6dd9a3fbd4e87d6190dce6 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 10 Sep 2024 12:00:36 -0500 Subject: [PATCH 10/71] Add static initializer support --- .../compiler/asmboxpiler/AsmTranspiler.java | 3 + .../compiler/asmboxpiler/Transpiler.java | 10 ++ .../expression/BoxAssignmentTransformer.java | 93 ++++++++++++++++++- .../statement/BoxClassTransformer.java | 19 +++- .../BoxStaticInitializerTransformer.java | 42 +++++++++ 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxStaticInitializerTransformer.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 5395139ad..340f1da27 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -63,6 +63,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxFunctionDeclarationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxIfElseTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxRethrowTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxStaticInitializerTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxThrowTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxTryTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxWhileTransformer; @@ -70,6 +71,7 @@ import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxScript; +import ortus.boxlang.compiler.ast.BoxStaticInitializer; import ortus.boxlang.compiler.ast.Source; import ortus.boxlang.compiler.ast.SourceFile; import ortus.boxlang.compiler.ast.expression.BoxArgument; @@ -191,6 +193,7 @@ public AsmTranspiler() { registry.put( BoxForIndex.class, new BoxForIndexTransformer( this ) ); registry.put( BoxClosure.class, new BoxClosureTransformer( this ) ); registry.put( BoxComponent.class, new BoxComponentTransformer( this ) ); + registry.put( BoxStaticInitializer.class, new BoxStaticInitializerTransformer( this ) ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 20a77fea1..f5dab36e9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -23,6 +23,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.BoxStaticInitializer; import ortus.boxlang.compiler.ast.expression.BoxIdentifier; import ortus.boxlang.compiler.ast.expression.BoxIntegerLiteral; import ortus.boxlang.compiler.ast.expression.BoxStringLiteral; @@ -45,6 +46,7 @@ public abstract class Transpiler implements ITranspiler { private Map continues = new LinkedHashMap<>(); private List imports = new ArrayList<>(); private List methodContextTrackers = new ArrayList(); + private List staticInitializers = new ArrayList<>(); /** * Set a property @@ -124,6 +126,14 @@ public void clearTryCatchStack() { tryCatchBlockNodes = new ArrayList(); } + public void addBoxStaticInitializer( BoxStaticInitializer staticInitializer ) { + this.staticInitializers.add( staticInitializer ); + } + + public List getBoxStaticInitializers() { + return this.staticInitializers; + } + public Map getAuxiliary() { return auxiliaries; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java index 0598d9a07..27b050c71 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; @@ -86,11 +87,22 @@ public List transform( BoxNode node, TransformerContext contex public List transformEquals( BoxExpression left, List jRight, BoxAssignmentOperator op, List modifiers ) throws IllegalStateException { - boolean hasVar = hasVar( modifiers ); - Optional tracker = transpiler.getCurrentMethodContextTracker(); + boolean hasVar = hasVar( modifiers ); + boolean hasStatic = hasStatic( modifiers ); + boolean hasFinal = hasFinal( modifiers ); + String mustBeScopeName = null; + Optional tracker = transpiler.getCurrentMethodContextTracker(); // "#arguments.scope#.#arguments.propertyName#" = arguments.propertyValue; if ( left instanceof BoxStringInterpolation || left instanceof BoxStringLiteral ) { + if ( hasVar ) { + throw new ExpressionException( "You cannot use the [var] keyword with a quoted string on the left hand side of your assignment", + left.getPosition(), left.getSourceText() ); + } + if ( hasStatic ) { + throw new ExpressionException( "You cannot use the [static] keyword with a quoted string on the left hand side of your assignment", + left.getPosition(), left.getSourceText() ); + } /* * ExpressionInterpreter.setVariable( * ${contextName}, @@ -139,8 +151,13 @@ public List transformEquals( BoxExpression left, List transformEquals( BoxExpression left, List nodes = new ArrayList<>(); if ( furthestLeft instanceof BoxIdentifier id ) { if ( transpiler.matchesImport( id.getName() ) && transpiler.getProperty( "sourceType" ).toLowerCase().startsWith( "box" ) ) { @@ -167,15 +205,32 @@ public List transformEquals( BoxExpression left, List nodes.addAll( t.loadCurrentContext() ) ); + tracker.ifPresent( t -> { + nodes.addAll( t.loadCurrentContext() ); + } ); + + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, Type.getInternalName( Boolean.class ), hasFinal ? "TRUE" : "FALSE", + Type.getDescriptor( Boolean.class ) ) ); + nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( Boolean.class ), + "booleanValue", + Type.getMethodDescriptor( Type.getType( boolean.class ) ), + false ) ); + + if ( mustBeScopeName != null ) { + nodes.addAll( transpiler.createKey( mustBeScopeName ) ); + } else { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } tracker.ifPresent( t -> nodes.addAll( t.loadCurrentContext() ) ); - List keyNode = transpiler.createKey( id.getName() ); - nodes.addAll( keyNode ); + nodes.addAll( transpiler.createKey( id.getName() ) ); tracker.ifPresent( t -> nodes.addAll( t.loadCurrentContext() ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), @@ -198,6 +253,8 @@ public List transformEquals( BoxExpression left, List transformEquals( BoxExpression left, List nodes.addAll( t.loadCurrentContext() ) ); + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, Type.getInternalName( Boolean.class ), hasFinal ? "TRUE" : "FALSE", + Type.getDescriptor( Boolean.class ) ) ); + nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( Boolean.class ), + "booleanValue", + Type.getMethodDescriptor( Type.getType( boolean.class ) ), + false ) ); + + if ( mustBeScopeName != null ) { + nodes.addAll( transpiler.createKey( mustBeScopeName ) ); + } else { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } + nodes.addAll( transpiler.transform( furthestLeft, TransformerContext.NONE, ReturnValueContext.VALUE ) ); nodes.addAll( jRight ); @@ -228,6 +301,8 @@ public List transformEquals( BoxExpression left, List modifiers ) { return modifiers.stream().anyMatch( it -> it == BoxAssignmentModifier.VAR ); } + private boolean hasStatic( List modifiers ) { + return modifiers.stream().anyMatch( it -> it == BoxAssignmentModifier.STATIC ); + } + + private boolean hasFinal( List modifiers ) { + return modifiers.stream().anyMatch( it -> it == BoxAssignmentModifier.FINAL ); + } + private Class getMethodCallTemplate( BoxAssignment assignment ) { BoxAssignmentOperator operator = assignment.getOp(); return switch ( operator ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 6df62308f..3781158da 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -90,6 +90,7 @@ public class BoxClassTransformer { private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; + @SuppressWarnings( "unchecked" ) public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) throws BoxRuntimeException { Source source = boxClass.getPosition().getSource(); String sourceType = transpiler.getProperty( "sourceType" ); @@ -483,7 +484,23 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th ); AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, true, - List::of + () -> { + return ( List ) transpiler.getBoxStaticInitializers() + .stream() + .map( ( staticInitializer ) -> { + if ( staticInitializer == null || staticInitializer.getBody().size() == 0 ) { + return List.of(); + } + + return staticInitializer.getBody() + .stream() + .map( statement -> transpiler.transform( statement, TransformerContext.NONE ) ) + .flatMap( nodes -> nodes.stream() ) + .toList(); + } ) + .flatMap( s -> s.stream() ) + .toList(); + } ); AsmHelper.complete( classNode, type, methodVisitor -> { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxStaticInitializerTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxStaticInitializerTransformer.java new file mode 100644 index 000000000..c137a057c --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxStaticInitializerTransformer.java @@ -0,0 +1,42 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.statement; + +import java.util.List; + +import org.objectweb.asm.tree.AbstractInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.BoxStaticInitializer; + +public class BoxStaticInitializerTransformer extends AbstractTransformer { + + public BoxStaticInitializerTransformer( Transpiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { + BoxStaticInitializer staticInitializer = ( BoxStaticInitializer ) node; + + transpiler.addBoxStaticInitializer( staticInitializer ); + + return List.of(); + } +} From b96023708f8b6db2ededb3ab35dd4b3e481b97a5 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 10 Sep 2024 21:57:39 -0500 Subject: [PATCH 11/71] Finalize static support --- .../compiler/asmboxpiler/AsmHelper.java | 83 +++++++++++ .../compiler/asmboxpiler/AsmTranspiler.java | 10 +- .../BoxStaticAccessTransformer.java | 132 ++++++++++++++++++ .../BoxStaticMethodInvocationTransformer.java | 91 ++++++++++++ .../statement/BoxClassTransformer.java | 32 +++-- 5 files changed, 338 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 52617465c..88d3fa04c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -18,6 +18,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; @@ -32,6 +33,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.dynamic.Referencer; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.runnables.BoxClassSupport; @@ -43,6 +45,87 @@ public class AsmHelper { + public static List callReferencerGetAndInvoke( + Transpiler transpiler, + List args, + String name, + TransformerContext context, + boolean safe ) { + List nodes = new ArrayList(); + + nodes.addAll( transpiler.createKey( name ) ); + + // handle positional args + if ( args.size() == 0 || args.get( 0 ).getName() == null ) { + nodes.addAll( + AsmHelper.array( Type.getType( Object.class ), args, + ( argument, i ) -> transpiler.transform( args.get( i ), context, ReturnValueContext.VALUE ) ) + ); + + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, Type.getInternalName( Boolean.class ), safe ? "TRUE" : "FALSE", + Type.getDescriptor( Boolean.class ) ) ); + + nodes.add( new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Referencer.class ), + "getAndInvoke", + Type.getMethodDescriptor( Type.getType( Object.class ), + Type.getType( IBoxContext.class ), + Type.getType( Object.class ), + Type.getType( Key.class ), + Type.getType( Object[].class ), + Type.getType( Boolean.class ) + ), + false ) + ); + + return nodes; + } + + List> keyValues = args.stream() + .map( arg -> { + List> kv = List.of( + transpiler.createKey( arg.getName() ), + transpiler.transform( arg, context, ReturnValueContext.VALUE ) + ); + + return kv; + } ) + .flatMap( x -> x.stream() ) + .collect( Collectors.toList() ); + + nodes.addAll( AsmHelper.array( Type.getType( Object.class ), keyValues ) ); + + nodes.add( + new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( Struct.class ), + "of", + Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), + false + ) + ); + + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, Type.getInternalName( Boolean.class ), safe ? "TRUE" : "FALSE", + Type.getDescriptor( Boolean.class ) ) ); + + nodes.add( new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Referencer.class ), + "getAndInvoke", + Type.getMethodDescriptor( Type.getType( Object.class ), + Type.getType( IBoxContext.class ), + Type.getType( Object.class ), + Type.getType( Key.class ), + Type.getType( Map.class ), + Type.getType( Boolean.class ) + ), + false ) + ); + + return nodes; + + } + public static List callDynamicObjectInvokeConstructor( Transpiler transpiler, List args, TransformerContext context ) { List nodes = new ArrayList(); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 340f1da27..1c1763bf2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -46,6 +46,8 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxReturnTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxScopeTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxStatementBlockTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxStaticAccessTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxStaticMethodInvocationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxStringConcatTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxStringInterpolationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxStringLiteralTransformer; @@ -94,6 +96,8 @@ import ortus.boxlang.compiler.ast.expression.BoxNull; import ortus.boxlang.compiler.ast.expression.BoxParenthesis; import ortus.boxlang.compiler.ast.expression.BoxScope; +import ortus.boxlang.compiler.ast.expression.BoxStaticAccess; +import ortus.boxlang.compiler.ast.expression.BoxStaticMethodInvocation; import ortus.boxlang.compiler.ast.expression.BoxStringConcat; import ortus.boxlang.compiler.ast.expression.BoxStringInterpolation; import ortus.boxlang.compiler.ast.expression.BoxStringLiteral; @@ -194,11 +198,15 @@ public AsmTranspiler() { registry.put( BoxClosure.class, new BoxClosureTransformer( this ) ); registry.put( BoxComponent.class, new BoxComponentTransformer( this ) ); registry.put( BoxStaticInitializer.class, new BoxStaticInitializerTransformer( this ) ); + registry.put( BoxStaticAccess.class, new BoxStaticAccessTransformer( this ) ); + registry.put( BoxStaticMethodInvocation.class, new BoxStaticMethodInvocationTransformer( this ) ); } @Override public ClassNode transpile( BoxScript boxScript ) throws BoxRuntimeException { - Type type = Type.getType( "L" + getProperty( "packageName" ).replace( '.', '/' ) + "/" + getProperty( "classname" ) + ";" ); + Type type = Type.getType( "L" + getProperty( "packageName" ).replace( '.', '/' ) + "/" + getProperty( "classname" ) + ";" ); + setProperty( "classType", type.getDescriptor() ); + setProperty( "classTypeInternal", type.getInternalName() ); ClassNode classNode = new ClassNode(); String mappingName = getProperty( "mappingName" ); String mappingPath = getProperty( "mappingPath" ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java new file mode 100644 index 000000000..2b00c8254 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java @@ -0,0 +1,132 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +package ortus.boxlang.compiler.asmboxpiler.transformer.expression; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.expression.BoxFQN; +import ortus.boxlang.compiler.ast.expression.BoxIdentifier; +import ortus.boxlang.compiler.ast.expression.BoxIntegerLiteral; +import ortus.boxlang.compiler.ast.expression.BoxStaticAccess; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.dynamic.Referencer; +import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.runnables.BoxClassSupport; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.exceptions.ExpressionException; + +public class BoxStaticAccessTransformer extends AbstractTransformer { + + public BoxStaticAccessTransformer( Transpiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { + BoxStaticAccess objectAccess = ( BoxStaticAccess ) node; + List nodes = new ArrayList<>(); + Boolean safe = objectAccess.isSafe() || context == TransformerContext.SAFE; + + transpiler.getCurrentMethodContextTracker().ifPresent( t -> nodes.addAll( t.loadCurrentContext() ) ); + + // does this go here or above? + + // get the imports for the current class + transpiler.getCurrentMethodContextTracker().ifPresent( t -> nodes.addAll( t.loadCurrentContext() ) ); + // nodes.add( new VarInsnNode( Opcodes.ALOAD, 0 ) ); + // push scope reference onto the stack + if ( objectAccess.getContext() instanceof BoxFQN fqn ) { + nodes.add( new LdcInsnNode( fqn.getValue() ) ); + } else if ( objectAccess.getContext() instanceof BoxIdentifier id ) { + nodes.addAll( transpiler.transform( id, context, ReturnValueContext.VALUE ) ); + } else { + throw new ExpressionException( "Unexpected base token in static access.", objectAccess.getContext() ); + } + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, transpiler.getProperty( "classTypeInternal" ), "imports", Type.getDescriptor( List.class ) ) ); + + // invoke BoxClassSupport.ensureClass(${contextName},${scopeReference},imports), + nodes.add( new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( BoxClassSupport.class ), + "ensureClass", + Type.getMethodDescriptor( Type.getType( DynamicObject.class ), + Type.getType( IBoxContext.class ), + Type.getType( Object.class ), + Type.getType( List.class ) + ), + false ) + ); + + // Node accessKey; + // objectAccess just uses the string directly, array access allows any expression + if ( objectAccess.getAccess() instanceof BoxIdentifier id ) { + nodes.addAll( transpiler.createKey( id.getName() ) ); + } else if ( objectAccess.getAccess() instanceof BoxIntegerLiteral il ) { + nodes.addAll( transpiler.createKey( il ) ); + } else { + throw new ExpressionException( "Unsupported access type: " + objectAccess.getAccess().getClass().getName(), objectAccess.getAccess() ); + } + + // Expression jContext; + + // // "scope" here isn't a BoxLang proper scope, it's just whatever Java source represents the context of the access expression + // values.put( "scopeReference", jContext.toString() ); + + // String template = """ + // Referencer.get( + // ${contextName}, + // BoxClassSupport.ensureClass(${contextName},${scopeReference},imports), + // ${accessKey}, + // ${safe} + // ) + // """; + + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, Type.getInternalName( Boolean.class ), safe ? "TRUE" : "FALSE", + Type.getDescriptor( Boolean.class ) ) ); + + nodes.add( new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Referencer.class ), + "get", + Type.getMethodDescriptor( Type.getType( Object.class ), + Type.getType( IBoxContext.class ), + Type.getType( Object.class ), + Type.getType( Key.class ), + Type.getType( Boolean.class ) + ), + false ) + ); + + // Node javaExpr = parseExpression( template, values ); + // logger.trace( node.getSourceText() + " -> " + javaExpr ); + // addIndex( javaExpr, node ); + return nodes; + } + +} diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java new file mode 100644 index 000000000..6d40a3dd2 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java @@ -0,0 +1,91 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.expression; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxExpression; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.expression.BoxFQN; +import ortus.boxlang.compiler.ast.expression.BoxIdentifier; +import ortus.boxlang.compiler.ast.expression.BoxStaticMethodInvocation; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.runnables.BoxClassSupport; +import ortus.boxlang.runtime.types.exceptions.ExpressionException; + +public class BoxStaticMethodInvocationTransformer extends AbstractTransformer { + + public BoxStaticMethodInvocationTransformer( Transpiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) throws IllegalStateException { + BoxStaticMethodInvocation invocation = ( BoxStaticMethodInvocation ) node; + List nodes = new ArrayList<>(); + BoxExpression baseObject = invocation.getObj(); + // Expression expr; + + transpiler.getCurrentMethodContextTracker().ifPresent( t -> nodes.addAll( t.loadCurrentContext() ) ); + + nodes.add( new InsnNode( Opcodes.DUP ) ); + + if ( baseObject instanceof BoxFQN fqn ) { + nodes.add( new LdcInsnNode( fqn.getValue() ) ); + } else if ( baseObject instanceof BoxIdentifier id ) { + nodes.addAll( transpiler.transform( id, context, ReturnValueContext.VALUE ) ); + } else { + throw new ExpressionException( "Unexpected base token in static method access.", baseObject ); + } + + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, transpiler.getProperty( "classTypeInternal" ), "imports", + Type.getDescriptor( List.class ) ) ); + + // invoke BoxClassSupport.ensureClass(${contextName},${scopeReference},imports), + nodes.add( new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( BoxClassSupport.class ), + "ensureClass", + Type.getMethodDescriptor( Type.getType( DynamicObject.class ), + Type.getType( IBoxContext.class ), + Type.getType( Object.class ), + Type.getType( List.class ) + ), + false ) + ); + + nodes.addAll( AsmHelper.callReferencerGetAndInvoke( transpiler, invocation.getArguments(), invocation.getName().toString(), context, false ) ); + + return nodes; + } +} diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 3781158da..6a232f6d7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.objectweb.asm.Label; @@ -55,6 +56,7 @@ import ortus.boxlang.compiler.ast.statement.BoxArgumentDeclaration; import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; import ortus.boxlang.compiler.ast.statement.BoxImport; +import ortus.boxlang.compiler.ast.statement.BoxMethodDeclarationModifier; import ortus.boxlang.compiler.ast.statement.BoxReturnType; import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.compiler.parser.BoxSourceType; @@ -107,14 +109,16 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th boxClassName = rawBoxClassName; } transpiler.setProperty( "boxClassName", boxClassName ); - String mappingName = transpiler.getProperty( "mappingName" ); - String mappingPath = transpiler.getProperty( "mappingPath" ); - String relativePath = transpiler.getProperty( "relativePath" ); + String mappingName = transpiler.getProperty( "mappingName" ); + String mappingPath = transpiler.getProperty( "mappingPath" ); + String relativePath = transpiler.getProperty( "relativePath" ); - Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + "/" + transpiler.getProperty( "classname" ) + ";" ); + transpiler.setProperty( "classType", type.getDescriptor() ); + transpiler.setProperty( "classTypeInternal", type.getInternalName() ); - List interfaces = new ArrayList<>(); + List interfaces = new ArrayList<>(); interfaces.add( Type.getType( IClassRunnable.class ) ); interfaces.add( Type.getType( IReferenceable.class ) ); interfaces.add( Type.getType( IType.class ) ); @@ -485,21 +489,31 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, true, () -> { - return ( List ) transpiler.getBoxStaticInitializers() + List staticNodes = ( List ) transpiler.getBoxStaticInitializers() .stream() .map( ( staticInitializer ) -> { if ( staticInitializer == null || staticInitializer.getBody().size() == 0 ) { - return List.of(); + return new ArrayList(); } return staticInitializer.getBody() .stream() .map( statement -> transpiler.transform( statement, TransformerContext.NONE ) ) .flatMap( nodes -> nodes.stream() ) - .toList(); + .collect( Collectors.toList() ); } ) .flatMap( s -> s.stream() ) - .toList(); + .collect( Collectors.toList() ); + + boxClass.getDescendantsOfType( BoxFunctionDeclaration.class, ( expr ) -> { + BoxFunctionDeclaration func = ( BoxFunctionDeclaration ) expr; + + return func.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ); + } ).forEach( func -> { + staticNodes.addAll( transpiler.transform( func, TransformerContext.NONE ) ); + } ); + + return staticNodes; } ); From a07008f0db80fc5d1b3f6afd8bb7da056158a0d6 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 10 Sep 2024 21:57:55 -0500 Subject: [PATCH 12/71] Allow static functions --- .../BoxFunctionDeclarationTransformer.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 04bb239fc..b2f926379 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -14,6 +14,7 @@ */ package ortus.boxlang.compiler.asmboxpiler.transformer.statement; +import java.util.ArrayList; import java.util.List; import org.objectweb.asm.Opcodes; @@ -21,7 +22,6 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; @@ -176,18 +176,24 @@ public List transform( BoxNode node, TransformerContext contex Type.getDescriptor( IStruct.class ) ); } ); - return List.of( - new VarInsnNode( Opcodes.ALOAD, 1 ), + List nodes = new ArrayList(); + + nodes.addAll( transpiler.getCurrentMethodContextTracker().get().loadCurrentContext() ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, type.getInternalName(), "getInstance", Type.getMethodDescriptor( type ), - false ), + false ) + ); + nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "registerUDF", Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( UDF.class ) ), true ) ); + + return nodes; } } From f7e8f1d9d0f1df177f7eb6d5e57277571c033764 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 10 Sep 2024 22:31:27 -0500 Subject: [PATCH 13/71] Refactor asm string transformer to support huge strings --- .../BoxStringLiteralTransformer.java | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java index 7dcf040a0..3cae1e7df 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java @@ -14,11 +14,16 @@ */ package ortus.boxlang.compiler.asmboxpiler.transformer.expression; +import java.util.ArrayList; import java.util.List; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -28,13 +33,64 @@ public class BoxStringLiteralTransformer extends AbstractTransformer { + private static final int MAX_LITERAL_LENGTH = 30000; // 64KB limit + public BoxStringLiteralTransformer( Transpiler transpiler ) { super( transpiler ); } @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxStringLiteral literal = ( BoxStringLiteral ) node; - return List.of( new LdcInsnNode( literal.getValue() ) ); + BoxStringLiteral literal = ( BoxStringLiteral ) node; + + String value = literal.getValue(); + + if ( value.length() < MAX_LITERAL_LENGTH ) { + return List.of( new LdcInsnNode( literal.getValue() ) ); + + } + + List nodes = new ArrayList(); + List parts = splitStringIntoParts( value ); + + nodes.add( new LdcInsnNode( "" ) ); + nodes.addAll( + AsmHelper.array( Type.getType( String.class ), parts.stream().map( s -> { + List x = List.of( new LdcInsnNode( s ) ); + + return x; + } + ).toList() ) ); + + nodes.add( new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( String.class ), + "join", + Type.getMethodDescriptor( Type.getType( String.class ), + Type.getType( CharSequence.class ), + Type.getType( CharSequence[].class ) + ), + false ) + ); + + return nodes; + } + + /** + * Split a large string into parts + * + * @param str The input string. + * + * @return A list of StringLiteralExpr parts. + **/ + private List splitStringIntoParts( String str ) { + List parts = new ArrayList<>(); + int length = str.length(); + for ( int i = 0; i < length; i += MAX_LITERAL_LENGTH ) { + int end = Math.min( length, i + MAX_LITERAL_LENGTH ); + String part = str.substring( i, end ); + parts.add( part ); + } + return parts; } } From 8934a5415c25a45c8a8bbc09378fd04dd35e936e Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 10 Sep 2024 22:38:34 -0500 Subject: [PATCH 14/71] Add !== support to asm transformers --- .../BoxComparisonOperationTransformer.java | 32 ++++++++++++++--- .../TestCases/asm/phase1/OperatorsTest.java | 35 +++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java index 5bcaf8093..a5e13c947 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java @@ -17,10 +17,14 @@ */ package ortus.boxlang.compiler.asmboxpiler.transformer.expression; +import java.util.ArrayList; +import java.util.List; + import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; + import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -28,12 +32,16 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxComparisonOperation; import ortus.boxlang.compiler.ast.expression.BoxComparisonOperator; -import ortus.boxlang.runtime.operators.*; +import ortus.boxlang.runtime.operators.EqualsEquals; +import ortus.boxlang.runtime.operators.EqualsEqualsEquals; +import ortus.boxlang.runtime.operators.GreaterThan; +import ortus.boxlang.runtime.operators.GreaterThanEqual; +import ortus.boxlang.runtime.operators.LessThan; +import ortus.boxlang.runtime.operators.LessThanEqual; +import ortus.boxlang.runtime.operators.Not; +import ortus.boxlang.runtime.operators.NotEqualsEquals; import ortus.boxlang.runtime.types.exceptions.ExpressionException; -import java.util.ArrayList; -import java.util.List; - public class BoxComparisonOperationTransformer extends AbstractTransformer { public BoxComparisonOperationTransformer( Transpiler transpiler ) { @@ -45,6 +53,7 @@ public List transform( BoxNode node, TransformerContext contex BoxComparisonOperation operation = ( BoxComparisonOperation ) node; List left = transpiler.transform( operation.getLeft(), TransformerContext.NONE, ReturnValueContext.VALUE ); List right = transpiler.transform( operation.getRight(), TransformerContext.NONE, ReturnValueContext.VALUE ); + boolean negated = false; Class dispatcher; if ( operation.getOperator() == BoxComparisonOperator.Equal ) { @@ -53,6 +62,9 @@ public List transform( BoxNode node, TransformerContext contex dispatcher = NotEqualsEquals.class; } else if ( operation.getOperator() == BoxComparisonOperator.TEqual ) { dispatcher = EqualsEqualsEquals.class; + } else if ( operation.getOperator() == BoxComparisonOperator.TNotEqual ) { + dispatcher = EqualsEqualsEquals.class; + negated = true; } else if ( operation.getOperator() == BoxComparisonOperator.GreaterThan ) { dispatcher = GreaterThan.class; } else if ( operation.getOperator() == BoxComparisonOperator.GreaterThanEquals ) { @@ -70,6 +82,18 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( right ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( dispatcher ), "invoke", Type.getMethodDescriptor( Type.getType( Boolean.class ), Type.getType( Object.class ), Type.getType( Object.class ) ), false ) ); + + if ( negated ) { + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Not.class ), + "invoke", + Type.getMethodDescriptor( Type.getType( Boolean.class ), Type.getType( Object.class ) ), + false + ) + ); + } return nodes; } diff --git a/src/test/java/TestCases/asm/phase1/OperatorsTest.java b/src/test/java/TestCases/asm/phase1/OperatorsTest.java index 065b6069d..5b0bacd61 100644 --- a/src/test/java/TestCases/asm/phase1/OperatorsTest.java +++ b/src/test/java/TestCases/asm/phase1/OperatorsTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; @@ -929,4 +930,38 @@ public void testIMPOperator() { } + @DisplayName( "comparison strict not equality" ) + @Test + public void testComparisonStrictNotEquality() { + + Object result = instance.executeStatement( "5!==5", context ); + assertThat( result ).isEqualTo( false ); + + result = instance.executeStatement( "'5'!==5", context ); + assertThat( result ).isEqualTo( true ); + + result = instance.executeStatement( "'brad'!=='brad'", context ); + assertThat( result ).isEqualTo( false ); + + result = instance.executeStatement( "'brad'!==5", context ); + assertThat( result ).isEqualTo( true ); + } + + @DisplayName( "comparison strict not equality CF" ) + @Test + public void testComparisonStrictNotEqualityCF() { + + Object result = instance.executeStatement( "5!==5", context, BoxSourceType.CFSCRIPT ); + assertThat( result ).isEqualTo( false ); + + result = instance.executeStatement( "'5'!==5", context, BoxSourceType.CFSCRIPT ); + assertThat( result ).isEqualTo( true ); + + result = instance.executeStatement( "'brad'!=='brad'", context, BoxSourceType.CFSCRIPT ); + assertThat( result ).isEqualTo( false ); + + result = instance.executeStatement( "'brad'!==5", context, BoxSourceType.CFSCRIPT ); + assertThat( result ).isEqualTo( true ); + } + } From f8f2c6a01d9d02a443047547faf6bcd24c566670 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 17 Sep 2024 09:50:59 -0500 Subject: [PATCH 15/71] Fix abstract class test --- .../statement/BoxClassTransformer.java | 43 ++++++++++++++++--- .../BoxFunctionDeclarationTransformer.java | 13 +++++- .../boxlang/runtime/types/util/MapHelper.java | 18 ++++++++ .../java/TestCases/asm/phase3/ClassTest.java | 2 +- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 6a232f6d7..e64d73144 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -649,11 +649,11 @@ private static List generateMapOfAbstractMethodNames( Transpil List nodes = new ArrayList(); - nodes.addAll( AsmHelper.array( Type.getType( Key.class ), methodKeyLists ) ); + nodes.addAll( AsmHelper.array( Type.getType( Object.class ), methodKeyLists ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( MapHelper.class ), - "LinkedHashMapOfProperties", + "LinkedHashMapOfAny", Type.getMethodDescriptor( Type.getType( Map.class ), Type.getType( Object[].class ) ), false ) ); @@ -712,13 +712,23 @@ private static List createAbstractFunction( Transpiler transpi .toList(); nodes.addAll( AsmHelper.array( Type.getType( Argument.class ), argList ) ); // String returnType - nodes.addAll( transpiler.transform( func.getType(), TransformerContext.NONE ) ); + if ( func.getType() != null ) { + nodes.addAll( transpiler.transform( func.getType(), TransformerContext.NONE ) ); + } else { + nodes.add( new LdcInsnNode( "any" ) ); + } + + String accessModifier = "PUBLIC"; + + if ( func.getAccessModifier() != null ) { + accessModifier = func.getAccessModifier().name().toUpperCase(); + } // Access access nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, - Type.getDescriptor( Function.Access.class ), - func.getAccessModifier().name().toUpperCase(), + Type.getInternalName( Function.Access.class ), + accessModifier, Type.getDescriptor( Function.Access.class ) ) ); @@ -742,13 +752,32 @@ private static List createAbstractFunction( Transpiler transpi nodes.add( new MethodInsnNode( Opcodes.INVOKESPECIAL, - Type.getInternalName( ClassVariablesScope.class ), + Type.getInternalName( AbstractFunction.class ), "", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IClassRunnable.class ) ), + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getType( Key.class ), + Type.getType( Argument[].class ), + Type.getType( String.class ), + Type.getType( Function.Access.class ), + Type.getType( IStruct.class ), + Type.getType( IStruct.class ), + Type.getType( String.class ), + Type.getType( String.class ) + ), false ) ); + // Key name + // Argument[] arguments + // String returnType + // Access access + // IStruct annotations + // IStruct documentation + // String sourceObjectName + // String sourceObjectType + return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index b2f926379..ce18080b9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -135,8 +135,17 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, transpiler, true, - () -> function.getBody().stream().flatMap( statement -> transpiler.transform( statement, safe, ReturnValueContext.EMPTY ).stream() ) - .toList() ); + () -> { + + if ( function.getBody() == null ) { + return new ArrayList(); + } + + return function.getBody() + .stream() + .flatMap( statement -> transpiler.transform( statement, safe, ReturnValueContext.EMPTY ).stream() ) + .toList(); + } ); AsmHelper.complete( classNode, type, methodVisitor -> { transpiler.createKey( function.getName() ).forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); diff --git a/src/main/java/ortus/boxlang/runtime/types/util/MapHelper.java b/src/main/java/ortus/boxlang/runtime/types/util/MapHelper.java index 451d45d77..32bf79651 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/MapHelper.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/MapHelper.java @@ -27,6 +27,24 @@ public class MapHelper { + /** + * Create a LinkedHashMap from a list of values. The values must be in pairs, key, value, key, value, etc. + * + * @param values The values to create the struct from + * + * @return The struct + */ + public static Map LinkedHashMapOfAny( Object... values ) { + if ( values.length % 2 != 0 ) { + throw new BoxRuntimeException( "Invalid number of arguments. Must be an even number." ); + } + var map = new LinkedHashMap(); + for ( int i = 0; i < values.length; i += 2 ) { + map.put( ( Key ) values[ i ], ( Object ) values[ i + 1 ] ); + } + return map; + } + /** * Create a LinkedHashMap from a list of values. The values must be in pairs, key, value, key, value, etc. * diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java index fa16b3abb..a8d73352d 100644 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -1193,7 +1193,7 @@ public void testAbstractClass() { Throwable t = assertThrows( AbstractClassException.class, () -> instance.executeSource( """ clazz = new src.test.java.TestCases.phase3.AbstractClass(); - """, context ) ); + """, context ) ); assertThat( t.getMessage() ).contains( "Cannot instantiate an abstract class" ); instance.executeSource( From 43f5ec2fc4d557c519f35f68bead1626e844bca5 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 18 Sep 2024 09:36:40 -0500 Subject: [PATCH 16/71] Fix static access issue --- .../ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java | 3 +++ .../java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java | 4 ++-- .../transformer/expression/BoxStaticAccessTransformer.java | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 1c1763bf2..f3a2055f3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -65,6 +65,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxFunctionDeclarationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxIfElseTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxRethrowTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxScriptIslandTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxStaticInitializerTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxThrowTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxTryTransformer; @@ -120,6 +121,7 @@ import ortus.boxlang.compiler.ast.statement.BoxProperty; import ortus.boxlang.compiler.ast.statement.BoxRethrow; import ortus.boxlang.compiler.ast.statement.BoxReturn; +import ortus.boxlang.compiler.ast.statement.BoxScriptIsland; import ortus.boxlang.compiler.ast.statement.BoxStatementBlock; import ortus.boxlang.compiler.ast.statement.BoxSwitch; import ortus.boxlang.compiler.ast.statement.BoxThrow; @@ -200,6 +202,7 @@ public AsmTranspiler() { registry.put( BoxStaticInitializer.class, new BoxStaticInitializerTransformer( this ) ); registry.put( BoxStaticAccess.class, new BoxStaticAccessTransformer( this ) ); registry.put( BoxStaticMethodInvocation.class, new BoxStaticMethodInvocationTransformer( this ) ); + registry.put( BoxScriptIsland.class, new BoxScriptIslandTransformer( this ) ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index f5dab36e9..161058f02 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -215,7 +215,7 @@ public List transformAnnotations( List annotati if ( thisValue != null ) { // Literal values are transformed directly if ( thisValue.isLiteral() ) { - value = transform( thisValue, TransformerContext.NONE, ReturnValueContext.EMPTY ); + value = transform( thisValue, TransformerContext.NONE, ReturnValueContext.VALUE ); } // gonna try commenting this out // else if ( onlyLiteralValues ) { @@ -223,7 +223,7 @@ public List transformAnnotations( List annotati // value = List.of( new LdcInsnNode( "" ) ); // } else { - value = transform( thisValue, TransformerContext.NONE, ReturnValueContext.EMPTY ); + value = transform( thisValue, TransformerContext.NONE, ReturnValueContext.VALUE ); } } else if ( defaultTrue ) { // Annotations in tags with no value default to true string (CF compat) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java index 2b00c8254..a8bdc9a6e 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java @@ -64,7 +64,11 @@ public List transform( BoxNode node, TransformerContext contex if ( objectAccess.getContext() instanceof BoxFQN fqn ) { nodes.add( new LdcInsnNode( fqn.getValue() ) ); } else if ( objectAccess.getContext() instanceof BoxIdentifier id ) { - nodes.addAll( transpiler.transform( id, context, ReturnValueContext.VALUE ) ); + if ( transpiler.matchesImport( id.getName() ) && transpiler.getProperty( "sourceType" ).toLowerCase().startsWith( "box" ) ) { + nodes.addAll( transpiler.transform( id, context, ReturnValueContext.VALUE ) ); + } else { + nodes.add( new LdcInsnNode( id.getName() ) ); + } } else { throw new ExpressionException( "Unexpected base token in static access.", objectAccess.getContext() ); } From 89efd26816225633518392b7cd1ecb61b453fc76 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 18 Sep 2024 09:37:14 -0500 Subject: [PATCH 17/71] Fix static access issue --- .../statement/BoxBufferOutputTransformer.java | 3 +- .../statement/BoxScriptIslandTransformer.java | 50 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java index d051a7c9e..01d656087 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java @@ -47,7 +47,8 @@ public List transform( BoxNode node, TransformerContext contex List nodes = new ArrayList<>(); nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); - nodes.addAll( transpiler.transform( bufferOuput.getExpression(), TransformerContext.NONE, ReturnValueContext.EMPTY ) ); + nodes + .addAll( transpiler.transform( bufferOuput.getExpression(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "writeToBuffer", diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java new file mode 100644 index 000000000..39e4c62c5 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.statement; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.tree.AbstractInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.BoxStatement; +import ortus.boxlang.compiler.ast.statement.BoxScriptIsland; + +public class BoxScriptIslandTransformer extends AbstractTransformer { + + public BoxScriptIslandTransformer( Transpiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { + BoxScriptIsland scriptIsland = ( BoxScriptIsland ) node; + + List nodes = new ArrayList<>(); + for ( BoxStatement statement : scriptIsland.getStatements() ) { + nodes.addAll( transpiler.transform( statement, context, returnContext ) ); + } + + return nodes; + } +} From a8da50f4d1739ca70bcfee56d9e85fd2d980acef Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 19 Sep 2024 22:12:18 -0500 Subject: [PATCH 18/71] Many coldbox asm bugfixes --- .../compiler/asmboxpiler/AsmHelper.java | 74 +++++++++++++++++- .../compiler/asmboxpiler/AsmTranspiler.java | 3 + .../asmboxpiler/MethodContextTracker.java | 49 +++++++++++- .../compiler/asmboxpiler/Transpiler.java | 36 ++++----- .../BoxArgumentDeclarationTransformer.java | 76 ++++++++++++++++++- .../expression/BoxBreakTransformer.java | 13 +++- .../expression/BoxClosureTransformer.java | 5 +- .../expression/BoxContinueTransformer.java | 13 +++- .../BoxFunctionInvocationTransformer.java | 24 +++--- .../expression/BoxLambdaTransformer.java | 11 ++- .../BoxMethodInvocationTransformer.java | 51 ++++++------- .../BoxStringConcatTransformer.java | 2 +- .../expression/BoxSwitchTransformer.java | 9 ++- .../statement/BoxDoTransformer.java | 6 +- .../statement/BoxForInTransformer.java | 6 ++ .../statement/BoxForIndexTransformer.java | 8 +- .../BoxTemplateIslandTransformer.java | 50 ++++++++++++ .../statement/BoxTryTransformer.java | 14 +++- .../statement/BoxWhileTransformer.java | 8 +- 19 files changed, 364 insertions(+), 94 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 88d3fa04c..a0413cf09 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -45,15 +45,83 @@ public class AsmHelper { + public static List callinvokeFunction( + Transpiler transpiler, + List args, + List name, + TransformerContext context, + boolean safe ) { + List nodes = new ArrayList(); + + nodes.addAll( name ); + + // handle positional args + if ( args.size() == 0 || args.get( 0 ).getName() == null ) { + nodes.addAll( + AsmHelper.array( Type.getType( Object.class ), args, + ( argument, i ) -> transpiler.transform( args.get( i ), context, ReturnValueContext.VALUE ) ) + ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, + Type.getInternalName( IBoxContext.class ), + "invokeFunction", + Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( Key.class ), Type.getType( Object[].class ) ), + true ) ); + + return nodes; + } + + List> keyValues = args.stream() + .map( arg -> { + List> kv = List.of( + transpiler.createKey( arg.getName() ), + transpiler.transform( arg, context, ReturnValueContext.VALUE ) + ); + + return kv; + } ) + .flatMap( x -> x.stream() ) + .collect( Collectors.toList() ); + + nodes.addAll( AsmHelper.array( Type.getType( Object.class ), keyValues ) ); + + nodes.add( + new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( Struct.class ), + "of", + Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), + false + ) + ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, + Type.getInternalName( IBoxContext.class ), + "invokeFunction", + Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( Key.class ), Type.getType( Map.class ) ), + true ) ); + + return nodes; + + } + public static List callReferencerGetAndInvoke( Transpiler transpiler, List args, String name, TransformerContext context, + boolean safe ) { + return callReferencerGetAndInvoke( transpiler, args, transpiler.createKey( name ), context, safe ); + } + + public static List callReferencerGetAndInvoke( + Transpiler transpiler, + List args, + List name, + TransformerContext context, boolean safe ) { List nodes = new ArrayList(); - nodes.addAll( transpiler.createKey( name ) ); + nodes.addAll( name ); // handle positional args if ( args.size() == 0 || args.get( 0 ).getName() == null ) { @@ -490,8 +558,8 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, // TODO needs to only use try catches that match labels in the above node list // TODO should only clear the used nodes - transpiler.getTryCatchStack().stream().forEach( ( tryNode ) -> tryNode.accept( methodVisitor ) ); - transpiler.clearTryCatchStack(); + tracker.getTryCatchStack().stream().forEach( ( tryNode ) -> tryNode.accept( methodVisitor ) ); + tracker.clearTryCatchStack(); methodVisitor.visitEnd(); transpiler.popMethodContextTracker(); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index f3a2055f3..351cdd183 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -67,6 +67,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxRethrowTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxScriptIslandTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxStaticInitializerTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxTemplateIslandTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxThrowTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxTryTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxWhileTransformer; @@ -128,6 +129,7 @@ import ortus.boxlang.compiler.ast.statement.BoxTry; import ortus.boxlang.compiler.ast.statement.BoxWhile; import ortus.boxlang.compiler.ast.statement.component.BoxComponent; +import ortus.boxlang.compiler.ast.statement.component.BoxTemplateIsland; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; @@ -203,6 +205,7 @@ public AsmTranspiler() { registry.put( BoxStaticAccess.class, new BoxStaticAccessTransformer( this ) ); registry.put( BoxStaticMethodInvocation.class, new BoxStaticMethodInvocationTransformer( this ) ); registry.put( BoxScriptIsland.class, new BoxScriptIslandTransformer( this ) ); + registry.put( BoxTemplateIsland.class, new BoxTemplateIslandTransformer( this ) ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java index 5bf6eba5e..1ca3e00a5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java @@ -1,24 +1,67 @@ package ortus.boxlang.compiler.asmboxpiler; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.IntStream; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.TryCatchBlockNode; import org.objectweb.asm.tree.VarInsnNode; public class MethodContextTracker { - private int varCount = 0; - private int unusedStackEntries = 0; - private List contextStack = new ArrayList(); + private int varCount = 0; + private int unusedStackEntries = 0; + private List contextStack = new ArrayList(); + private List tryCatchBlockNodes = new ArrayList(); + private Map breaks = new LinkedHashMap<>(); + private Map continues = new LinkedHashMap<>(); public record VarStore( int index, List nodes ) { } + public LabelNode getCurrentBreak( String label ) { + return breaks.get( label == null ? "" : label ); + } + + public void setCurrentBreak( String label, LabelNode labelNode ) { + this.breaks.put( label == null ? "" : label, labelNode ); + } + + public void removeCurrentBreak( String label ) { + this.breaks.remove( label == null ? "" : label ); + } + + public LabelNode getCurrentContinue( String label ) { + return continues.get( label == null ? "" : label ); + } + + public void setCurrentContinue( String label, LabelNode labelNode ) { + this.continues.put( label == null ? "" : label, labelNode ); + } + + public void removeCurrentContinue( String label ) { + this.continues.remove( label == null ? "" : label ); + } + + public List getTryCatchStack() { + return tryCatchBlockNodes; + } + + public void addTryCatchBlock( TryCatchBlockNode tryCatchBlockNode ) { + tryCatchBlockNodes.add( tryCatchBlockNode ); + } + + public void clearTryCatchStack() { + tryCatchBlockNodes = new ArrayList(); + } + public int getUnusedStackCount() { return unusedStackEntries; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 161058f02..49febbcb5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -263,29 +263,29 @@ public List transformAnnotations( List annotati return transformAnnotations( annotations, false, true ); } - public LabelNode getCurrentBreak( String label ) { - return breaks.get( label == null ? "" : label ); - } + // public LabelNode getCurrentBreak( String label ) { + // return breaks.get( label == null ? "" : label ); + // } - public void setCurrentBreak( String label, LabelNode labelNode ) { - this.breaks.put( label == null ? "" : label, labelNode ); - } + // public void setCurrentBreak( String label, LabelNode labelNode ) { + // this.breaks.put( label == null ? "" : label, labelNode ); + // } - public void removeCurrentBreak( String label ) { - this.breaks.remove( label == null ? "" : label ); - } + // public void removeCurrentBreak( String label ) { + // this.breaks.remove( label == null ? "" : label ); + // } - public LabelNode getCurrentContinue( String label ) { - return continues.get( label == null ? "" : label ); - } + // public LabelNode getCurrentContinue( String label ) { + // return continues.get( label == null ? "" : label ); + // } - public void setCurrentContinue( String label, LabelNode labelNode ) { - this.continues.put( label == null ? "" : label, labelNode ); - } + // public void setCurrentContinue( String label, LabelNode labelNode ) { + // this.continues.put( label == null ? "" : label, labelNode ); + // } - public void removeCurrentContinue( String label ) { - this.continues.remove( label == null ? "" : label ); - } + // public void removeCurrentContinue( String label ) { + // this.continues.remove( label == null ? "" : label ); + // } public void addImport( BoxExpression expression, BoxIdentifier alias ) { imports.add( ImportDefinition.parse( alias == null diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java index 80f231f23..c845798d9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java @@ -21,20 +21,25 @@ import java.util.List; import java.util.Optional; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; +import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.statement.BoxArgumentDeclaration; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.DefaultExpression; @@ -57,7 +62,8 @@ public List transform( BoxNode node, TransformerContext contex if ( boxArgument.getValue().isLiteral() ) { defaultLiteral = transpiler.transform( boxArgument.getValue(), TransformerContext.NONE ); } else { - defaultExpression = transpiler.transform( boxArgument.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); + defaultExpression = getDefaultExpression( boxArgument.getValue() ); + // defaultExpression = transpiler.transform( boxArgument.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); } } @@ -68,6 +74,8 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new LdcInsnNode( Optional.ofNullable( boxArgument.getType() ).orElse( "any" ) ) ); nodes.addAll( transpiler.createKey( boxArgument.getName() ) ); nodes.addAll( defaultLiteral ); + nodes.add( new LdcInsnNode( "DEBUG -args" ) ); + nodes.add( new InsnNode( Opcodes.POP ) ); nodes.addAll( defaultExpression ); nodes.addAll( transpiler.transformAnnotations( boxArgument.getAnnotations() ) ); nodes.addAll( transpiler.transformDocumentation( boxArgument.getDocumentation() ) ); @@ -85,4 +93,70 @@ public List transform( BoxNode node, TransformerContext contex false ) ); return nodes; } + + private List getDefaultExpression( BoxExpression body ) { + Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + + "/" + transpiler.getProperty( "classname" ) + + "$Lambda_" + transpiler.incrementAndGetLambdaCounter() + ";" ); + + ClassNode classNode = new ClassNode(); + + classNode.visit( + Opcodes.V17, + Opcodes.ACC_PUBLIC, + type.getInternalName(), + null, + Type.getInternalName( Object.class ), + new String[] { Type.getInternalName( Argument.DefaultExpression.class ) } ); + + MethodVisitor initVisitor = classNode.visitMethod( Opcodes.ACC_PUBLIC, + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + null, + null ); + initVisitor.visitCode(); + initVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + initVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( Object.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + initVisitor.visitInsn( Opcodes.RETURN ); + initVisitor.visitEnd(); + + MethodContextTracker t = new MethodContextTracker( false ); + transpiler.addMethodContextTracker( t ); + // Object evaluate( IBoxContext context ); + MethodVisitor methodVisitor = classNode.visitMethod( + Opcodes.ACC_PUBLIC, + "evaluate", + Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( IBoxContext.class ) ), + null, + null ); + methodVisitor.visitCode(); + + t.trackNewContext(); + + transpiler.transform( body, TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) + .forEach( ( ins ) -> ins.accept( methodVisitor ) ); + + methodVisitor.visitInsn( Opcodes.ARETURN ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + + transpiler.popMethodContextTracker(); + + transpiler.setAuxiliary( type.getClassName(), classNode ); + + List nodes = new ArrayList(); + + nodes.add( new TypeInsnNode( Opcodes.NEW, type.getInternalName() ) ); + nodes.add( new InsnNode( Opcodes.DUP ) ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESPECIAL, + type.getInternalName(), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ) ); + return nodes; + } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java index 8950fd26d..c78cec5cc 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java @@ -42,11 +42,16 @@ public BoxBreakTransformer( Transpiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxBreak breakNode = ( BoxBreak ) node; - ExitsAllowed exitsAllowed = getExitsAllowed( node ); + BoxBreak breakNode = ( BoxBreak ) node; + ExitsAllowed exitsAllowed = getExitsAllowed( node ); - LabelNode currentBreak = transpiler.getCurrentBreak( breakNode.getLabel() ); - List nodes = new ArrayList(); + LabelNode currentBreak = null; + + if ( transpiler.getCurrentMethodContextTracker().isPresent() ) { + currentBreak = transpiler.getCurrentMethodContextTracker().get().getCurrentBreak( breakNode.getLabel() ); + } + + List nodes = new ArrayList(); if ( returnContext.nullable || exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java index aee83d060..b34b48770 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java @@ -33,6 +33,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxClosure; +import ortus.boxlang.compiler.ast.statement.BoxStatementBlock; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.FunctionBoxContext; import ortus.boxlang.runtime.context.IBoxContext; @@ -139,8 +140,10 @@ public List transform( BoxNode node, TransformerContext contex "getSourceType", Type.getType( BoxSourceType.class ) ); + boolean isBlock = boxClosure.getBody() instanceof BoxStatementBlock; + AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, - transpiler, false, + transpiler, isBlock, () -> boxClosure.getBody().getChildren().stream().flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) .toList() ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java index 5a943affc..13974a46f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java @@ -38,11 +38,16 @@ public BoxContinueTransformer( Transpiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxContinue continueNode = ( BoxContinue ) node; - ExitsAllowed exitsAllowed = getExitsAllowed( node ); + BoxContinue continueNode = ( BoxContinue ) node; + ExitsAllowed exitsAllowed = getExitsAllowed( node ); - LabelNode currentBreak = transpiler.getCurrentContinue( continueNode.getLabel() ); - List nodes = new ArrayList(); + LabelNode currentBreak = transpiler.getCurrentMethodContextTracker().get().getCurrentContinue( continueNode.getLabel() ); + + if ( transpiler.getCurrentMethodContextTracker().isPresent() ) { + currentBreak = transpiler.getCurrentMethodContextTracker().get().getCurrentContinue( continueNode.getLabel() ); + } + + List nodes = new ArrayList(); if ( returnContext.nullable || exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java index 802012fbd..13a17d5a1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java @@ -18,10 +18,8 @@ import java.util.List; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -31,8 +29,6 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxFunctionInvocation; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.scopes.Key; public class BoxFunctionInvocationTransformer extends AbstractTransformer { @@ -43,20 +39,22 @@ public BoxFunctionInvocationTransformer( AsmTranspiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { BoxFunctionInvocation function = ( BoxFunctionInvocation ) node; - TransformerContext safe = function.getName().equalsIgnoreCase( "isnull" ) ? TransformerContext.SAFE : context; + boolean safe = function.getName().equalsIgnoreCase( "isnull" ) ? true : false; List nodes = new ArrayList<>(); nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); - nodes.addAll( transpiler.createKey( function.getName() ) ); + // nodes.addAll( transpiler.createKey( function.getName() ) ); - nodes.addAll( AsmHelper.array( Type.getType( Object.class ), function.getArguments(), - ( argument, i ) -> transpiler.transform( argument, safe, ReturnValueContext.VALUE ) ) ); + nodes.addAll( AsmHelper.callinvokeFunction( transpiler, function.getArguments(), transpiler.createKey( function.getName() ), context, safe ) ); - nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, - Type.getInternalName( IBoxContext.class ), - "invokeFunction", - Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( Key.class ), Type.getType( Object[].class ) ), - true ) ); + // nodes.addAll( AsmHelper.array( Type.getType( Object.class ), function.getArguments(), + // ( argument, i ) -> transpiler.transform( argument, safe, ReturnValueContext.VALUE ) ) ); + + // nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, + // Type.getInternalName( IBoxContext.class ), + // "invokeFunction", + // Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( Key.class ), Type.getType( Object[].class ) ), + // true ) ); if ( returnContext.empty ) { nodes.add( new InsnNode( Opcodes.POP ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java index 9e6690714..8b917ed8a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java @@ -20,6 +20,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -118,8 +119,14 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, transpiler, false, - () -> boxLambda.getBody().getChildren().stream().flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) - .toList() ); + () -> { + if ( boxLambda.getBody().getChildren().size() == 0 ) { + return List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + } + return boxLambda.getBody().getChildren().stream() + .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ).stream() ) + .toList(); + } ); AsmHelper.complete( classNode, type, methodVisitor -> { methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java index 610e071a1..60a533afe 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java @@ -21,11 +21,8 @@ import java.util.List; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; @@ -35,9 +32,6 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxIdentifier; import ortus.boxlang.compiler.ast.expression.BoxMethodInvocation; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.dynamic.Referencer; -import ortus.boxlang.runtime.scopes.Key; public class BoxMethodInvocationTransformer extends AbstractTransformer { @@ -56,31 +50,34 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( invocation.getObj(), context, ReturnValueContext.VALUE ) ); + List name = null; if ( invocation.getUsedDotAccess() ) { - nodes.addAll( transpiler.createKey( ( ( BoxIdentifier ) invocation.getName() ).getName() ) ); + name = transpiler.createKey( ( ( BoxIdentifier ) invocation.getName() ).getName() ); } else { - nodes.addAll( transpiler.createKey( invocation.getName() ) ); + name = transpiler.createKey( invocation.getName() ); } - nodes - .addAll( AsmHelper.array( Type.getType( Object.class ), invocation.getArguments(), - ( argument, i ) -> transpiler.transform( argument, context, ReturnValueContext.VALUE ) ) ); - - nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, - Type.getInternalName( Boolean.class ), - safe.toString().toUpperCase(), - Type.getDescriptor( Boolean.class ) ) ); - - nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, - Type.getInternalName( Referencer.class ), - "getAndInvoke", - Type.getMethodDescriptor( Type.getType( Object.class ), - Type.getType( IBoxContext.class ), - Type.getType( Object.class ), - Type.getType( Key.class ), - Type.getType( Object[].class ), - Type.getType( Boolean.class ) ), - false ) ); + nodes.addAll( AsmHelper.callReferencerGetAndInvoke( transpiler, invocation.getArguments(), name, context, false ) ); + + // nodes + // .addAll( AsmHelper.array( Type.getType( Object.class ), invocation.getArguments(), + // ( argument, i ) -> transpiler.transform( argument, context, ReturnValueContext.VALUE ) ) ); + + // nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + // Type.getInternalName( Boolean.class ), + // safe.toString().toUpperCase(), + // Type.getDescriptor( Boolean.class ) ) ); + + // nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + // Type.getInternalName( Referencer.class ), + // "getAndInvoke", + // Type.getMethodDescriptor( Type.getType( Object.class ), + // Type.getType( IBoxContext.class ), + // Type.getType( Object.class ), + // Type.getType( Key.class ), + // Type.getType( Object[].class ), + // Type.getType( Boolean.class ) ), + // false ) ); if ( returnContext.empty ) { nodes.add( new InsnNode( Opcodes.POP ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java index 0d51ea2e4..0786efc34 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java @@ -44,7 +44,7 @@ public BoxStringConcatTransformer( Transpiler transpiler ) { public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { BoxStringConcat interpolation = ( BoxStringConcat ) node; if ( interpolation.getValues().size() == 1 ) { - return transpiler.transform( interpolation.getValues().get( 0 ), TransformerContext.NONE ); + return transpiler.transform( interpolation.getValues().get( 0 ), TransformerContext.NONE, returnContext ); } else { List nodes = new ArrayList<>(); nodes.addAll( AsmHelper.array( Type.getType( Object.class ), interpolation.getValues(), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java index ff7957cbb..89f5f484f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java @@ -96,9 +96,9 @@ public List transform( BoxNode node, TransformerContext contex false ) ); nodes.add( new JumpInsnNode( Opcodes.IFEQ, endOfCase ) ); nodes.add( startOfCase ); - transpiler.setCurrentBreak( null, endLabel ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, endLabel ); c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE ) ) ); - transpiler.removeCurrentBreak( null ); // TODO: label name? + transpiler.getCurrentMethodContextTracker().get().removeCurrentBreak( null ); // TODO: label name? nodes.add( new LdcInsnNode( 1 ) ); nodes.add( new JumpInsnNode( Opcodes.GOTO, endOfAll ) ); nodes.add( endOfCase ); @@ -114,9 +114,14 @@ public List transform( BoxNode node, TransformerContext contex throw new ExpressionException( "Multiple default cases not supported", c.getPosition(), c.getSourceText() ); } hasDefault = true; + nodes.add( new InsnNode( Opcodes.POP ) ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, endLabel ); c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE ) ) ); + transpiler.getCurrentMethodContextTracker().get().removeCurrentBreak( null ); + nodes.add( new JumpInsnNode( Opcodes.GOTO, endLabel ) ); } } + nodes.add( new InsnNode( Opcodes.POP ) ); nodes.add( endLabel ); nodes.add( new InsnNode( Opcodes.POP ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java index 789d1356e..c668f9de6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java @@ -48,10 +48,10 @@ public List transform( BoxNode node, TransformerContext contex LabelNode continueLabel = new LabelNode(); List nodes = new ArrayList<>(); - transpiler.setCurrentBreak( boxDo.getLabel(), end ); - transpiler.setCurrentBreak( null, end ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( boxDo.getLabel(), end ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, end ); - transpiler.setCurrentContinue( null, continueLabel ); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, continueLabel ); // push two nulls onto the stack in order to initialize our strategy for keeping the stack height consistent // this is to allow the statement to return an expression in the case of a BoxScript execution diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index e8ecf67fe..8bcfc0fc6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -67,6 +67,12 @@ public List transform( BoxNode node, TransformerContext contex LabelNode loopStart = new LabelNode(); LabelNode loopEnd = new LabelNode(); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, loopStart ); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( forIn.getLabel(), loopStart ); + + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, loopEnd ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( forIn.getLabel(), loopEnd ); + // access the collection nodes.addAll( transpiler.transform( forIn.getExpression(), context ) ); // unwrap it diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java index d2fe576a4..6b9368b66 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java @@ -58,11 +58,11 @@ public List transform( BoxNode node, TransformerContext contex LabelNode loopStart = new LabelNode(); LabelNode loopEnd = new LabelNode(); - transpiler.setCurrentBreak( forIn.getLabel(), breakTarget ); - transpiler.setCurrentBreak( null, breakTarget ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( forIn.getLabel(), breakTarget ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, breakTarget ); - transpiler.setCurrentContinue( null, loopStart ); - transpiler.setCurrentContinue( forIn.getLabel(), loopStart ); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, loopStart ); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( forIn.getLabel(), loopStart ); if ( forIn.getInitializer() != null ) { nodes.addAll( transpiler.transform( forIn.getInitializer(), context, ReturnValueContext.EMPTY ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java new file mode 100644 index 000000000..b9a2fbc76 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.statement; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.tree.AbstractInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.BoxStatement; +import ortus.boxlang.compiler.ast.statement.component.BoxTemplateIsland; + +public class BoxTemplateIslandTransformer extends AbstractTransformer { + + public BoxTemplateIslandTransformer( Transpiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { + BoxTemplateIsland templateIsland = ( BoxTemplateIsland ) node; + + List nodes = new ArrayList<>(); + for ( BoxStatement statement : templateIsland.getStatements() ) { + nodes.addAll( transpiler.transform( statement, context, returnContext ) ); + } + + return nodes; + } +} diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java index d192e485a..464d527ba 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java @@ -91,7 +91,7 @@ public List transform( BoxNode node, TransformerContext contex TryCatchBlockNode catchHandler = new TryCatchBlockNode( tryStartLabel, tryEndLabel, javaCatchBodyStart, null ); - transpiler.addTryCatchBlock( catchHandler ); + tracker.addTryCatchBlock( catchHandler ); // if we are here none of our catch handlers matched the error so we inline another finally block if ( boxTry.getFinallyBody().size() == 0 ) { @@ -106,7 +106,7 @@ public List transform( BoxNode node, TransformerContext contex TryCatchBlockNode catchHandler = new TryCatchBlockNode( tryStartLabel, tryEndLabel, finallyStartLabel, null ); - transpiler.addTryCatchBlock( catchHandler ); + tracker.addTryCatchBlock( catchHandler ); nodes.add( finallyStartLabel ); @@ -121,7 +121,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( finallyEndLabel ); - transpiler.addTryCatchBlock( new TryCatchBlockNode( tryStartLabel, tryEndLabel, finallyStartLabel, null ) ); + tracker.addTryCatchBlock( new TryCatchBlockNode( tryStartLabel, tryEndLabel, finallyStartLabel, null ) ); return nodes; @@ -140,6 +140,12 @@ private List generateBodyNodesWithInlinedFinally( if ( returnValueContext != ReturnValueContext.VALUE_OR_NULL ) { nodes.add( new InsnNode( Opcodes.POP ) ); } + + AbstractInsnNode inBetweenNode = inBetween.get(); + + if ( inBetweenNode != null ) { + nodes.add( inBetweenNode ); + } } nodes.addAll( AsmHelper.transformBodyExpressions( @@ -213,7 +219,7 @@ private List generateCatchBodyNodes( nodes.add( new JumpInsnNode( Opcodes.GOTO, finallyEndLabel ) ); nodes.add( endHandlerLabel ); - transpiler.addTryCatchBlock( new TryCatchBlockNode( startHandlerLabel, endHandlerLabel, finallyStartLabel, null ) ); + tracker.addTryCatchBlock( new TryCatchBlockNode( startHandlerLabel, endHandlerLabel, finallyStartLabel, null ) ); return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java index 13ce37512..37849793e 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java @@ -51,11 +51,11 @@ public List transform( BoxNode node, TransformerContext contex // if ( boxWhile.getLabel() != null ) { // } - transpiler.setCurrentBreak( boxWhile.getLabel(), breakTarget ); - transpiler.setCurrentBreak( "", breakTarget ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( boxWhile.getLabel(), breakTarget ); + transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( "", breakTarget ); - transpiler.setCurrentContinue( null, start ); - transpiler.setCurrentContinue( boxWhile.getLabel(), start ); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, start ); + transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( boxWhile.getLabel(), start ); // push two nulls onto the stack in order to initialize our strategy for keeping the stack height consistent // this is to allow the statement to return an expression in the case of a BoxScript execution From 0a7a2aeb71a65beb6207c41be50236e018dfceb6 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 25 Sep 2024 10:25:21 -0500 Subject: [PATCH 19/71] Continued work getting ColdBox to work with ASM --- .../compiler/asmboxpiler/AsmHelper.java | 5 +- .../compiler/asmboxpiler/AsmTranspiler.java | 12 ++++ .../compiler/asmboxpiler/Transpiler.java | 20 ++++++ .../BoxArrayLiteralTransformer.java | 3 +- .../BoxExpressionInvocationTransformer.java | 63 +++++++++++++++++++ .../BoxFunctionInvocationTransformer.java | 6 +- .../expression/BoxNewTransformer.java | 2 +- .../expression/BoxReturnTransformer.java | 15 +++++ .../statement/BoxComponentTransformer.java | 4 ++ .../statement/BoxForInTransformer.java | 10 +-- .../statement/BoxForIndexTransformer.java | 22 ++++--- 11 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index a0413cf09..d629130c9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -47,6 +47,7 @@ public class AsmHelper { public static List callinvokeFunction( Transpiler transpiler, + Type invokeType, List args, List name, TransformerContext context, @@ -65,7 +66,7 @@ public static List callinvokeFunction( nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "invokeFunction", - Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( Key.class ), Type.getType( Object[].class ) ), + Type.getMethodDescriptor( Type.getType( Object.class ), invokeType, Type.getType( Object[].class ) ), true ) ); return nodes; @@ -97,7 +98,7 @@ public static List callinvokeFunction( nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "invokeFunction", - Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( Key.class ), Type.getType( Map.class ) ), + Type.getMethodDescriptor( Type.getType( Object.class ), invokeType, Type.getType( Map.class ) ), true ) ); return nodes; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 351cdd183..97adfcd48 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -32,6 +32,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxComparisonOperationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxContinueTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxDecimalLiteralTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxExpressionInvocationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxExpressionStatementTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxFQNTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.expression.BoxFunctionInvocationTransformer; @@ -64,6 +65,8 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxForIndexTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxFunctionDeclarationTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxIfElseTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxInterfaceTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxParamTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxRethrowTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxScriptIslandTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxStaticInitializerTransformer; @@ -73,6 +76,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxWhileTransformer; import ortus.boxlang.compiler.ast.BoxClass; import ortus.boxlang.compiler.ast.BoxExpression; +import ortus.boxlang.compiler.ast.BoxInterface; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxScript; import ortus.boxlang.compiler.ast.BoxStaticInitializer; @@ -88,6 +92,7 @@ import ortus.boxlang.compiler.ast.expression.BoxComparisonOperation; import ortus.boxlang.compiler.ast.expression.BoxDecimalLiteral; import ortus.boxlang.compiler.ast.expression.BoxDotAccess; +import ortus.boxlang.compiler.ast.expression.BoxExpressionInvocation; import ortus.boxlang.compiler.ast.expression.BoxFQN; import ortus.boxlang.compiler.ast.expression.BoxFunctionInvocation; import ortus.boxlang.compiler.ast.expression.BoxIdentifier; @@ -119,6 +124,7 @@ import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; import ortus.boxlang.compiler.ast.statement.BoxIfElse; import ortus.boxlang.compiler.ast.statement.BoxImport; +import ortus.boxlang.compiler.ast.statement.BoxParam; import ortus.boxlang.compiler.ast.statement.BoxProperty; import ortus.boxlang.compiler.ast.statement.BoxRethrow; import ortus.boxlang.compiler.ast.statement.BoxReturn; @@ -206,6 +212,8 @@ public AsmTranspiler() { registry.put( BoxStaticMethodInvocation.class, new BoxStaticMethodInvocationTransformer( this ) ); registry.put( BoxScriptIsland.class, new BoxScriptIslandTransformer( this ) ); registry.put( BoxTemplateIsland.class, new BoxTemplateIslandTransformer( this ) ); + registry.put( BoxExpressionInvocation.class, new BoxExpressionInvocationTransformer( this ) ); + registry.put( BoxParam.class, new BoxParamTransformer( this ) ); } @Override @@ -337,6 +345,10 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { return BoxClassTransformer.transpile( this, boxClass ); } + public ClassNode transpile( BoxInterface boxClass ) throws BoxRuntimeException { + return BoxInterfaceTransformer.transpile( this, boxClass ); + } + @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) { Transformer transformer = registry.get( node.getClass() ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 49febbcb5..a08d34cf0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -21,7 +21,9 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.statement.BoxInterfaceTransformer; import ortus.boxlang.compiler.ast.BoxExpression; +import ortus.boxlang.compiler.ast.BoxInterface; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxStaticInitializer; import ortus.boxlang.compiler.ast.expression.BoxIdentifier; @@ -34,6 +36,7 @@ import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; public abstract class Transpiler implements ITranspiler { @@ -42,6 +45,7 @@ public abstract class Transpiler implements ITranspiler { private Map auxiliaries = new LinkedHashMap(); private List tryCatchBlockNodes = new ArrayList(); private int lambdaCounter = 0; + private int componentCounter = 0; private Map breaks = new LinkedHashMap<>(); private Map continues = new LinkedHashMap<>(); private List imports = new ArrayList<>(); @@ -58,6 +62,22 @@ public void setProperty( String key, String value ) { properties.put( key, value ); } + public boolean isInsideComponent() { + return componentCounter > 0; + } + + public void incrementComponentCounter() { + componentCounter++; + } + + public void decrementComponentCounter() { + componentCounter--; + } + + public ClassNode transpile( BoxInterface boxClass ) throws BoxRuntimeException { + return BoxInterfaceTransformer.transpile( this, boxClass ); + } + /** * Get a Propoerty * diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java index 39c9be122..3a4fd38cf 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java @@ -44,7 +44,8 @@ public BoxArrayLiteralTransformer( AsmTranspiler transpiler ) { public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { BoxArrayLiteral arrayLiteral = ( BoxArrayLiteral ) node; List nodes = new ArrayList<>(); - nodes.addAll( AsmHelper.array( Type.getType( Object.class ), arrayLiteral.getValues(), ( value, i ) -> transpiler.transform( value, context ) ) ); + nodes.addAll( AsmHelper.array( Type.getType( Object.class ), arrayLiteral.getValues(), + ( value, i ) -> transpiler.transform( value, context, ReturnValueContext.VALUE_OR_NULL ) ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( Array.class ), "of", diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java new file mode 100644 index 000000000..d27482bc5 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java @@ -0,0 +1,63 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.expression; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.expression.BoxExpressionInvocation; +import ortus.boxlang.compiler.ast.expression.BoxLambda; +import ortus.boxlang.runtime.scopes.Key; + +public class BoxExpressionInvocationTransformer extends AbstractTransformer { + + public BoxExpressionInvocationTransformer( AsmTranspiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { + BoxExpressionInvocation invocation = ( BoxExpressionInvocation ) node; + + List nameNodes = transpiler.transform( invocation.getExpr(), context, ReturnValueContext.VALUE ); + + List nodes = new ArrayList<>(); + nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); + + Type invokeType = invocation.getExpr() instanceof BoxLambda || invocation.getExpr().getDescendantsOfType( BoxLambda.class ).size() > 0 + ? Type.getType( Object.class ) + : Type.getType( Key.class ); + + nodes.addAll( AsmHelper.callinvokeFunction( transpiler, invokeType, invocation.getArguments(), nameNodes, context, false ) ); + + if ( returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + + return nodes; + } +} diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java index 13a17d5a1..dd6a6c202 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java @@ -18,6 +18,7 @@ import java.util.List; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.VarInsnNode; @@ -29,6 +30,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxFunctionInvocation; +import ortus.boxlang.runtime.scopes.Key; public class BoxFunctionInvocationTransformer extends AbstractTransformer { @@ -45,7 +47,9 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); // nodes.addAll( transpiler.createKey( function.getName() ) ); - nodes.addAll( AsmHelper.callinvokeFunction( transpiler, function.getArguments(), transpiler.createKey( function.getName() ), context, safe ) ); + TransformerContext argContext = safe ? TransformerContext.SAFE : context; + nodes.addAll( AsmHelper.callinvokeFunction( transpiler, Type.getType( Key.class ), function.getArguments(), transpiler.createKey( function.getName() ), + argContext, safe ) ); // nodes.addAll( AsmHelper.array( Type.getType( Object.class ), function.getArguments(), // ( argument, i ) -> transpiler.transform( argument, safe, ReturnValueContext.VALUE ) ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java index 368ba34e1..57b5b9afa 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java @@ -55,7 +55,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); nodes.add( new LdcInsnNode( "" ) ); // TODO: how to set this? - nodes.addAll( transpiler.transform( boxNew.getExpression(), TransformerContext.NONE ) ); + nodes.addAll( transpiler.transform( boxNew.getExpression(), TransformerContext.NONE, ReturnValueContext.VALUE ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( StringCaster.class ), "cast", diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java index b676f508d..616dd6b23 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java @@ -19,8 +19,10 @@ import java.util.List; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; @@ -28,6 +30,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.statement.BoxReturn; +import ortus.boxlang.runtime.components.Component; public class BoxReturnTransformer extends AbstractTransformer { @@ -42,6 +45,18 @@ public List transform( BoxNode node, TransformerContext contex List nodes = new ArrayList<>(); if ( boxReturn.getExpression() == null ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } else if ( transpiler.isInsideComponent() ) { + nodes.addAll( transpiler.transform( boxReturn.getExpression(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Component.BodyResult.class ), + "ofReturn", + Type.getMethodDescriptor( Type.getType( Component.BodyResult.class ), Type.getType( Object.class ) ), + false + ) + ); + // template = "return Component.BodyResult.ofReturn( ${expr} );"; } else { nodes.addAll( transpiler.transform( boxReturn.getExpression(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 950007e80..c7fb895cf 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -43,6 +43,8 @@ public List transform( BoxNode node, TransformerContext contex throw new IllegalStateException(); } + transpiler.incrementComponentCounter(); + MethodContextTracker tracker = trackerOption.get(); List nodes = new ArrayList<>(); nodes.addAll( tracker.loadCurrentContext() ); @@ -64,6 +66,8 @@ public List transform( BoxNode node, TransformerContext contex true ) ); nodes.add( new InsnNode( Opcodes.POP ) ); + transpiler.decrementComponentCounter(); + return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index 8bcfc0fc6..c07a9f0e8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -67,14 +67,14 @@ public List transform( BoxNode node, TransformerContext contex LabelNode loopStart = new LabelNode(); LabelNode loopEnd = new LabelNode(); - transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, loopStart ); - transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( forIn.getLabel(), loopStart ); + tracker.setCurrentContinue( null, loopStart ); + tracker.setCurrentContinue( forIn.getLabel(), loopStart ); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, loopEnd ); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( forIn.getLabel(), loopEnd ); + tracker.setCurrentBreak( null, loopEnd ); + tracker.setCurrentBreak( forIn.getLabel(), loopEnd ); // access the collection - nodes.addAll( transpiler.transform( forIn.getExpression(), context ) ); + nodes.addAll( transpiler.transform( forIn.getExpression(), context, ReturnValueContext.VALUE_OR_NULL ) ); // unwrap it nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( DynamicObject.class ), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java index 6b9368b66..73ff84ae2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java @@ -53,16 +53,18 @@ public List transform( BoxNode node, TransformerContext contex throw new IllegalStateException(); } - LabelNode breakTarget = new LabelNode(); - LabelNode firstLoop = new LabelNode(); - LabelNode loopStart = new LabelNode(); - LabelNode loopEnd = new LabelNode(); + MethodContextTracker tracker = trackerOption.get(); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( forIn.getLabel(), breakTarget ); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, breakTarget ); + LabelNode breakTarget = new LabelNode(); + LabelNode firstLoop = new LabelNode(); + LabelNode loopStart = new LabelNode(); + LabelNode loopEnd = new LabelNode(); - transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, loopStart ); - transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( forIn.getLabel(), loopStart ); + tracker.setCurrentBreak( forIn.getLabel(), breakTarget ); + tracker.setCurrentBreak( null, breakTarget ); + + tracker.setCurrentContinue( null, loopStart ); + tracker.setCurrentContinue( forIn.getLabel(), loopStart ); if ( forIn.getInitializer() != null ) { nodes.addAll( transpiler.transform( forIn.getInitializer(), context, ReturnValueContext.EMPTY ) ); @@ -122,6 +124,10 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } + tracker.setCurrentBreak( null, null ); + + tracker.setCurrentContinue( null, null ); + return nodes; } From 7eaef45aa415b579772165a2ce077babc1171a92 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 25 Sep 2024 10:28:56 -0500 Subject: [PATCH 20/71] Start work on ASM BoxInterface transformation --- .../statement/BoxInterfaceTransformer.java | 123 ++++++++++++++++++ .../statement/BoxParamTransformer.java | 79 +++++++++++ .../asm/integration/ControllerTest.java | 20 +++ .../asm/integration/TryCatchLabelStack.cfc | 8 ++ .../TestCases/asm/phase1/CoreLangTest.java | 23 ++++ 5 files changed, 253 insertions(+) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java create mode 100644 src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java new file mode 100644 index 000000000..b26513fa4 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -0,0 +1,123 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.statement; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; + +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.ast.BoxInterface; +import ortus.boxlang.compiler.ast.Source; +import ortus.boxlang.compiler.ast.SourceFile; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; + +public class BoxInterfaceTransformer { + + public static final Type CLASS_TYPE = Type.getType( Class.class ); + public static final Type CLASS_ARRAY_TYPE = Type.getType( Class[].class ); + + private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; + + @SuppressWarnings( "unchecked" ) + public static ClassNode transpile( Transpiler transpiler, BoxInterface boxClass ) throws BoxRuntimeException { + Source source = boxClass.getPosition().getSource(); + String sourceType = transpiler.getProperty( "sourceType" ); + + String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() + : "unknown"; + String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; + String boxPackageName = transpiler.getProperty( "boxPackageName" ); + String rawBoxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ), boxClassName; + // trim leading . if exists + if ( rawBoxClassName.startsWith( "." ) ) { + boxClassName = rawBoxClassName.substring( 1 ); + } else { + boxClassName = rawBoxClassName; + } + transpiler.setProperty( "boxClassName", boxClassName ); + String mappingName = transpiler.getProperty( "mappingName" ); + String mappingPath = transpiler.getProperty( "mappingPath" ); + String relativePath = transpiler.getProperty( "relativePath" ); + + Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + + "/" + transpiler.getProperty( "classname" ) + ";" ); + transpiler.setProperty( "classType", type.getDescriptor() ); + transpiler.setProperty( "classTypeInternal", type.getInternalName() ); + + List interfaces = new ArrayList<>(); + + ClassNode classNode = new ClassNode(); + + AsmHelper.init( classNode, false, type, Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ), methodVisitor -> { + + }, interfaces.toArray( Type[]::new ) ); + + addGetInstance( classNode, type ); + + return classNode; + } + + private static void addGetInstance( ClassVisitor classVisitor, Type type ) { + FieldVisitor fieldVisitor = classVisitor.visitField( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + "instance", + type.getDescriptor(), + null, + null ); + fieldVisitor.visitEnd(); + MethodVisitor methodVisitor = classVisitor.visitMethod( + Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_STATIC, + "getInstance", + Type.getMethodDescriptor( type, Type.getType( IBoxContext.class ) ), + null, + null ); + methodVisitor.visitCode(); + Label after = new Label(); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); + methodVisitor.visitJumpInsn( Opcodes.IFNONNULL, after ); + methodVisitor.visitTypeInsn( Opcodes.NEW, type.getInternalName() ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + type.getInternalName(), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); + methodVisitor.visitLabel( after ); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); + methodVisitor.visitInsn( Opcodes.ARETURN ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + } +} diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java new file mode 100644 index 000000000..ee2ba04bb --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java @@ -0,0 +1,79 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.compiler.asmboxpiler.transformer.statement; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.tree.AbstractInsnNode; + +import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.expression.BoxFQN; +import ortus.boxlang.compiler.ast.statement.BoxAnnotation; +import ortus.boxlang.compiler.ast.statement.BoxParam; +import ortus.boxlang.compiler.ast.statement.component.BoxComponent; + +public class BoxParamTransformer extends AbstractTransformer { + + public BoxParamTransformer( AsmTranspiler transpiler ) { + super( transpiler ); + } + + @Override + public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) { + BoxParam boxParam = ( BoxParam ) node; + List attrs = new ArrayList(); + attrs.add( + new BoxAnnotation( + new BoxFQN( "name", + boxParam.getVariable().getPosition(), + boxParam.getVariable().getSourceText() ), + boxParam.getVariable(), + boxParam.getVariable().getPosition(), + boxParam.getVariable().getSourceText() + ) + ); + if ( boxParam.getType() != null ) { + attrs.add( + new BoxAnnotation( + new BoxFQN( "type", + boxParam.getType().getPosition(), + boxParam.getType().getSourceText() ), + boxParam.getType(), + boxParam.getType().getPosition(), + boxParam.getType().getSourceText() + ) + ); + } + if ( boxParam.getDefaultValue() != null ) { + attrs.add( + new BoxAnnotation( + new BoxFQN( "default", + boxParam.getDefaultValue().getPosition(), + boxParam.getDefaultValue().getSourceText() ), + boxParam.getDefaultValue(), + boxParam.getDefaultValue().getPosition(), + boxParam.getDefaultValue().getSourceText() + ) + ); + } + // Delegate to the component transformer + return transpiler.transform( new BoxComponent( "param", attrs, node.getPosition(), node.getSourceText() ), context ); + } +} diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java index f1d0cbe41..15478f4f8 100644 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ b/src/test/java/TestCases/asm/integration/ControllerTest.java @@ -70,4 +70,24 @@ public void testControllerCompilation() { context ); } + @Test + public void testCompatModuleConfig() { + + instance.executeStatement( + """ + controller = new src.test.java.TestCases.asm.integration.mc(); + """, + context ); + } + + @Test + public void testTryCatchLabelStack() { + + instance.executeStatement( + """ + controller = new src.test.java.TestCases.asm.integration.TryCatchLabelStack(); + """, + context ); + } + } diff --git a/src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc b/src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc new file mode 100644 index 000000000..0e655390c --- /dev/null +++ b/src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc @@ -0,0 +1,8 @@ +component { + try { + } + + savecontent variable="x" { + "test"; + } +} diff --git a/src/test/java/TestCases/asm/phase1/CoreLangTest.java b/src/test/java/TestCases/asm/phase1/CoreLangTest.java index f02b6736c..c68f12d5d 100644 --- a/src/test/java/TestCases/asm/phase1/CoreLangTest.java +++ b/src/test/java/TestCases/asm/phase1/CoreLangTest.java @@ -1188,6 +1188,29 @@ public void testSwtich() { } + @Test + public void testSwtichWithBreakInDefault() { + + instance.executeSource( + """ + result = "" + variables.foo = true; + + switch( "12" ) { + case "brad": + result= "brad"; + break; + default: + result="test"; + break; + } + """, + context ); + + assertThat( variables.get( result ) ).isEqualTo( "test" ); + + } + @DisplayName( "switch fall through case" ) @Test public void testSwtichFallThroughCase() { From ab366f8e71f26b8ca149b14677292b84455a57d3 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 25 Sep 2024 11:02:17 -0500 Subject: [PATCH 21/71] Add ASM interface test --- .../BoxArgumentDeclarationTransformer.java | 2 +- .../asm/integration/ControllerTest.java | 15 ++++++++--- .../asm/integration/ICacheProvider.cfc | 26 +++++++++++++++++++ .../TestCases/asm/integration/Implementor.cfc | 12 +++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/test/java/TestCases/asm/integration/ICacheProvider.cfc create mode 100644 src/test/java/TestCases/asm/integration/Implementor.cfc diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java index c845798d9..8a7b9fcc8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java @@ -107,7 +107,7 @@ private List getDefaultExpression( BoxExpression body ) { type.getInternalName(), null, Type.getInternalName( Object.class ), - new String[] { Type.getInternalName( Argument.DefaultExpression.class ) } ); + new String[] { Type.getInternalName( DefaultExpression.class ) } ); MethodVisitor initVisitor = classNode.visitMethod( Opcodes.ACC_PUBLIC, "", diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java index 15478f4f8..31ffcb45d 100644 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ b/src/test/java/TestCases/asm/integration/ControllerTest.java @@ -17,6 +17,8 @@ */ package TestCases.asm.integration; +import static com.google.common.truth.Truth.assertThat; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -71,13 +73,18 @@ public void testControllerCompilation() { } @Test - public void testCompatModuleConfig() { - + public void testInterfaceImplementation() { instance.executeStatement( """ - controller = new src.test.java.TestCases.asm.integration.mc(); - """, + impl = new src.test.java.TestCases.asm.integration.Implementor(); + + impl.setName( "test" ); + + result = impl.getName(); + """, context ); + + assertThat( variables.get( result ) ).isEqualTo( "test" ); } @Test diff --git a/src/test/java/TestCases/asm/integration/ICacheProvider.cfc b/src/test/java/TestCases/asm/integration/ICacheProvider.cfc new file mode 100644 index 000000000..4f750cc85 --- /dev/null +++ b/src/test/java/TestCases/asm/integration/ICacheProvider.cfc @@ -0,0 +1,26 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * + * The main interface for a CacheBox cache provider. You need to implement all the methods in order for CacheBox to work correctly for the implementing cache provider. + * + * @author Luis Majano + */ +interface { + + /** + * Get the name of this cache + */ + function getName(); + + /** + * Set the cache name + * + * @name The name to set + * + * @return ICacheProvider + */ + function setName( required name ); + +} diff --git a/src/test/java/TestCases/asm/integration/Implementor.cfc b/src/test/java/TestCases/asm/integration/Implementor.cfc new file mode 100644 index 000000000..9283f17c2 --- /dev/null +++ b/src/test/java/TestCases/asm/integration/Implementor.cfc @@ -0,0 +1,12 @@ +component implements="ICacheProvider" { + + variables.name = ""; + + function getName(){ + return variables.name; + } + + function setName( required name ){ + variables.name = name; + } +} \ No newline at end of file From a0101f476404e56537937938808cf5acabdcffb1 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 8 Oct 2024 00:26:44 +0200 Subject: [PATCH 22/71] Implement interface transformer. --- .../compiler/asmboxpiler/ASMBoxpiler.java | 5 +- .../compiler/asmboxpiler/AsmHelper.java | 46 +- .../statement/BoxClassTransformer.java | 5 +- .../statement/BoxInterfaceTransformer.java | 514 +++++++++++++++--- 4 files changed, 485 insertions(+), 85 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index a8c5fd6d6..b7cab5291 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -14,6 +14,7 @@ import ortus.boxlang.compiler.Boxpiler; import ortus.boxlang.compiler.ClassInfo; import ortus.boxlang.compiler.ast.BoxClass; +import ortus.boxlang.compiler.ast.BoxInterface; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxScript; import ortus.boxlang.compiler.ast.visitor.QueryEscapeSingleQuoteVisitor; @@ -130,8 +131,10 @@ private void doCompileClassInfo( Transpiler transpiler, ClassInfo classInfo, Box classNode = transpiler.transpile( boxScript ); } else if ( node instanceof BoxClass boxClass ) { classNode = transpiler.transpile( boxClass ); + } else if ( node instanceof BoxInterface boxInterface ) { + classNode = transpiler.transpile( boxInterface ); } else { - throw new IllegalStateException( "Unexpected root type: " + node ); + throw new IllegalStateException( "Unexpected root type: " + node.getClass() + ": " + node ); } transpiler.getAuxiliary().forEach( consumer ); consumer.accept( classInfo.FQN(), classNode ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index d629130c9..302c793f3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -531,7 +531,7 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, null ); methodVisitor.visitCode(); // start tacking the context - new VarInsnNode( Opcodes.ALOAD, isStatic ? 0 : 1 ).accept( methodVisitor ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, isStatic ? 0 : 1 ); tracker.trackNewContext().forEach( ( node ) -> node.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, @@ -549,9 +549,9 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, nodes.forEach( node -> node.accept( methodVisitor ) ); } - if ( implicityReturnNull ) { + if ( implicityReturnNull && !returnType.equals(Type.VOID_TYPE)) { // push a null onto the stack so that we can return it if there isn't an explicity return - new InsnNode( Opcodes.ACONST_NULL ).accept( methodVisitor ); + methodVisitor.visitInsn( Opcodes.ACONST_NULL ); } methodVisitor.visitInsn( returnType.getOpcode( Opcodes.IRETURN ) ); @@ -712,4 +712,44 @@ public static MethodNode dereferenceAndInvoke( String name, Type descriptor, Typ return node; } + public static void addLazySingleton( ClassVisitor classVisitor, Type type, Consumer instantiation, Type... arguments ) { + classVisitor.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + "instance", + type.getDescriptor(), + null, + null).visitEnd(); + MethodVisitor methodVisitor = classVisitor.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "getInstance", + Type.getMethodDescriptor(type, arguments), + null, + null); + + methodVisitor.visitCode(); + Label endOfMethod = new Label(), endOfMonitor = new Label(); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMethod); + methodVisitor.visitLdcInsn( type ); + methodVisitor.visitInsn(Opcodes.MONITORENTER); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMonitor); + instantiation.accept(methodVisitor); + methodVisitor.visitLabel( endOfMonitor ); + methodVisitor.visitLdcInsn( type ); + methodVisitor.visitInsn( Opcodes.MONITOREXIT ); + methodVisitor.visitLabel(endOfMethod); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitInsn( Opcodes.ARETURN ); + + methodVisitor.visitMaxs(0, 0); + methodVisitor.visitEnd(); + } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index e64d73144..7a46e51b0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -92,7 +92,6 @@ public class BoxClassTransformer { private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; - @SuppressWarnings( "unchecked" ) public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) throws BoxRuntimeException { Source source = boxClass.getPosition().getSource(); String sourceType = transpiler.getProperty( "sourceType" ); @@ -468,7 +467,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th ).filter( l -> l.size() > 0 ).toList() ); // end import node setup - AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, true, + AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, () -> { return boxClass.getBody() .stream() @@ -487,7 +486,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th } ); - AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, true, + AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, false, () -> { List staticNodes = ( List ) transpiler.getBoxStaticInitializers() .stream() diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index b26513fa4..e14283d8b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -14,110 +14,468 @@ */ package ortus.boxlang.compiler.asmboxpiler.transformer.statement; -import java.util.ArrayList; -import java.util.List; - -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; - +import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; +import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; +import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxInterface; import ortus.boxlang.compiler.ast.Source; import ortus.boxlang.compiler.ast.SourceFile; +import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxImport; +import ortus.boxlang.compiler.ast.statement.BoxMethodDeclarationModifier; +import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.InterfaceBoxContext; +import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.runnables.IBoxRunnable; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.StaticScope; +import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.util.ResolvedFilePath; -public class BoxInterfaceTransformer { - - public static final Type CLASS_TYPE = Type.getType( Class.class ); - public static final Type CLASS_ARRAY_TYPE = Type.getType( Class[].class ); +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; - private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; +public class BoxInterfaceTransformer { - @SuppressWarnings( "unchecked" ) - public static ClassNode transpile( Transpiler transpiler, BoxInterface boxClass ) throws BoxRuntimeException { - Source source = boxClass.getPosition().getSource(); - String sourceType = transpiler.getProperty( "sourceType" ); + public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterface ) throws BoxRuntimeException { - String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() - : "unknown"; - String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; - String boxPackageName = transpiler.getProperty( "boxPackageName" ); - String rawBoxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ), boxClassName; + Source source = boxInterface.getPosition().getSource(); + String packageName = transpiler.getProperty( "packageName" ); + String boxPackageName = transpiler.getProperty( "boxPackageName" ); + String classname = transpiler.getProperty( "classname" ); + String mappingName = transpiler.getProperty( "mappingName" ); + String mappingPath = transpiler.getProperty( "mappingPath" ); + String relativePath = transpiler.getProperty( "relativePath" ); + String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; + String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; // trim leading . if exists - if ( rawBoxClassName.startsWith( "." ) ) { - boxClassName = rawBoxClassName.substring( 1 ); - } else { - boxClassName = rawBoxClassName; - } - transpiler.setProperty( "boxClassName", boxClassName ); - String mappingName = transpiler.getProperty( "mappingName" ); - String mappingPath = transpiler.getProperty( "mappingPath" ); - String relativePath = transpiler.getProperty( "relativePath" ); + String boxInterfacename = boxPackageName + (boxPackageName.isEmpty() ? "" : ".") + fileName.replace( ".bx", "" ).replace( ".cfc", "" ); + String sourceType = transpiler.getProperty( "sourceType" ); - Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) - + "/" + transpiler.getProperty( "classname" ) + ";" ); + Type type = Type.getType( "L" + packageName.replace( '.', '/' ) + + "/" + classname + ";" ); transpiler.setProperty( "classType", type.getDescriptor() ); transpiler.setProperty( "classTypeInternal", type.getInternalName() ); - List interfaces = new ArrayList<>(); - - ClassNode classNode = new ClassNode(); + ClassNode classNode = new ClassNode(); AsmHelper.init( classNode, false, type, Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ), methodVisitor -> { + } ); - }, interfaces.toArray( Type[]::new ) ); + AsmHelper.addLazySingleton( classNode, type, methodVisitor -> { + methodVisitor.visitTypeInsn( Opcodes.NEW, type.getInternalName() ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + type.getInternalName(), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); - addGetInstance( classNode, type ); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( StaticScope.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( StaticScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "staticScope", + Type.getDescriptor( StaticScope.class ) ); - return classNode; - } + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( InterfaceBoxContext.class ) ); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( InterfaceBoxContext.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, + Type.getType( IBoxContext.class ), + Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), + false ); + methodVisitor.visitVarInsn( Opcodes.ASTORE, 1 ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( InterfaceBoxContext.class ), + "pushTemplate", + Type.getMethodDescriptor( Type.getType(IBoxContext.class), Type.getType( IBoxRunnable.class ) ), + false ); + methodVisitor.visitInsn(Opcodes.POP); + + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), + "resolveSupers", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, + Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), + "staticInitializer", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); + + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor()); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, + type.getInternalName(), + "pseudoConstructor", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); + + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, + Type.getInternalName( InterfaceBoxContext.class ), + "popTemplate", + Type.getMethodDescriptor( Type.getType(ResolvedFilePath.class) ), + false ); + methodVisitor.visitInsn(Opcodes.POP); + }, Type.getType( IBoxContext.class ) ); + + AsmHelper.addStaticFieldGetter( classNode, + type, + "imports", + "getImports", + Type.getType( List.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "path", + "getRunnablePath", + Type.getType( ResolvedFilePath.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "sourceType", + "getSourceType", + Type.getType( BoxSourceType.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "annotations", + "getAnnotations", + Type.getType( IStruct.class ), + null ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "documentation", + "getDocumentation", + Type.getType( IStruct.class ), + null ); + AsmHelper.addStaticFieldGetterWithStaticGetter( classNode, + type, + "staticScope", + "getStaticScope", + "getStaticScopeStatic", + Type.getType( StaticScope.class ), + null ); + + AsmHelper.addStaticFieldGetter( classNode, + type, + "name", + "getName", + Type.getType( Key.class ), + null ); + + AsmHelper.addStaticFieldGetter( classNode, + type, + "_supers", + "getSupers", + Type.getType( List.class ), + null ); + MethodVisitor addSuper = classNode.visitMethod(Opcodes.ACC_PUBLIC, + "_addSuper", + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(BoxInterface.class)), + null, + null); + addSuper.visitCode(); + addSuper.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "_supers", + Type.getDescriptor( List.class )); + addSuper.visitVarInsn( Opcodes.ALOAD, 0 ); + addSuper.visitMethodInsn( Opcodes.INVOKEINTERFACE, + Type.getInternalName( List.class ), + "add", + Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), + true); + addSuper.visitInsn( Opcodes.POP ); + addSuper.visitInsn( Opcodes.RETURN ); + addSuper.visitMaxs(0, 0); + addSuper.visitEnd(); + + classNode.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, + "keys", + Type.getDescriptor( Key[].class ), + null, + null ).visitEnd(); + + AsmHelper.addPrviateStaticFieldGetter( classNode, + type, + "abstractMethods", + "getAbstractMethods", + Type.getType( Map.class ), + null ); + AsmHelper.addPrviateStaticFieldGetter( classNode, + type, + "defaultMethods", + "getDefaultMethods", + Type.getType( Map.class ), + null ); + + Label start = new Label(), end = new Label(), handler = new Label(); + MethodVisitor pseudoConstructor = classNode.visitMethod(Opcodes.ACC_PUBLIC, + "pseudoConstructor", + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(IBoxContext.class)), + null, + null); + pseudoConstructor.visitTryCatchBlock( start, end, handler, null ); + pseudoConstructor.visitCode(); + pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); + pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 0 ); + pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, + Type.getInternalName(IBoxContext.class), + "pushTemplate", + Type.getMethodDescriptor(Type.getType(IBoxContext.class), Type.getType(IBoxRunnable.class)), + true ); + pseudoConstructor.visitInsn( Opcodes.POP ); + pseudoConstructor.visitLabel(start); + pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 0 ); + pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); + pseudoConstructor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, + type.getInternalName(), + "_pseudoConstructor", + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(IBoxContext.class)), + false ); + pseudoConstructor.visitLabel(end); + pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); + pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, + Type.getInternalName(IBoxContext.class), + "popTemplate", + Type.getMethodDescriptor(Type.getType(ResolvedFilePath.class)), + true ); + pseudoConstructor.visitInsn( Opcodes.POP ); + pseudoConstructor.visitInsn( Opcodes.RETURN ); + pseudoConstructor.visitLabel(handler); + pseudoConstructor.visitInsn( Opcodes.POP ); + pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); + pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, + Type.getInternalName(IBoxContext.class), + "popTemplate", + Type.getMethodDescriptor(Type.getType(ResolvedFilePath.class)), + true ); + pseudoConstructor.visitInsn( Opcodes.POP ); + pseudoConstructor.visitInsn( Opcodes.RETURN ); + pseudoConstructor.visitMaxs(0, 0); + pseudoConstructor.visitEnd(); - private static void addGetInstance( ClassVisitor classVisitor, Type type ) { - FieldVisitor fieldVisitor = classVisitor.visitField( - Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, - "instance", - type.getDescriptor(), - null, - null ); - fieldVisitor.visitEnd(); - MethodVisitor methodVisitor = classVisitor.visitMethod( - Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_STATIC, - "getInstance", - Type.getMethodDescriptor( type, Type.getType( IBoxContext.class ) ), - null, - null ); - methodVisitor.visitCode(); - Label after = new Label(); - methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor() ); - methodVisitor.visitJumpInsn( Opcodes.IFNONNULL, after ); - methodVisitor.visitTypeInsn( Opcodes.NEW, type.getInternalName() ); - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - type.getInternalName(), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor() ); - methodVisitor.visitLabel( after ); - methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor() ); - methodVisitor.visitInsn( Opcodes.ARETURN ); - methodVisitor.visitMaxs( 0, 0 ); - methodVisitor.visitEnd(); + // these imports need to happen before any methods are processed - the actual nodes will be used later on in the static init section + List> imports = new ArrayList<>(); + for ( BoxImport statement : boxInterface.getImports() ) { + imports.add( transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ) ); + } + List importNodes = AsmHelper.array( Type.getType( ImportDefinition.class ), Stream.concat( + imports.stream(), + transpiler.getImports().stream().map( raw -> { + List nodes = new ArrayList<>(); + nodes.addAll( raw ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( ImportDefinition.class ), + "parse", + Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), + false ) ); + return nodes; + } ) + ).filter( l -> l.size() > 0 ).toList() ); + // end import node setup + + AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, + () -> { + return boxInterface.getBody() + .stream() + .sorted( ( a, b ) -> { + if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { + return -1; + } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { + return 1; + } + + return 0; + + } ) + .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) + .toList(); + } + ); + + AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, false, + () -> { + List staticNodes = ( List ) transpiler.getBoxStaticInitializers() + .stream() + .map( ( staticInitializer ) -> { + if ( staticInitializer == null || staticInitializer.getBody().size() == 0 ) { + return new ArrayList(); + } + + return staticInitializer.getBody() + .stream() + .map( statement -> transpiler.transform( statement, TransformerContext.NONE ) ) + .flatMap( nodes -> nodes.stream() ) + .collect( Collectors.toList() ); + } ) + .flatMap( s -> s.stream() ) + .collect( Collectors.toList() ); + + boxInterface.getDescendantsOfType( BoxFunctionDeclaration.class, ( expr ) -> { + BoxFunctionDeclaration func = ( BoxFunctionDeclaration ) expr; + + return func.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ); + } ).forEach( func -> { + staticNodes.addAll( transpiler.transform( func, TransformerContext.NONE ) ); + } ); + + return staticNodes; + } + ); + + AsmHelper.complete( classNode, type, methodVisitor -> { + AsmHelper.resolvedFilePath( methodVisitor, mappingName, mappingPath, relativePath, filePath ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "path", + Type.getDescriptor( ResolvedFilePath.class ) ); + + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + Type.getInternalName( BoxSourceType.class ), + sourceType, + Type.getDescriptor( BoxSourceType.class ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "sourceType", + Type.getDescriptor( BoxSourceType.class ) ); + + transpiler.createKey( boxInterfacename ) + .forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor )); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "name", + Type.getDescriptor( Key.class ) ); + + List annotations = transpiler.transformAnnotations( boxInterface.getAnnotations() ); + List documenation = transpiler.transformDocumentation( boxInterface.getDocumentation() ); + + methodVisitor.visitLdcInsn( transpiler.getKeys().size() ); + methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); + int index = 0; + for ( BoxExpression expression : transpiler.getKeys().values() ) { + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitLdcInsn( index++ ); + transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ) + .forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, + Type.getInternalName( Key.class ), + "of", + Type.getMethodDescriptor( Type.getType( Key.class ), Type.getType( Object.class ) ), + false ); + methodVisitor.visitInsn( Opcodes.AASTORE ); + } + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "keys", + Type.getDescriptor( Key[].class ) ); + + importNodes.forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, + Type.getInternalName( List.class ), + "of", + Type.getMethodDescriptor( Type.getType( List.class ), Type.getType( Object[].class ) ), + true ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "imports", + Type.getDescriptor( List.class ) ); + + annotations.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "annotations", + Type.getDescriptor( IStruct.class ) ); + + documenation.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "documentation", + Type.getDescriptor( IStruct.class ) ); + + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(LinkedHashMap.class)); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( LinkedHashMap.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); + + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(LinkedHashMap.class)); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( LinkedHashMap.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "defaultMethods", Type.getDescriptor( Map.class ) ); + + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(ArrayList.class)); + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( ArrayList.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "_supers", Type.getDescriptor( List.class ) ); + + } ); + + return classNode; } } From 0fac0f4861a23ba4880f651b890fdaf3883b74a4 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 8 Oct 2024 00:27:24 +0200 Subject: [PATCH 23/71] Clean up code. --- .../compiler/asmboxpiler/AsmHelper.java | 56 +- .../statement/BoxClassTransformer.java | 3 +- .../statement/BoxInterfaceTransformer.java | 559 +++++++++--------- 3 files changed, 310 insertions(+), 308 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 302c793f3..8fbefd14d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -549,7 +549,7 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, nodes.forEach( node -> node.accept( methodVisitor ) ); } - if ( implicityReturnNull && !returnType.equals(Type.VOID_TYPE)) { + if ( implicityReturnNull && !returnType.equals( Type.VOID_TYPE ) ) { // push a null onto the stack so that we can return it if there isn't an explicity return methodVisitor.visitInsn( Opcodes.ACONST_NULL ); } @@ -714,42 +714,42 @@ public static MethodNode dereferenceAndInvoke( String name, Type descriptor, Typ public static void addLazySingleton( ClassVisitor classVisitor, Type type, Consumer instantiation, Type... arguments ) { classVisitor.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, - "instance", - type.getDescriptor(), - null, - null).visitEnd(); - MethodVisitor methodVisitor = classVisitor.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, - "getInstance", - Type.getMethodDescriptor(type, arguments), - null, - null); + "instance", + type.getDescriptor(), + null, + null ).visitEnd(); + MethodVisitor methodVisitor = classVisitor.visitMethod( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "getInstance", + Type.getMethodDescriptor( type, arguments ), + null, + null ); methodVisitor.visitCode(); Label endOfMethod = new Label(), endOfMonitor = new Label(); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); - methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMethod); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); + methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMethod ); methodVisitor.visitLdcInsn( type ); - methodVisitor.visitInsn(Opcodes.MONITORENTER); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); - methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMonitor); - instantiation.accept(methodVisitor); + methodVisitor.visitInsn( Opcodes.MONITORENTER ); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); + methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMonitor ); + instantiation.accept( methodVisitor ); methodVisitor.visitLabel( endOfMonitor ); methodVisitor.visitLdcInsn( type ); methodVisitor.visitInsn( Opcodes.MONITOREXIT ); - methodVisitor.visitLabel(endOfMethod); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + methodVisitor.visitLabel( endOfMethod ); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitInsn( Opcodes.ARETURN ); - methodVisitor.visitMaxs(0, 0); + methodVisitor.visitMaxs( 0, 0 ); methodVisitor.visitEnd(); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 7a46e51b0..3cd63a8b7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -467,7 +467,8 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th ).filter( l -> l.size() > 0 ).toList() ); // end import node setup - AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, + AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, + false, () -> { return boxClass.getBody() .stream() diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index e14283d8b..c33bcc27d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -54,21 +54,21 @@ public class BoxInterfaceTransformer { public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterface ) throws BoxRuntimeException { - Source source = boxInterface.getPosition().getSource(); - String packageName = transpiler.getProperty( "packageName" ); - String boxPackageName = transpiler.getProperty( "boxPackageName" ); - String classname = transpiler.getProperty( "classname" ); - String mappingName = transpiler.getProperty( "mappingName" ); - String mappingPath = transpiler.getProperty( "mappingPath" ); - String relativePath = transpiler.getProperty( "relativePath" ); - String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; - String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; + Source source = boxInterface.getPosition().getSource(); + String packageName = transpiler.getProperty( "packageName" ); + String boxPackageName = transpiler.getProperty( "boxPackageName" ); + String classname = transpiler.getProperty( "classname" ); + String mappingName = transpiler.getProperty( "mappingName" ); + String mappingPath = transpiler.getProperty( "mappingPath" ); + String relativePath = transpiler.getProperty( "relativePath" ); + String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; + String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; // trim leading . if exists - String boxInterfacename = boxPackageName + (boxPackageName.isEmpty() ? "" : ".") + fileName.replace( ".bx", "" ).replace( ".cfc", "" ); - String sourceType = transpiler.getProperty( "sourceType" ); + String boxInterfacename = boxPackageName + ( boxPackageName.isEmpty() ? "" : "." ) + fileName.replace( ".bx", "" ).replace( ".cfc", "" ); + String sourceType = transpiler.getProperty( "sourceType" ); - Type type = Type.getType( "L" + packageName.replace( '.', '/' ) - + "/" + classname + ";" ); + Type type = Type.getType( "L" + packageName.replace( '.', '/' ) + + "/" + classname + ";" ); transpiler.setProperty( "classType", type.getDescriptor() ); transpiler.setProperty( "classTypeInternal", type.getInternalName() ); @@ -81,232 +81,232 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf methodVisitor.visitTypeInsn( Opcodes.NEW, type.getInternalName() ); methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - type.getInternalName(), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); - methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + type.getInternalName(), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( StaticScope.class ) ); methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( StaticScope.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), - false ); + Type.getInternalName( StaticScope.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), + false ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "staticScope", - Type.getDescriptor( StaticScope.class ) ); + type.getInternalName(), + "staticScope", + Type.getDescriptor( StaticScope.class ) ); methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( InterfaceBoxContext.class ) ); methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( InterfaceBoxContext.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE, - Type.getType( IBoxContext.class ), - Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), - false ); + Type.getInternalName( InterfaceBoxContext.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE, + Type.getType( IBoxContext.class ), + Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), + false ); methodVisitor.visitVarInsn( Opcodes.ASTORE, 1 ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, - Type.getInternalName( InterfaceBoxContext.class ), - "pushTemplate", - Type.getMethodDescriptor( Type.getType(IBoxContext.class), Type.getType( IBoxRunnable.class ) ), - false ); - methodVisitor.visitInsn(Opcodes.POP); - - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + Type.getInternalName( InterfaceBoxContext.class ), + "pushTemplate", + Type.getMethodDescriptor( Type.getType( IBoxContext.class ), Type.getType( IBoxRunnable.class ) ), + false ); + methodVisitor.visitInsn( Opcodes.POP ); + + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, - Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), - "resolveSupers", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), - false ); + Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), + "resolveSupers", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), - "staticInitializer", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), - false ); - - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, - type.getInternalName(), - "instance", - type.getDescriptor()); + Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), + "staticInitializer", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); + + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + "instance", + type.getDescriptor() ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, - type.getInternalName(), - "pseudoConstructor", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), - false ); + type.getInternalName(), + "pseudoConstructor", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, - Type.getInternalName( InterfaceBoxContext.class ), - "popTemplate", - Type.getMethodDescriptor( Type.getType(ResolvedFilePath.class) ), - false ); - methodVisitor.visitInsn(Opcodes.POP); + Type.getInternalName( InterfaceBoxContext.class ), + "popTemplate", + Type.getMethodDescriptor( Type.getType( ResolvedFilePath.class ) ), + false ); + methodVisitor.visitInsn( Opcodes.POP ); }, Type.getType( IBoxContext.class ) ); AsmHelper.addStaticFieldGetter( classNode, - type, - "imports", - "getImports", - Type.getType( List.class ), - null ); + type, + "imports", + "getImports", + Type.getType( List.class ), + null ); AsmHelper.addStaticFieldGetter( classNode, - type, - "path", - "getRunnablePath", - Type.getType( ResolvedFilePath.class ), - null ); + type, + "path", + "getRunnablePath", + Type.getType( ResolvedFilePath.class ), + null ); AsmHelper.addStaticFieldGetter( classNode, - type, - "sourceType", - "getSourceType", - Type.getType( BoxSourceType.class ), - null ); + type, + "sourceType", + "getSourceType", + Type.getType( BoxSourceType.class ), + null ); AsmHelper.addStaticFieldGetter( classNode, - type, - "annotations", - "getAnnotations", - Type.getType( IStruct.class ), - null ); + type, + "annotations", + "getAnnotations", + Type.getType( IStruct.class ), + null ); AsmHelper.addStaticFieldGetter( classNode, - type, - "documentation", - "getDocumentation", - Type.getType( IStruct.class ), - null ); + type, + "documentation", + "getDocumentation", + Type.getType( IStruct.class ), + null ); AsmHelper.addStaticFieldGetterWithStaticGetter( classNode, - type, - "staticScope", - "getStaticScope", - "getStaticScopeStatic", - Type.getType( StaticScope.class ), - null ); + type, + "staticScope", + "getStaticScope", + "getStaticScopeStatic", + Type.getType( StaticScope.class ), + null ); AsmHelper.addStaticFieldGetter( classNode, - type, - "name", - "getName", - Type.getType( Key.class ), - null ); + type, + "name", + "getName", + Type.getType( Key.class ), + null ); AsmHelper.addStaticFieldGetter( classNode, - type, - "_supers", - "getSupers", - Type.getType( List.class ), - null ); - MethodVisitor addSuper = classNode.visitMethod(Opcodes.ACC_PUBLIC, - "_addSuper", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(BoxInterface.class)), - null, - null); + type, + "_supers", + "getSupers", + Type.getType( List.class ), + null ); + MethodVisitor addSuper = classNode.visitMethod( Opcodes.ACC_PUBLIC, + "_addSuper", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( BoxInterface.class ) ), + null, + null ); addSuper.visitCode(); addSuper.visitFieldInsn( Opcodes.GETSTATIC, - type.getInternalName(), - "_supers", - Type.getDescriptor( List.class )); + type.getInternalName(), + "_supers", + Type.getDescriptor( List.class ) ); addSuper.visitVarInsn( Opcodes.ALOAD, 0 ); addSuper.visitMethodInsn( Opcodes.INVOKEINTERFACE, - Type.getInternalName( List.class ), - "add", - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), - true); + Type.getInternalName( List.class ), + "add", + Type.getMethodDescriptor( Type.BOOLEAN_TYPE, Type.getType( Object.class ) ), + true ); addSuper.visitInsn( Opcodes.POP ); addSuper.visitInsn( Opcodes.RETURN ); - addSuper.visitMaxs(0, 0); + addSuper.visitMaxs( 0, 0 ); addSuper.visitEnd(); classNode.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, - "keys", - Type.getDescriptor( Key[].class ), - null, - null ).visitEnd(); + "keys", + Type.getDescriptor( Key[].class ), + null, + null ).visitEnd(); AsmHelper.addPrviateStaticFieldGetter( classNode, - type, - "abstractMethods", - "getAbstractMethods", - Type.getType( Map.class ), - null ); + type, + "abstractMethods", + "getAbstractMethods", + Type.getType( Map.class ), + null ); AsmHelper.addPrviateStaticFieldGetter( classNode, - type, - "defaultMethods", - "getDefaultMethods", - Type.getType( Map.class ), - null ); - - Label start = new Label(), end = new Label(), handler = new Label(); - MethodVisitor pseudoConstructor = classNode.visitMethod(Opcodes.ACC_PUBLIC, - "pseudoConstructor", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(IBoxContext.class)), - null, - null); + type, + "defaultMethods", + "getDefaultMethods", + Type.getType( Map.class ), + null ); + + Label start = new Label(), end = new Label(), handler = new Label(); + MethodVisitor pseudoConstructor = classNode.visitMethod( Opcodes.ACC_PUBLIC, + "pseudoConstructor", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + null, + null ); pseudoConstructor.visitTryCatchBlock( start, end, handler, null ); pseudoConstructor.visitCode(); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 0 ); pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, - Type.getInternalName(IBoxContext.class), - "pushTemplate", - Type.getMethodDescriptor(Type.getType(IBoxContext.class), Type.getType(IBoxRunnable.class)), - true ); + Type.getInternalName( IBoxContext.class ), + "pushTemplate", + Type.getMethodDescriptor( Type.getType( IBoxContext.class ), Type.getType( IBoxRunnable.class ) ), + true ); pseudoConstructor.visitInsn( Opcodes.POP ); - pseudoConstructor.visitLabel(start); + pseudoConstructor.visitLabel( start ); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 0 ); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); pseudoConstructor.visitMethodInsn( Opcodes.INVOKEVIRTUAL, - type.getInternalName(), - "_pseudoConstructor", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(IBoxContext.class)), - false ); - pseudoConstructor.visitLabel(end); + type.getInternalName(), + "_pseudoConstructor", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), + false ); + pseudoConstructor.visitLabel( end ); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, - Type.getInternalName(IBoxContext.class), - "popTemplate", - Type.getMethodDescriptor(Type.getType(ResolvedFilePath.class)), - true ); + Type.getInternalName( IBoxContext.class ), + "popTemplate", + Type.getMethodDescriptor( Type.getType( ResolvedFilePath.class ) ), + true ); pseudoConstructor.visitInsn( Opcodes.POP ); pseudoConstructor.visitInsn( Opcodes.RETURN ); - pseudoConstructor.visitLabel(handler); + pseudoConstructor.visitLabel( handler ); pseudoConstructor.visitInsn( Opcodes.POP ); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, - Type.getInternalName(IBoxContext.class), - "popTemplate", - Type.getMethodDescriptor(Type.getType(ResolvedFilePath.class)), - true ); + Type.getInternalName( IBoxContext.class ), + "popTemplate", + Type.getMethodDescriptor( Type.getType( ResolvedFilePath.class ) ), + true ); pseudoConstructor.visitInsn( Opcodes.POP ); pseudoConstructor.visitInsn( Opcodes.RETURN ); - pseudoConstructor.visitMaxs(0, 0); + pseudoConstructor.visitMaxs( 0, 0 ); pseudoConstructor.visitEnd(); // these imports need to happen before any methods are processed - the actual nodes will be used later on in the static init section @@ -315,94 +315,95 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf imports.add( transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ) ); } List importNodes = AsmHelper.array( Type.getType( ImportDefinition.class ), Stream.concat( - imports.stream(), - transpiler.getImports().stream().map( raw -> { - List nodes = new ArrayList<>(); - nodes.addAll( raw ); - nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, - Type.getInternalName( ImportDefinition.class ), - "parse", - Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), - false ) ); - return nodes; - } ) + imports.stream(), + transpiler.getImports().stream().map( raw -> { + List nodes = new ArrayList<>(); + nodes.addAll( raw ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( ImportDefinition.class ), + "parse", + Type.getMethodDescriptor( Type.getType( ImportDefinition.class ), Type.getType( String.class ) ), + false ) ); + return nodes; + } ) ).filter( l -> l.size() > 0 ).toList() ); // end import node setup - AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, - () -> { - return boxInterface.getBody() - .stream() - .sorted( ( a, b ) -> { - if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { - return -1; - } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { - return 1; - } - - return 0; - - } ) - .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) - .toList(); - } + AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, + false, + () -> { + return boxInterface.getBody() + .stream() + .sorted( ( a, b ) -> { + if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { + return -1; + } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { + return 1; + } + + return 0; + + } ) + .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) + .toList(); + } ); AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, false, - () -> { - List staticNodes = ( List ) transpiler.getBoxStaticInitializers() - .stream() - .map( ( staticInitializer ) -> { - if ( staticInitializer == null || staticInitializer.getBody().size() == 0 ) { - return new ArrayList(); - } - - return staticInitializer.getBody() - .stream() - .map( statement -> transpiler.transform( statement, TransformerContext.NONE ) ) - .flatMap( nodes -> nodes.stream() ) - .collect( Collectors.toList() ); - } ) - .flatMap( s -> s.stream() ) - .collect( Collectors.toList() ); - - boxInterface.getDescendantsOfType( BoxFunctionDeclaration.class, ( expr ) -> { - BoxFunctionDeclaration func = ( BoxFunctionDeclaration ) expr; - - return func.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ); - } ).forEach( func -> { - staticNodes.addAll( transpiler.transform( func, TransformerContext.NONE ) ); - } ); - - return staticNodes; - } + () -> { + List staticNodes = ( List ) transpiler.getBoxStaticInitializers() + .stream() + .map( ( staticInitializer ) -> { + if ( staticInitializer == null || staticInitializer.getBody().size() == 0 ) { + return new ArrayList(); + } + + return staticInitializer.getBody() + .stream() + .map( statement -> transpiler.transform( statement, TransformerContext.NONE ) ) + .flatMap( nodes -> nodes.stream() ) + .collect( Collectors.toList() ); + } ) + .flatMap( s -> s.stream() ) + .collect( Collectors.toList() ); + + boxInterface.getDescendantsOfType( BoxFunctionDeclaration.class, ( expr ) -> { + BoxFunctionDeclaration func = ( BoxFunctionDeclaration ) expr; + + return func.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ); + } ).forEach( func -> { + staticNodes.addAll( transpiler.transform( func, TransformerContext.NONE ) ); + } ); + + return staticNodes; + } ); AsmHelper.complete( classNode, type, methodVisitor -> { AsmHelper.resolvedFilePath( methodVisitor, mappingName, mappingPath, relativePath, filePath ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "path", - Type.getDescriptor( ResolvedFilePath.class ) ); + type.getInternalName(), + "path", + Type.getDescriptor( ResolvedFilePath.class ) ); methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, - Type.getInternalName( BoxSourceType.class ), - sourceType, - Type.getDescriptor( BoxSourceType.class ) ); + Type.getInternalName( BoxSourceType.class ), + sourceType, + Type.getDescriptor( BoxSourceType.class ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "sourceType", - Type.getDescriptor( BoxSourceType.class ) ); + type.getInternalName(), + "sourceType", + Type.getDescriptor( BoxSourceType.class ) ); transpiler.createKey( boxInterfacename ) - .forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor )); + .forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "name", - Type.getDescriptor( Key.class ) ); + type.getInternalName(), + "name", + Type.getDescriptor( Key.class ) ); - List annotations = transpiler.transformAnnotations( boxInterface.getAnnotations() ); - List documenation = transpiler.transformDocumentation( boxInterface.getDocumentation() ); + List annotations = transpiler.transformAnnotations( boxInterface.getAnnotations() ); + List documenation = transpiler.transformDocumentation( boxInterface.getDocumentation() ); methodVisitor.visitLdcInsn( transpiler.getKeys().size() ); methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); @@ -411,67 +412,67 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitLdcInsn( index++ ); transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ) - .forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); + .forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( Key.class ), - "of", - Type.getMethodDescriptor( Type.getType( Key.class ), Type.getType( Object.class ) ), - false ); + Type.getInternalName( Key.class ), + "of", + Type.getMethodDescriptor( Type.getType( Key.class ), Type.getType( Object.class ) ), + false ); methodVisitor.visitInsn( Opcodes.AASTORE ); } methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "keys", - Type.getDescriptor( Key[].class ) ); + type.getInternalName(), + "keys", + Type.getDescriptor( Key[].class ) ); importNodes.forEach( node -> node.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( List.class ), - "of", - Type.getMethodDescriptor( Type.getType( List.class ), Type.getType( Object[].class ) ), - true ); + Type.getInternalName( List.class ), + "of", + Type.getMethodDescriptor( Type.getType( List.class ), Type.getType( Object[].class ) ), + true ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "imports", - Type.getDescriptor( List.class ) ); + type.getInternalName(), + "imports", + Type.getDescriptor( List.class ) ); annotations.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "annotations", - Type.getDescriptor( IStruct.class ) ); + type.getInternalName(), + "annotations", + Type.getDescriptor( IStruct.class ) ); documenation.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "documentation", - Type.getDescriptor( IStruct.class ) ); + type.getInternalName(), + "documentation", + Type.getDescriptor( IStruct.class ) ); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(LinkedHashMap.class)); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( LinkedHashMap.class ) ); methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( LinkedHashMap.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); + Type.getInternalName( LinkedHashMap.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(LinkedHashMap.class)); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( LinkedHashMap.class ) ); methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( LinkedHashMap.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); + Type.getInternalName( LinkedHashMap.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "defaultMethods", Type.getDescriptor( Map.class ) ); - methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(ArrayList.class)); + methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ArrayList.class ) ); methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, - Type.getInternalName( ArrayList.class ), - "", - Type.getMethodDescriptor( Type.VOID_TYPE ), - false ); + Type.getInternalName( ArrayList.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "_supers", Type.getDescriptor( List.class ) ); } ); From 2d2b60b8a7d556a0a06d97717903cad436f35e9e Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 8 Oct 2024 01:11:13 +0200 Subject: [PATCH 24/71] Clean up code and minor bug fixes. --- .../compiler/asmboxpiler/AsmHelper.java | 29 ++++++++++++++----- .../statement/BoxClassTransformer.java | 2 +- .../statement/BoxInterfaceTransformer.java | 24 +++++++-------- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 8fbefd14d..dad67e899 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -348,7 +348,12 @@ private static void addGetInstance( ClassVisitor classVisitor, Type type ) { public static void addStaticFieldGetterWithStaticGetter( ClassVisitor classVisitor, Type type, String field, String method, String staticMethod, Type property, Object value ) { - addStaticFieldGetter( classVisitor, type, field, method, property, value ); + addStaticFieldGetterWithStaticGetter( classVisitor, type, field, method, staticMethod, property, value, true ); + } + + public static void addStaticFieldGetterWithStaticGetter( ClassVisitor classVisitor, Type type, String field, String method, String staticMethod, + Type property, Object value, boolean isFinal ) { + addStaticFieldGetter( classVisitor, type, field, method, property, value, isFinal ); MethodVisitor methodVisitor = classVisitor.visitMethod( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, staticMethod, Type.getMethodDescriptor( property ), @@ -365,7 +370,11 @@ public static void addStaticFieldGetterWithStaticGetter( ClassVisitor classVisit } public static void addStaticFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value ) { - FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_PUBLIC, + addStaticFieldGetter( classVisitor, type, field, method, property, value, true ); + } + + public static void addStaticFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value, boolean isFinal ) { + FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_STATIC | ( isFinal ? Opcodes.ACC_FINAL : 0 ) | Opcodes.ACC_PUBLIC, field, property.getDescriptor(), null, @@ -725,21 +734,24 @@ public static void addLazySingleton( ClassVisitor classVisitor, Type type, Consu null ); methodVisitor.visitCode(); - Label endOfMethod = new Label(), endOfMonitor = new Label(); + Label endOfMethod = new Label(); methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, type.getInternalName(), "instance", type.getDescriptor() ); - methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMethod ); + methodVisitor.visitJumpInsn( Opcodes.IFNONNULL, endOfMethod ); methodVisitor.visitLdcInsn( type ); methodVisitor.visitInsn( Opcodes.MONITORENTER ); methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, type.getInternalName(), "instance", type.getDescriptor() ); - methodVisitor.visitJumpInsn( Opcodes.IFNULL, endOfMonitor ); + Label start = new Label(), end = new Label(), handler = new Label(); + methodVisitor.visitTryCatchBlock( start, end, handler, null ); + methodVisitor.visitLabel( start ); + methodVisitor.visitJumpInsn( Opcodes.IFNONNULL, end ); instantiation.accept( methodVisitor ); - methodVisitor.visitLabel( endOfMonitor ); + methodVisitor.visitLabel( end ); methodVisitor.visitLdcInsn( type ); methodVisitor.visitInsn( Opcodes.MONITOREXIT ); methodVisitor.visitLabel( endOfMethod ); @@ -748,7 +760,10 @@ public static void addLazySingleton( ClassVisitor classVisitor, Type type, Consu "instance", type.getDescriptor() ); methodVisitor.visitInsn( Opcodes.ARETURN ); - + methodVisitor.visitLabel( handler ); + methodVisitor.visitLdcInsn( type ); + methodVisitor.visitInsn( Opcodes.MONITOREXIT ); + methodVisitor.visitInsn( Opcodes.ATHROW ); methodVisitor.visitMaxs( 0, 0 ); methodVisitor.visitEnd(); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 3cd63a8b7..88bf2123f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -48,7 +48,6 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxClass; import ortus.boxlang.compiler.ast.BoxExpression; -import ortus.boxlang.compiler.ast.BoxInterface; import ortus.boxlang.compiler.ast.Source; import ortus.boxlang.compiler.ast.SourceFile; import ortus.boxlang.compiler.ast.expression.BoxStringLiteral; @@ -65,6 +64,7 @@ import ortus.boxlang.runtime.dynamic.IReferenceable; import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService; import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.runnables.BoxInterface; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.ClassVariablesScope; import ortus.boxlang.runtime.scopes.Key; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index c33bcc27d..8cc21494a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -147,7 +147,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( ortus.boxlang.runtime.runnables.BoxInterface.class ), + type.getInternalName(), "staticInitializer", Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), false ); @@ -208,7 +208,8 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf "getStaticScope", "getStaticScopeStatic", Type.getType( StaticScope.class ), - null ); + null, + false ); AsmHelper.addStaticFieldGetter( classNode, type, @@ -225,7 +226,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf null ); MethodVisitor addSuper = classNode.visitMethod( Opcodes.ACC_PUBLIC, "_addSuper", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( BoxInterface.class ) ), + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( ortus.boxlang.runtime.runnables.BoxInterface.class ) ), null, null ); addSuper.visitCode(); @@ -297,7 +298,6 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf pseudoConstructor.visitInsn( Opcodes.POP ); pseudoConstructor.visitInsn( Opcodes.RETURN ); pseudoConstructor.visitLabel( handler ); - pseudoConstructor.visitInsn( Opcodes.POP ); pseudoConstructor.visitVarInsn( Opcodes.ALOAD, 1 ); pseudoConstructor.visitMethodInsn( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), @@ -305,7 +305,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf Type.getMethodDescriptor( Type.getType( ResolvedFilePath.class ) ), true ); pseudoConstructor.visitInsn( Opcodes.POP ); - pseudoConstructor.visitInsn( Opcodes.RETURN ); + pseudoConstructor.visitInsn( Opcodes.ATHROW ); pseudoConstructor.visitMaxs( 0, 0 ); pseudoConstructor.visitEnd(); @@ -395,15 +395,9 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf "sourceType", Type.getDescriptor( BoxSourceType.class ) ); - transpiler.createKey( boxInterfacename ) - .forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); - methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, - type.getInternalName(), - "name", - Type.getDescriptor( Key.class ) ); - List annotations = transpiler.transformAnnotations( boxInterface.getAnnotations() ); List documenation = transpiler.transformDocumentation( boxInterface.getDocumentation() ); + List name = transpiler.createKey( boxInterfacename ); methodVisitor.visitLdcInsn( transpiler.getKeys().size() ); methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); @@ -425,6 +419,12 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf "keys", Type.getDescriptor( Key[].class ) ); + name.forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "name", + Type.getDescriptor( Key.class ) ); + importNodes.forEach( node -> node.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName( List.class ), From 0f7a2977ce61ee5eec897b009a21b85c87c36378 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 8 Oct 2024 22:32:28 -0500 Subject: [PATCH 25/71] Coninue working on ColdBox - add test for closure in component --- .../compiler/asmboxpiler/AsmHelper.java | 69 ++++++++++++++++++- .../compiler/asmboxpiler/AsmTranspiler.java | 36 ++++++++-- .../compiler/asmboxpiler/Transpiler.java | 10 +++ .../expression/BoxClosureTransformer.java | 5 +- .../statement/BoxClassTransformer.java | 20 +++++- .../statement/BoxTryTransformer.java | 2 + .../asm/integration/ControllerTest.java | 18 +++++ 7 files changed, 149 insertions(+), 11 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index dad67e899..aab45a3ce 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -24,10 +24,10 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; -import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxStatement; import ortus.boxlang.compiler.ast.expression.BoxArgument; import ortus.boxlang.runtime.BoxRuntime; @@ -39,12 +39,79 @@ import ortus.boxlang.runtime.runnables.BoxClassSupport; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.DefaultExpression; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.util.ResolvedFilePath; public class AsmHelper { + public static List getDefaultExpression( AsmTranspiler transpiler, BoxExpression body ) { + Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + + "/" + transpiler.getProperty( "classname" ) + + "$Lambda_" + transpiler.incrementAndGetLambdaCounter() + ";" ); + + ClassNode classNode = new ClassNode(); + + classNode.visit( + Opcodes.V17, + Opcodes.ACC_PUBLIC, + type.getInternalName(), + null, + Type.getInternalName( Object.class ), + new String[] { Type.getInternalName( DefaultExpression.class ) } ); + + MethodVisitor initVisitor = classNode.visitMethod( Opcodes.ACC_PUBLIC, + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + null, + null ); + initVisitor.visitCode(); + initVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + initVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, + Type.getInternalName( Object.class ), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ); + initVisitor.visitInsn( Opcodes.RETURN ); + initVisitor.visitEnd(); + + MethodContextTracker t = new MethodContextTracker( false ); + transpiler.addMethodContextTracker( t ); + // Object evaluate( IBoxContext context ); + MethodVisitor methodVisitor = classNode.visitMethod( + Opcodes.ACC_PUBLIC, + "evaluate", + Type.getMethodDescriptor( Type.getType( Object.class ), Type.getType( IBoxContext.class ) ), + null, + null ); + methodVisitor.visitCode(); + + t.trackNewContext(); + + transpiler.transform( body, TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) + .forEach( ( ins ) -> ins.accept( methodVisitor ) ); + + methodVisitor.visitInsn( Opcodes.ARETURN ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + + transpiler.popMethodContextTracker(); + + transpiler.setAuxiliary( type.getClassName(), classNode ); + + List nodes = new ArrayList(); + + nodes.add( new TypeInsnNode( Opcodes.NEW, type.getInternalName() ) ); + nodes.add( new InsnNode( Opcodes.DUP ) ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESPECIAL, + type.getInternalName(), + "", + Type.getMethodDescriptor( Type.VOID_TYPE ), + false ) ); + return nodes; + } + public static List callinvokeFunction( Transpiler transpiler, Type invokeType, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 97adfcd48..69f8777f8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -145,6 +145,7 @@ import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.AbstractFunction; import ortus.boxlang.runtime.types.Argument; +import ortus.boxlang.runtime.types.DefaultExpression; import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Property; @@ -382,12 +383,22 @@ public List> transformProperties( Type declaringType, Lis .findFirst() .orElse( null ); - // Process the default value - List init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); - if ( defaultAnnotation != null && defaultAnnotation.getValue() != null ) { - init = transform( ( BoxNode ) defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); + List defaultLiteral = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + List defaultExpression = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + if ( defaultAnnotation.getValue() != null ) { + if ( defaultAnnotation.getValue().isLiteral() ) { + defaultLiteral = transform( defaultAnnotation.getValue(), TransformerContext.NONE ); + } else { + defaultExpression = AsmHelper.getDefaultExpression( this, defaultAnnotation.getValue() ); + } } + // Process the default value + // List init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + // if ( defaultAnnotation != null && defaultAnnotation.getValue() != null ) { + // init = transform( ( BoxNode ) defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); + // } + // name and type must be simple values String name; String type; @@ -414,7 +425,10 @@ public List> transformProperties( Type declaringType, Lis javaExpr.add( new InsnNode( Opcodes.DUP ) ); javaExpr.addAll( jNameKey ); javaExpr.add( new LdcInsnNode( type ) ); - javaExpr.addAll( init ); + javaExpr.addAll( defaultLiteral ); + + // create the default expression + javaExpr.addAll( defaultExpression ); javaExpr.addAll( transformAnnotations( finalAnnotations ) ); javaExpr.addAll( documentationStruct ); @@ -426,8 +440,16 @@ public List> transformProperties( Type declaringType, Lis javaExpr.add( new MethodInsnNode( Opcodes.INVOKESPECIAL, Type.getInternalName( Property.class ), "", - Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( Key.class ), Type.getType( String.class ), Type.getType( Object.class ), - Type.getType( IStruct.class ), Type.getType( IStruct.class ), Type.getType( BoxSourceType.class ) ), + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getType( Key.class ), + Type.getType( String.class ), + Type.getType( Object.class ), + Type.getType( DefaultExpression.class ), + Type.getType( IStruct.class ), + Type.getType( IStruct.class ), + Type.getType( BoxSourceType.class ) + ), false ) ); members.add( jNameKey ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index a08d34cf0..60daf1ed6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -66,6 +66,14 @@ public boolean isInsideComponent() { return componentCounter > 0; } + public int getComponentCounter() { + return componentCounter; + } + + public void setComponentCounter( int counter ) { + componentCounter = counter; + } + public void incrementComponentCounter() { componentCounter++; } @@ -227,6 +235,7 @@ public List transformDocumentation( List transformAnnotations( List annotations, Boolean defaultTrue, boolean onlyLiteralValues ) { List> members = new ArrayList<>(); + annotations.forEach( annotation -> { List annotationKey = createKey( annotation.getKey().getValue() ); members.add( annotationKey ); @@ -257,6 +266,7 @@ public List transformAnnotations( List annotati } members.add( value ); } ); + if ( annotations.isEmpty() ) { return List.of( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( Struct.class ) ), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java index b34b48770..7ad7596ac 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java @@ -140,12 +140,15 @@ public List transform( BoxNode node, TransformerContext contex "getSourceType", Type.getType( BoxSourceType.class ) ); - boolean isBlock = boxClosure.getBody() instanceof BoxStatementBlock; + boolean isBlock = boxClosure.getBody() instanceof BoxStatementBlock; + int componentCounter = transpiler.getComponentCounter(); + transpiler.setComponentCounter( 0 ); AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, transpiler, isBlock, () -> boxClosure.getBody().getChildren().stream().flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) .toList() ); + transpiler.setComponentCounter( componentCounter ); AsmHelper.complete( classNode, type, methodVisitor -> { methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 88bf2123f..10a7d50b5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -41,6 +41,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; @@ -64,6 +65,7 @@ import ortus.boxlang.runtime.dynamic.IReferenceable; import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService; import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.runnables.BoxClassSupport; import ortus.boxlang.runtime.runnables.BoxInterface; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.ClassVariablesScope; @@ -470,7 +472,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, () -> { - return boxClass.getBody() + List psuedoBody = boxClass.getBody() .stream() .sorted( ( a, b ) -> { if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { @@ -483,7 +485,21 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th } ) .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) - .toList(); + .collect( Collectors.toList() ); + + psuedoBody.add( new VarInsnNode( Opcodes.ALOAD, 0 ) ); + psuedoBody.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); + + psuedoBody.add( + new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( BoxClassSupport.class ), + "defaultProperties", + Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IClassRunnable.class ), Type.getType( IBoxContext.class ) ), + false + ) + ); + + return psuedoBody; } ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java index 464d527ba..eade0d83f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java @@ -146,6 +146,8 @@ private List generateBodyNodesWithInlinedFinally( if ( inBetweenNode != null ) { nodes.add( inBetweenNode ); } + + return nodes; } nodes.addAll( AsmHelper.transformBodyExpressions( diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java index 31ffcb45d..c2fd0e97a 100644 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ b/src/test/java/TestCases/asm/integration/ControllerTest.java @@ -97,4 +97,22 @@ public void testTryCatchLabelStack() { context ); } + @Test + public void testNestedLFunctionInComponent() { + + instance.executeStatement( + """ + lock name = "what" timeout=300 { + t = function(){ + return "test"; + } + + result = t(); + } + """, + context ); + + assertThat( variables.get( result ) ).isEqualTo( "test" ); + } + } From 29b92350c405977ccb780e44726f11ea2314d4f8 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Fri, 11 Oct 2024 15:26:53 +0000 Subject: [PATCH 26/71] Version bump --- changelog.md | 6 +++++- gradle.properties | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index ca542ba25..952e02d9e 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0-beta18] - 2024-10-11 + ## [1.0.0-beta17] - 2024-10-04 ## [1.0.0-beta16] - 2024-09-27 @@ -47,7 +49,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0-beta1] - 2024-06-14 -[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta17...HEAD +[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta18...HEAD + +[1.0.0-beta18]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta17...v1.0.0-beta18 [1.0.0-beta17]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta16...v1.0.0-beta17 diff --git a/gradle.properties b/gradle.properties index c94a19d18..3689f5ebf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -#Fri Oct 04 18:10:20 UTC 2024 +#Fri Oct 11 15:26:50 UTC 2024 antlrVersion=4.13.1 jdkVersion=21 -version=1.0.0-beta18 +version=1.0.0-beta19 From a2d245a94be7e66c8186540c14728b5b45573445 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 11 Oct 2024 10:53:56 -0500 Subject: [PATCH 27/71] BL-635 --- .../application/ApplicationClassListener.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java b/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java index 474e487df..4a12f411f 100644 --- a/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java @@ -81,7 +81,7 @@ public IClassRunnable getListenerClass() { public void onRequest( IBoxContext context, Object[] args ) { super.onRequest( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onRequest ) ) { + if ( listener.getThisScope().containsKey( Key.onRequest ) ) { listener.dereferenceAndInvoke( context, Key.onRequest, args, false ); } else { // Default includes template inside the Class's context @@ -105,7 +105,7 @@ public void onRequest( IBoxContext context, Object[] args ) { public boolean onRequestStart( IBoxContext context, Object[] args ) { super.onRequestStart( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onRequestStart ) ) { + if ( listener.getThisScope().containsKey( Key.onRequestStart ) ) { Object result = listener.dereferenceAndInvoke( context, Key.onRequestStart, args, false ); if ( result != null ) { return BooleanCaster.cast( result ); @@ -121,7 +121,7 @@ public boolean onRequestStart( IBoxContext context, Object[] args ) { public void onSessionStart( IBoxContext context, Object[] args ) { super.onSessionStart( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onSessionStart ) ) { + if ( listener.getThisScope().containsKey( Key.onSessionStart ) ) { listener.dereferenceAndInvoke( context, Key.onSessionStart, args, false ); } } @@ -130,7 +130,7 @@ public void onSessionStart( IBoxContext context, Object[] args ) { public void onApplicationStart( IBoxContext context, Object[] args ) { super.onApplicationStart( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onApplicationStart ) ) { + if ( listener.getThisScope().containsKey( Key.onApplicationStart ) ) { listener.dereferenceAndInvoke( context, Key.onApplicationStart, args, false ); } } @@ -139,7 +139,7 @@ public void onApplicationStart( IBoxContext context, Object[] args ) { public void onRequestEnd( IBoxContext context, Object[] args ) { super.onRequestEnd( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onRequestEnd ) ) { + if ( listener.getThisScope().containsKey( Key.onRequestEnd ) ) { listener.dereferenceAndInvoke( context, Key.onRequestEnd, args, false ); } } @@ -148,7 +148,7 @@ public void onRequestEnd( IBoxContext context, Object[] args ) { public void onAbort( IBoxContext context, Object[] args ) { super.onAbort( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onAbort ) ) { + if ( listener.getThisScope().containsKey( Key.onAbort ) ) { listener.dereferenceAndInvoke( context, Key.onAbort, args, false ); } } @@ -157,7 +157,7 @@ public void onAbort( IBoxContext context, Object[] args ) { public void onSessionEnd( IBoxContext context, Object[] args ) { super.onSessionEnd( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onSessionEnd ) ) { + if ( listener.getThisScope().containsKey( Key.onSessionEnd ) ) { listener.dereferenceAndInvoke( context, Key.onSessionEnd, args, false ); } } @@ -166,7 +166,7 @@ public void onSessionEnd( IBoxContext context, Object[] args ) { public void onApplicationEnd( IBoxContext context, Object[] args ) { super.onApplicationEnd( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onApplicationEnd ) ) { + if ( listener.getThisScope().containsKey( Key.onApplicationEnd ) ) { listener.dereferenceAndInvoke( context, Key.onApplicationEnd, args, false ); } } @@ -175,7 +175,7 @@ public void onApplicationEnd( IBoxContext context, Object[] args ) { public boolean onError( IBoxContext context, Object[] args ) { super.onError( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onError ) ) { + if ( listener.getThisScope().containsKey( Key.onError ) ) { listener.dereferenceAndInvoke( context, Key.onError, args, false ); return true; } else { @@ -187,7 +187,7 @@ public boolean onError( IBoxContext context, Object[] args ) { public boolean onMissingTemplate( IBoxContext context, Object[] args ) { super.onMissingTemplate( context, args ); - if ( listener.getVariablesScope().containsKey( Key.onMissingTemplate ) ) { + if ( listener.getThisScope().containsKey( Key.onMissingTemplate ) ) { Object result = listener.dereferenceAndInvoke( context, Key.onMissingTemplate, args, false ); if ( result != null ) { return BooleanCaster.cast( result ); @@ -220,7 +220,7 @@ public void onClassRequest( IBoxContext context, Object[] args ) { * Any returnFormat annotation on any other method called during the request is ignored, so long as it wasn't the method specified in the URL * Lucee sets the response content type based on the return format, but Adobe doesn't appear to set any response headers at all based on the return format */ - if ( listener.getVariablesScope().get( Key.onClassRequest ) instanceof Function ) { + if ( listener.getThisScope().get( Key.onClassRequest ) instanceof Function ) { invokeClassRequest( context, listener, Key.onClassRequest.getName(), null, args, returnFormat, false ); } else { invokeClassRequest( From feed00cb9a22eb7727182743973d33dad7170ed1 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Sun, 13 Oct 2024 06:46:05 -0400 Subject: [PATCH 28/71] JDBC - Allow retrieving original datasource name for easier matching on entity 'datasource' annotations --- .../runtime/config/segments/DatasourceConfig.java | 9 +++++++++ src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java index 2675d8b9b..d5ba91757 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java @@ -325,6 +325,15 @@ public Key getUniqueName() { return Key.of( uniqueName.toString() ); } + /** + * Get the original name of the datasource - this is NOT unique and should not be used for identification. + * + * @return The original name of the datasource. + */ + public String getOriginalName() { + return this.name.getName(); + } + /** * Processes the state of the configuration segment from the configuration struct. *

diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java b/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java index 111872a5d..b80866d0d 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java @@ -113,6 +113,15 @@ public static DataSource fromStruct( Key name, IStruct properties ) { * -------------------------------------------------------------------------- */ + /** + * Get the original name of the datasource - this is NOT unique and should not be used for identification. + * + * @return The original name of the datasource. + */ + public String getOriginalName() { + return this.configuration.getOriginalName(); + } + /** * Get a unique datasource name which includes a hash of the properties * Following the pattern: bx_{name}_{properties_hash} From 979c99758aef67ce7da1dbcfb15f91fd6a2468b5 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Sun, 13 Oct 2024 07:32:37 -0400 Subject: [PATCH 29/71] JDBC - Fix ConnectionManager datasource registration to use original name DatasourceService is a singleton across the entire runtime and should use the unique datasource name. However, the ConnectionManager is per-request or per-thread even, and should use the "configured" datasource name to support lookups by name. --- src/main/java/ortus/boxlang/runtime/jdbc/ConnectionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/ConnectionManager.java b/src/main/java/ortus/boxlang/runtime/jdbc/ConnectionManager.java index d53001366..335ef45e6 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/ConnectionManager.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/ConnectionManager.java @@ -454,7 +454,7 @@ public DataSource getDatasource( Key datasourceName ) { * @return The datasource object */ public DataSource register( DataSource target ) { - this.datasources.put( target.getUniqueName(), target ); + this.datasources.put( target.getConfiguration().name, target ); return target; } From 3bce7e0229c51a9ea1c5f79a42bebf2ab8debeed Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 14 Oct 2024 16:48:41 +0200 Subject: [PATCH 30/71] BL-646 #resolve Ability to import/create BoxLang classes from modules via direct `@{moduleName}` notation. --- .../runtime/loader/resolvers/BoxResolver.java | 181 +++++++++++++----- .../runtime/modules/ModuleRecordTest.java | 13 ++ 2 files changed, 146 insertions(+), 48 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index 4cacdc3de..e87198381 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -32,9 +32,12 @@ import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.modules.ModuleRecord; import ortus.boxlang.runtime.runnables.RunnableLoader; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.services.ModuleService; import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.util.FileSystemUtil; import ortus.boxlang.runtime.util.ResolvedFilePath; @@ -167,64 +170,120 @@ public Optional resolve( IBoxContext context, String name, List findFromModules( IBoxContext context, String name, List imports ) { - return findFromModules( context, name, imports, true ); + public Optional findFromModules( IBoxContext context, String fullyQualifiedName, List imports ) { + return findFromModules( context, fullyQualifiedName, imports, true ); } /** - * Load a class from the registered runtime module class loaders + * This tries to load a BoxLang class from registered modules using the {@code fullyQualifiedName@moduleName} provided. + * If the class is not found, it will return an empty Optional. + *

+ * If there is no module name, then we return an empty Optional, because it will delegate to the {@link #findFromLocal} method, + * which will look for the class in the current template directory, or using module mappings. * + * + * @param context The current context of execution * @param name The fully qualified path of the class to load * @param imports The list of imports to use * @param loadClass When false, the class location is returned with informatino about where the class was found, but the class is not loaded and will be null. * * @return The loaded class or null if not found */ - public Optional findFromModules( IBoxContext context, String name, List imports, boolean loadClass ) { + public Optional findFromModules( IBoxContext context, String fullyQualifiedName, List imports, boolean loadClass ) { + // Do we have a explicit module name? path.to.Class@moduleName + String[] parts = fullyQualifiedName.split( "@" ); + + // If we have a module name, then we need to load the class from the module explicitly + if ( parts.length == 2 ) { + // fullyQualifiedName = parts[ 0 ]; + // moduleName = parts[ 1 ]; + return findFromModule( parts[ 0 ], Key.of( parts[ 1 ] ), imports, context ); + } + return Optional.ofNullable( null ); } + /** + * Find a class from a specific module explicitly. + * + * @param fullyQualifiedName The fully qualified path of the class to load in the module root + * @param moduleName The name of the module to look in + * @param imports The list of imports to use + * @param context The current context of execution + * + * @throws BoxRuntimeException If the module is not found + * + * @return The ClassLocation record wrapped in an optional if found, empty otherwise + */ + public Optional findFromModule( String fullyQualifiedName, Key moduleName, List imports, IBoxContext context ) { + ModuleService moduleService = BoxRuntime.getInstance().getModuleService(); + + // Verify the module exists, else throw up, as it was an explicit call + if ( !moduleService.hasModule( moduleName ) ) { + throw new BoxRuntimeException( + String.format( + "Module requested [%s] not found when looking for [%s]. Valid modules are: [%s]", + moduleName.getName(), + fullyQualifiedName, + moduleService.getModuleNames() + ) + ); + } + + // Get the module record and the physical path + ModuleRecord moduleRecord = moduleService.getModuleRecord( moduleName ); + String finalSlashName = getFullyQualifiedSlashName( fullyQualifiedName ); + + // See if path exists in this parent directory with a valid extension + Path targetPath = findExistingPathWithValidExtension( moduleRecord.physicalPath, finalSlashName ); + if ( targetPath != null ) { + ResolvedFilePath resolvedFilePath = ResolvedFilePath.of( targetPath ); + return Optional.of( new ClassLocation( + resolvedFilePath.getBoxFQN().getClassName(), + targetPath.toAbsolutePath().toString(), + resolvedFilePath.getBoxFQN().getPackageString(), + ClassLocator.TYPE_BX, + RunnableLoader.getInstance().loadClass( resolvedFilePath, context ), + moduleName.getName(), + false + ) ); + } + + return Optional.empty(); + } + /** * Load a class from the configured directory byte code * - * @param context The current context of execution - * @param name The fully qualified path of the class to load - * @param imports The list of imports to use + * @param context The current context of execution + * @param fullyQualifiedName The fully qualified path of the class to load + * @param imports The list of imports to use * * @return The loaded class or null if not found */ - public Optional findFromLocal( IBoxContext context, String name, List imports ) { - return findFromLocal( context, name, imports, true ); + public Optional findFromLocal( IBoxContext context, String fullyQualifiedName, List imports ) { + return findFromLocal( context, fullyQualifiedName, imports, true ); } /** * Load a class from the configured directory byte code * - * @param context The current context of execution - * @param name The fully qualified path of the class to load - * @param imports The list of imports to use - * @param loadClass When false, the class location is returned with informatino about where the class was found, but the class is not loaded and will be null. + * @param context The current context of execution + * @param fullyQualifiedName The fully qualified path of the class to load + * @param imports The list of imports to use + * @param loadClass When false, the class location is returned with informatino about where the class was found, but the class is not loaded and will be null. * * @return The loaded class or null if not found */ - public Optional findFromLocal( IBoxContext context, String name, List imports, boolean loadClass ) { - // Convert package dot name to a lookup path - String slashName = name.replace( "../", "DOT_DOT_SLASH" ) - .replace( ".", "/" ) - .replace( "DOT_DOT_SLASH", "../" ); - - // prepend / if not already present - if ( !slashName.startsWith( "/" ) ) { - slashName = "/" + slashName; - } - final String finalSlashName = slashName; - - // Find the class using: + public Optional findFromLocal( IBoxContext context, String fullyQualifiedName, List imports, boolean loadClass ) { + final String finalSlashName = getFullyQualifiedSlashName( fullyQualifiedName ); + // Try to find the class using: // 1. Relative to the current template // 2. A mapping return findByRelativeLocation( context, finalSlashName, name, imports, loadClass ) @@ -265,7 +324,7 @@ private Optional findByMapping( // Map it to a Stream object representing the paths to the classes .flatMap( entry -> { // Generate multiple paths here - List paths = new ArrayList(); + List paths = new ArrayList<>(); for ( String extension : getValidExtensions() ) { Path absolutePath = Path .of( StringUtils.replaceOnceIgnoreCase( slashName, entry.getKey().getName(), entry.getValue() + "/" ) + "." + extension ) @@ -347,29 +406,29 @@ private Optional findByRelativeLocation( if ( template != null ) { // Get the parent directory of the template, verify it exists, else we are done - Path parentPath = template.getParent(); + Path parentPath = template.getParent(); // System.out.println( "parentPath: " + parentPath ); - if ( parentPath != null ) { - // See if path exists in this parent directory with a valid extension - Path targetPath = findExistingPathWithValidExtension( parentPath, slashName ); - if ( targetPath != null ) { - - ResolvedFilePath newResolvedFilePath = resolvedFilePath - .newFromRelative( parentPath.relativize( Paths.get( targetPath.toString() ) ).toString() ); - - return Optional.of( new ClassLocation( - newResolvedFilePath.getBoxFQN().getClassName(), - targetPath.toAbsolutePath().toString(), - newResolvedFilePath.getBoxFQN().getPackageString(), - ClassLocator.TYPE_BX, - loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, - "", - false - ) ); - } + + // See if path exists in this parent directory with a valid extension + Path targetPath = findExistingPathWithValidExtension( parentPath, slashName ); + if ( targetPath != null ) { + + ResolvedFilePath newResolvedFilePath = resolvedFilePath + .newFromRelative( parentPath.relativize( Paths.get( targetPath.toString() ) ).toString() ); + + return Optional.of( new ClassLocation( + newResolvedFilePath.getBoxFQN().getClassName(), + targetPath.toAbsolutePath().toString(), + newResolvedFilePath.getBoxFQN().getPackageString(), + ClassLocator.TYPE_BX, + loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, + "", + false + ) ); } } } + return Optional.empty(); } @@ -393,4 +452,30 @@ private Path findExistingPathWithValidExtension( Path parentPath, String slashNa return null; } + /** + * This method will take the fully qualified class name and convert all + * periods (.) to (/) slashes. It will also prepend a slash (/) if it is not, + * already present. + * + * This is useful for converting a fully qualified class name to a path that + * can be used to look up a class in the file system. + * + * @param fullyQualifiedName The fully qualified class name to convert + * + * @return The fully qualified class name with periods converted to slashes + */ + private String getFullyQualifiedSlashName( String fullyQualifiedName ) { + // Convert package dot name to a lookup path + String slashName = fullyQualifiedName.replace( "../", "DOT_DOT_SLASH" ) + .replace( ".", "/" ) + .replace( "DOT_DOT_SLASH", "../" ); + + // prepend / if not already present + if ( !slashName.startsWith( "/" ) ) { + slashName = "/" + slashName; + } + + return slashName; + } + } diff --git a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java index 255d3d0ce..08d195910 100644 --- a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java +++ b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.config.segments.ModuleConfig; import ortus.boxlang.runtime.context.IBoxContext; @@ -250,5 +251,17 @@ void testItCanActivateAModule() throws ClassNotFoundException { "Hello World, my name is boxlang and I am 0 years old" ); // assertThat( variables.getAsString( Key.of( "result4" ) ) ).isEqualTo( "Hola Mundo!" ); + + // Test Module Class Locators + // @formatter:off + runtime.executeSource(""" + helloClass = createObject( "models.Hello@test" ) + result = helloClass.sayHello(); + """, + context, + BoxSourceType.BOXSCRIPT + ); + // @formatter:on + assertThat( variables.getAsString( Key.result ) ).contains( "ModuleLand" ); } } From efa8d8dfb140c188db387e22a8d0b325bcc0916b Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 14 Oct 2024 17:05:56 +0200 Subject: [PATCH 31/71] fixed tests I broke --- .../runtime/loader/resolvers/BoxResolver.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index e87198381..36b6c8f27 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -404,27 +404,27 @@ private Optional findByRelativeLocation( if ( resolvedFilePath != null ) { Path template = resolvedFilePath.absolutePath(); - if ( template != null ) { + if ( template != null && !template.toString().equalsIgnoreCase( "unknown" ) ) { // Get the parent directory of the template, verify it exists, else we are done - Path parentPath = template.getParent(); - // System.out.println( "parentPath: " + parentPath ); - - // See if path exists in this parent directory with a valid extension - Path targetPath = findExistingPathWithValidExtension( parentPath, slashName ); - if ( targetPath != null ) { - - ResolvedFilePath newResolvedFilePath = resolvedFilePath - .newFromRelative( parentPath.relativize( Paths.get( targetPath.toString() ) ).toString() ); - - return Optional.of( new ClassLocation( - newResolvedFilePath.getBoxFQN().getClassName(), - targetPath.toAbsolutePath().toString(), - newResolvedFilePath.getBoxFQN().getPackageString(), - ClassLocator.TYPE_BX, - loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, - "", - false - ) ); + Path parentPath = template.getParent(); + if ( parentPath != null ) { + // See if path exists in this parent directory with a valid extension + Path targetPath = findExistingPathWithValidExtension( parentPath, slashName ); + if ( targetPath != null ) { + + ResolvedFilePath newResolvedFilePath = resolvedFilePath + .newFromRelative( parentPath.relativize( Paths.get( targetPath.toString() ) ).toString() ); + + return Optional.of( new ClassLocation( + newResolvedFilePath.getBoxFQN().getClassName(), + targetPath.toAbsolutePath().toString(), + newResolvedFilePath.getBoxFQN().getPackageString(), + ClassLocator.TYPE_BX, + loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, + "", + false + ) ); + } } } } From 272d19b985813dc13a41a112282cfc8132478154 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:34:30 +0000 Subject: [PATCH 32/71] Bump ch.qos.logback:logback-classic from 1.5.8 to 1.5.10 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.8 to 1.5.10. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.8...v_1.5.10) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bc36c8f58..a0f306fbb 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,7 @@ dependencies { // https://mvnrepository.com/artifact/org.slf4j/slf4j-api implementation 'org.slf4j:slf4j-api:2.0.16' // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic - implementation 'ch.qos.logback:logback-classic:1.5.8' + implementation 'ch.qos.logback:logback-classic:1.5.10' // https://mvnrepository.com/artifact/com.zaxxer/HikariCP implementation 'com.zaxxer:HikariCP:6.0.0' // https://mvnrepository.com/artifact/org.ow2.asm/asm-tree From ad0781de8b16ee3e068e2e2f4866e69ec6aeebd6 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 14 Oct 2024 22:06:52 +0200 Subject: [PATCH 33/71] BL-648 #resolve Import Definitions now support module addressing for simple, wildcard and aliases BL-647 #resolve Ability to import create java classes from modules explicitly using the `@`notation --- .../boxlang/runtime/loader/ClassLocator.java | 17 ++++ .../runtime/loader/ImportDefinition.java | 38 ++++++-- .../loader/resolvers/BaseResolver.java | 9 +- .../runtime/loader/resolvers/BoxResolver.java | 36 ++++++++ .../runtime/loader/ImportDefinitionTest.java | 38 +++++++- .../loader/resolvers/BoxResolverTest.java | 42 ++++++++- .../loader/resolvers/JavaResolverTest.java | 91 ++++++++++++++++--- 7 files changed, 241 insertions(+), 30 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java index f1aae2702..4622a4147 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java +++ b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java @@ -649,6 +649,23 @@ public record ClassLocation( Boolean isFromModule() { return module != null; } + + /** + * Show the state of this record as a string + */ + @Override + public String toString() { + return String.format( + "ClassLocation [name=%s, path=%s, packageName=%s, type=%s, clazz=%s, module=%s, cachable=%s]", + name, + path, + packageName, + type, + clazz, + module, + cachable + ); + } } } diff --git a/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java b/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java index a3356ee3b..8e28d8388 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java +++ b/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java @@ -20,23 +20,36 @@ import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** - * Represents an import in BoxLang + * Represents an import in BoxLang. Imports can have aliases, wildcards, and resolver prefixes. + *

+ * However, they can also be linked to a specific BoxLang Module via the {@code @{moduleName}} syntax. + *

+ * Normal Resolution: * + *

  * import prefix:package.to.Class as alias
  * import package.to.Class
  * import package.to.Class as alias
  * import package.to.*
+ * 
+ *

+ * Module Resolution: + * + *

+ * import package.to.Class@ModuleName as alias
+ * import package.to.Class@ModuleName
+ * import package.to.Class@ModuleName as alias
+ * import package.to.*@ModuleName
+ * 
* * @param className The class name * @param resolverPrefix The resolver prefix * @param alias The alias */ -public record ImportDefinition( String className, String resolverPrefix, String alias ) { +public record ImportDefinition( String className, String resolverPrefix, String alias, String moduleName ) { // Compact constructor disallows null className - public ImportDefinition - - { + public ImportDefinition { if ( className == null ) { throw new BoxRuntimeException( "Class name cannot be null." ); } @@ -87,6 +100,7 @@ public static ImportDefinition parse( String importStr ) { String className = importStr; String resolverPrefix = null; String alias = null; + String module = null; int aliasDelimiterPos = importStr.toLowerCase().lastIndexOf( " as " ); if ( aliasDelimiterPos != -1 ) { @@ -100,6 +114,18 @@ public static ImportDefinition parse( String importStr ) { if ( alias.contains( "$" ) ) { alias = alias.substring( alias.lastIndexOf( "$" ) + 1 ); } + int moduleDelimiterPos = alias.indexOf( "@" ); + if ( moduleDelimiterPos != -1 ) { + alias = alias.substring( 0, moduleDelimiterPos ); + } + } + + // Check if the import is a module import, the class name must have a @moduleName + // Parse the module name and remove it from the class name + int moduleDelimiterPos = className.indexOf( "@" ); + if ( moduleDelimiterPos != -1 ) { + module = className.substring( moduleDelimiterPos + 1 ); + className = className.substring( 0, moduleDelimiterPos ); } int resolverDelimiterPos = className.indexOf( ":" ); @@ -108,6 +134,6 @@ public static ImportDefinition parse( String importStr ) { className = className.substring( resolverDelimiterPos + 1 ); } - return new ImportDefinition( className, resolverPrefix, alias ); + return new ImportDefinition( className, resolverPrefix, alias, module ); } } diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java index 5297cac40..fdb6a996e 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java @@ -63,7 +63,7 @@ public class BaseResolver implements IClassResolver { /** * The import cache */ - private Set importCache = ConcurrentHashMap.newKeySet(); + protected Set importCache = ConcurrentHashMap.newKeySet(); /** * -------------------------------------------------------------------------- @@ -210,13 +210,15 @@ public Optional resolve( IBoxContext context, String name, List imports ) { var fullyQualifiedName = imports.stream() // Discover import by matching the resolver prefix and the class name or alias or multi-import + // This runs from concrete resolvers: bx, java, etc. + // So if the resolver prefix matches, we continue, else we skip it. .filter( thisImport -> importApplies( thisImport ) && importHas( thisImport, className ) ) // Return the first one, the first one wins .findFirst() // Convert the import to a fully qualified class name .map( targetImport -> { String fqn = targetImport.getFullyQualifiedClass( className ); - importCache.add( className + ":" + fqn ); + this.importCache.add( className + ":" + fqn ); return fqn; } ) // Nothing found, return the original class name @@ -231,6 +233,7 @@ public String expandFromImport( IBoxContext context, String className, List resolve( IBoxContext context, String name, List fullyQualifiedName: " + fullyQualifiedName ); return findFromModules( context, fullyQualifiedName, imports, loadClass ) .or( () -> findFromLocal( context, fullyQualifiedName, imports, loadClass ) ); @@ -478,4 +479,39 @@ private String getFullyQualifiedSlashName( String fullyQualifiedName ) { return slashName; } + /** + * Tries to expand the full class name using the import aliases given. If the class + * name is not found as an import, we return the original class name. + * + * @param context The current context of execution + * @param className The name of the class to resolve + * @param imports The list of imports to use + * + * @return The resolved class name or the original class name if not found + */ + @Override + public String expandFromImport( IBoxContext context, String className, List imports ) { + var fullyQualifiedName = imports.stream() + // Discover import by matching the resolver prefix and the class name or alias or multi-import + // This runs from concrete resolvers: bx, java, etc. + // So if the resolver prefix matches, we continue, else we skip it. + .filter( thisImport -> importApplies( thisImport ) && importHas( thisImport, className ) ) + // Return the first one, the first one wins + .findFirst() + // Convert the import to a fully qualified class name + .map( targetImport -> { + String fqn = targetImport.getFullyQualifiedClass( className ); + this.importCache.add( className + ":" + fqn ); + return fqn; + } ) + // Nothing found, return the original class name + .orElse( className ); + + // Security check + BoxRuntime.getInstance().getConfiguration().security.isClassAllowed( fullyQualifiedName ); + + // Return the fully qualified class name + return fullyQualifiedName; + } + } diff --git a/src/test/java/ortus/boxlang/runtime/loader/ImportDefinitionTest.java b/src/test/java/ortus/boxlang/runtime/loader/ImportDefinitionTest.java index 6080ce7fe..f6cbe6813 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/ImportDefinitionTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/ImportDefinitionTest.java @@ -28,7 +28,7 @@ public class ImportDefinitionTest { @DisplayName( "It can use default constructor" ) @Test public void testCanUseDefaultConstructor() { - ImportDefinition ImportDefinition = new ImportDefinition( "java.lang.String", "java", "jString" ); + ImportDefinition ImportDefinition = new ImportDefinition( "java.lang.String", "java", "jString", null ); assertThat( ImportDefinition.className() ).isEqualTo( "java.lang.String" ); assertThat( ImportDefinition.resolverPrefix() ).isEqualTo( "java" ); @@ -38,14 +38,12 @@ public void testCanUseDefaultConstructor() { @DisplayName( "It can use default constructor with nulls" ) @Test public void testCanUseDefaultConstructorWithNulls() { - ImportDefinition ImportDefinition = new ImportDefinition( "java.lang.String", null, null ); + ImportDefinition ImportDefinition = new ImportDefinition( "java.lang.String", null, null, null ); assertThat( ImportDefinition.className() ).isEqualTo( "java.lang.String" ); assertThat( ImportDefinition.resolverPrefix() ).isEqualTo( null ); assertThat( ImportDefinition.alias() ).isEqualTo( null ); - - assertThrows( Throwable.class, () -> new ImportDefinition( null, null, null ) ); - + assertThrows( Throwable.class, () -> new ImportDefinition( null, null, null, null ) ); } @DisplayName( "It can use static constructor parser" ) @@ -74,4 +72,34 @@ public void testCanUseStaticConstructorParser() { assertThat( importDef.getPackageName() ).isEqualTo( "java.net.http" ); } + @DisplayName( "It can parse a simple import from a module" ) + @Test + public void testCanParseSimpleImportFromModule() { + ImportDefinition importDef = ImportDefinition.parse( "java:java.lang.String@module" ); + assertThat( importDef.className() ).isEqualTo( "java.lang.String" ); + assertThat( importDef.resolverPrefix() ).isEqualTo( "java" ); + assertThat( importDef.alias() ).isEqualTo( "String" ); + assertThat( importDef.moduleName() ).isEqualTo( "module" ); + } + + @DisplayName( "It can parse a simple import from a module using an alias" ) + @Test + public void testCanParseSimpleImportFromModuleUsingAlias() { + ImportDefinition importDef = ImportDefinition.parse( "java:java.lang.String@module AS jString" ); + assertThat( importDef.className() ).isEqualTo( "java.lang.String" ); + assertThat( importDef.resolverPrefix() ).isEqualTo( "java" ); + assertThat( importDef.alias() ).isEqualTo( "jString" ); + assertThat( importDef.moduleName() ).isEqualTo( "module" ); + } + + @DisplayName( "It can parse a simple import from a module using wildcards" ) + @Test + public void testCanParseSimpleImportFromModuleUsingWildcards() { + ImportDefinition importDef = ImportDefinition.parse( "java:java.lang.*@module" ); + assertThat( importDef.className() ).isEqualTo( "java.lang.*" ); + assertThat( importDef.resolverPrefix() ).isEqualTo( "java" ); + assertThat( importDef.alias() ).isEqualTo( "*" ); + assertThat( importDef.moduleName() ).isEqualTo( "module" ); + } + } diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java index 80f6bbe9f..e0b09f591 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java @@ -24,6 +24,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.AfterAll; @@ -37,6 +39,7 @@ import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; +import ortus.boxlang.runtime.loader.ImportDefinition; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.DateTime; @@ -100,13 +103,44 @@ void testFindFromLocal() throws URISyntaxException { assertThat( cfc.getThisScope().get( Key.of( "created" ) ) ).isInstanceOf( DateTime.class ); } - @DisplayName( "It can resolve classes" ) + @DisplayName( "It can resolve classes using direct class names" ) @Test void testResolve() { - IBoxContext context = new ScriptingRequestBoxContext(); - String className = "apppath.models.User"; // Example class name - + // Invalid Class + String className = "apppath.models.User"; assertThat( boxResolver.resolve( context, className ).isPresent() ).isFalse(); + + // Now a class that exists + className = "src.test.bx.Person"; + assertThat( boxResolver.resolve( context, className ).isPresent() ).isTrue(); + } + + @DisplayName( "It can resolve classes using imports" ) + @Test + void testResolveWithImports() { + String className = "TestClass"; + + List imports = Arrays.asList( + ImportDefinition.parse( "src.test.bx.models.Validation" ), + ImportDefinition.parse( "src.test.bx.models.TestClass" ) + ); + Optional classLocation = boxResolver.resolve( context, className, imports ); + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().path() ).contains( "src/test/bx/models/TestClass" ); + } + + @DisplayName( "It can resolve classes using imports for modules" ) + @Disabled + @Test + void testResolveWithImportsForModules() { + String className = "Hello"; + List imports = Arrays.asList( + ImportDefinition.parse( "models.Hello@test" ), + ImportDefinition.parse( "src.test.bx.models.TestClass" ) + ); + Optional classLocation = boxResolver.resolve( context, className, imports ); + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().path() ).contains( "test/models/Hello" ); } } diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java index ea1edf84a..f86357aff 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java @@ -22,15 +22,16 @@ import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.ConsoleHandler; import org.apache.commons.lang3.ClassUtils; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -43,6 +44,9 @@ import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; import ortus.boxlang.runtime.loader.DynamicClassLoader; import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.modules.ModuleRecord; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.services.ModuleService; import ortus.boxlang.runtime.types.IStruct; public class JavaResolverTest { @@ -60,11 +64,7 @@ public static void setUp() { @BeforeEach public void setup() { context = new ScriptingRequestBoxContext( runtime.getRuntimeContext() ); - } - - @AfterAll - public static void teardown() { - + javaResolver.clearJdkImportCache(); } @DisplayName( "It can find be created" ) @@ -132,18 +132,72 @@ public void testFindFromDependentLibraries() { @DisplayName( "It can resolve classes" ) @Test - public void testResolve() { - String className = "org.apache.commons.lang3.ClassUtils"; + public void testResolveClasses() { + String className = "java.util.HashSet"; Optional classLocation = javaResolver.findFromSystem( className, new ArrayList<>(), context ); assertThat( classLocation.isPresent() ).isTrue(); - assertThat( classLocation.get().clazz() ).isEqualTo( ClassUtils.class ); - assertThat( classLocation.get().name() ).isEqualTo( "ClassUtils" ); - assertThat( classLocation.get().packageName() ).isEqualTo( "org.apache.commons.lang3" ); + assertThat( classLocation.get().clazz() ).isEqualTo( HashSet.class ); + assertThat( classLocation.get().name() ).isEqualTo( "HashSet" ); + assertThat( classLocation.get().packageName() ).isEqualTo( "java.util" ); assertThat( classLocation.get().type() ).isEqualTo( ClassLocator.TYPE_JAVA ); assertThat( classLocation.get().module() ).isNull(); } + @DisplayName( "It can resolve classes using aliases" ) + @Test + public void testResolveWithAliases() { + String className = "MySet"; + List imports = Arrays.asList( + ImportDefinition.parse( "java:java.util.HashSet as MySet" ) + ); + Optional classLocation = javaResolver.resolve( context, className, imports ); + + System.out.println( classLocation ); + + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().clazz() ).isEqualTo( HashSet.class ); + assertThat( classLocation.get().name() ).isEqualTo( "HashSet" ); + assertThat( classLocation.get().packageName() ).isEqualTo( "java.util" ); + assertThat( classLocation.get().type() ).isEqualTo( ClassLocator.TYPE_JAVA ); + } + + @DisplayName( "It can resolve a module class by resolution and not explicitly" ) + @Test + public void testResolveModuleClass() { + loadTestModule(); + String className = "com.ortussolutions.bifs.Hola"; + List imports = Arrays.asList( + ImportDefinition.parse( "java:java.util.HashSet as MySet" ) + ); + Optional classLocation = javaResolver.resolve( context, className, imports ); + + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().name() ).isEqualTo( "Hola" ); + assertThat( classLocation.get().packageName() ).isEqualTo( "com.ortussolutions.bifs" ); + assertThat( classLocation.get().type() ).isEqualTo( ClassLocator.TYPE_JAVA ); + assertThat( classLocation.get().module() ).isEqualTo( "test" ); + } + + @DisplayName( "It can resolve a module class explicitly via an import" ) + @Test + public void testResolveModuleClassExplicitly() { + loadTestModule(); + String className = "Hola"; + List imports = Arrays.asList( + ImportDefinition.parse( "java:com.ortussolutions.bifs.Hola@test" ) + ); + Optional classLocation = javaResolver.resolve( context, className, imports ); + + System.out.println( classLocation ); + + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().name() ).isEqualTo( "Hola" ); + assertThat( classLocation.get().packageName() ).isEqualTo( "com.ortussolutions.bifs" ); + assertThat( classLocation.get().type() ).isEqualTo( ClassLocator.TYPE_JAVA ); + assertThat( classLocation.get().module() ).isEqualTo( "test" ); + } + @DisplayName( "It can resolve wildcard imports from the JDK itself" ) @Test void testItCanResolveWildcardImports() throws Exception { @@ -152,7 +206,6 @@ void testItCanResolveWildcardImports() throws Exception { ImportDefinition.parse( "java:java.util.*" ) ); - javaResolver.clearJdkImportCache(); assertThat( javaResolver.getJdkImportCacheSize() ).isEqualTo( 0 ); String fqn = javaResolver.expandFromImport( new ScriptingRequestBoxContext(), "String", imports ); @@ -184,4 +237,18 @@ void testItCanLoadLibsFromHomeLibs() throws IOException { assertThat( location.get().clazz().getName() ).isEqualTo( targetClass ); } + private void loadTestModule() { + Key moduleName = new Key( "test" ); + String physicalPath = Paths.get( "./modules/test" ).toAbsolutePath().toString(); + ModuleRecord moduleRecord = new ModuleRecord( physicalPath ); + ModuleService moduleService = runtime.getModuleService(); + + moduleRecord + .loadDescriptor( context ) + .register( context ) + .activate( context ); + + moduleService.getRegistry().put( moduleName, moduleRecord ); + } + } From 236153f0d01939d2c402d434549da684fb753684 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 14 Oct 2024 22:20:10 +0200 Subject: [PATCH 34/71] update tests --- .../ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java index e0b09f591..0bf37382d 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java @@ -126,7 +126,7 @@ void testResolveWithImports() { ); Optional classLocation = boxResolver.resolve( context, className, imports ); assertThat( classLocation.isPresent() ).isTrue(); - assertThat( classLocation.get().path() ).contains( "src/test/bx/models/TestClass" ); + assertThat( classLocation.get().path() ).contains( "TestClass" ); } @DisplayName( "It can resolve classes using imports for modules" ) From 7be376dfece0b7b00f613f4e81544f1540874dda Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 15:38:25 -0500 Subject: [PATCH 35/71] BL-649 --- .../boxlang/runtime/bifs/global/list/ListFind.java | 7 ++++++- .../runtime/bifs/global/list/ListFindTest.java | 13 +++++++++++++ .../runtime/bifs/global/query/QueryRowDataTest.java | 6 ------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListFind.java b/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListFind.java index b04b14689..14d30a9f4 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListFind.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListFind.java @@ -17,6 +17,7 @@ */ package ortus.boxlang.runtime.bifs.global.list; +import ortus.boxlang.runtime.bifs.BIF; import ortus.boxlang.runtime.bifs.BoxBIF; import ortus.boxlang.runtime.bifs.BoxMember; import ortus.boxlang.runtime.bifs.global.array.ArrayFind; @@ -37,6 +38,9 @@ @BoxMember( type = BoxLangType.STRING, name = "listContainsNoCase" ) public class ListFind extends ArrayFind { + private static final Key listContainsKey = new Key( "listContains" ); + private static final Key listContainsNoCaseKey = new Key( "listContainsNoCase" ); + /** * Constructor */ @@ -76,7 +80,8 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { arguments.getAsBoolean( Key.multiCharacterDelimiter ) ) ); - arguments.put( Key.substringMatch, true ); + Key calledName = arguments.getAsKey( BIF.__functionName ); + arguments.put( Key.substringMatch, calledName.equals( listContainsKey ) || calledName.equals( listContainsNoCaseKey ) ? true : false ); return super._invoke( context, arguments ); } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListFindTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListFindTest.java index 8db377b2a..daaa98043 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListFindTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListFindTest.java @@ -191,4 +191,17 @@ public void benchmark() { context ); } + @DisplayName( "It should not find partial" ) + @Test + public void testPartial() { + instance.executeSource( + """ + nums = "red,blue,orange"; + result = nums.listFindNoCase( "b" ); + """, + context ); + int found = ( int ) variables.get( result ); + assertThat( found ).isEqualTo( 0 ); + } + } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryRowDataTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryRowDataTest.java index a60c8fa8f..615d19480 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryRowDataTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryRowDataTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -48,11 +47,6 @@ public static void setUp() { instance = BoxRuntime.getInstance( true ); } - @AfterAll - public static void teardown() { - instance.shutdown(); - } - @BeforeEach public void setupEach() { context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); From 7723821ea293e21627367e9e5962fac173056a17 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 15:41:39 -0500 Subject: [PATCH 36/71] BL-636 --- .../runtime/util/ResolvedFilePath.java | 21 ++++++++++++++++--- .../bifs/global/io/ExpandPathTest.java | 17 +++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java b/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java index 8b311de24..d768583e9 100644 --- a/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java +++ b/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java @@ -17,9 +17,12 @@ */ package ortus.boxlang.runtime.util; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import ortus.boxlang.runtime.types.exceptions.BoxIOException; + /** * I represent the a file path that has been resolved to an absolute path. * I track additional data such as what mapping was used to resolve the path. @@ -42,7 +45,7 @@ public static ResolvedFilePath of( String mappingName, String mappingPath, Strin mappingName, mappingPath, relativePath, - absolutePath != null ? absolutePath.normalize() : null + absolutePath != null ? makeReal( absolutePath.normalize() ) : null ); } @@ -77,7 +80,7 @@ public static ResolvedFilePath of( Path absolutePath ) { null, null, absolutePath != null ? absolutePath.normalize().toString() : null, - absolutePath + makeReal( absolutePath ) ); } @@ -97,7 +100,7 @@ public static ResolvedFilePath of( String absolutePath ) { * * @return true if the path was resolved via a mapping. */ - public boolean resovledViaMapping() { + public boolean resolvedViaMapping() { return mappingName != null; } @@ -169,4 +172,16 @@ public ResolvedFilePath newFromRelative( String relativePath ) { ); } + private static Path makeReal( Path path ) { + // if exists, make it real + if ( path != null && path.toFile().exists() ) { + try { + return path.toRealPath(); + } catch ( IOException e ) { + throw new BoxIOException( e ); + } + } + return path; + } + } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java index 428632b93..f4152eaf3 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java @@ -133,6 +133,23 @@ public void testMapping() { assertThat( variables.get( result ) ).isEqualTo( abs ); } + @Test + public void testFSCase() { + // path relative to custom mapping + String abs; + try { + abs = Path.of( "src/test/java/ortus/boxlang/runtime/bifs/global/io/expandPathTest.txt" ).toAbsolutePath().toRealPath().toString(); + } catch ( IOException e ) { + throw new RuntimeException( e ); + } + instance.executeSource( + """ + result = ExpandPath( "/EXPAND/PATH/TEST/EXPANDPATHTEST.TXT" ); + """, + context ); + assertThat( variables.get( result ) ).isEqualTo( abs ); + } + @Test public void testRelative() { String abs = Path.of( "src/test/java/ortus/boxlang/runtime/bifs/global/io/expandPathTest.txt" ).toAbsolutePath().toString(); From 3a09ca58fa3c35b741f28ab55b498fcb18847ec5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 15:44:59 -0500 Subject: [PATCH 37/71] BL-650 --- .../bifs/global/string/CharsetEncode.java | 10 +-- .../runtime/dynamic/casters/ByteCaster.java | 26 +++--- .../runtime/dynamic/casters/FloatCaster.java | 24 ++---- .../dynamic/casters/GenericCaster.java | 79 ++++++++++++++----- .../runtime/dynamic/casters/LongCaster.java | 24 ++---- .../runtime/dynamic/casters/ShortCaster.java | 17 ++-- .../ortus/boxlang/runtime/types/Array.java | 11 +-- .../bifs/global/string/CharsetEncodeTest.java | 20 +++++ .../boxlang/runtime/operators/CastAsTest.java | 66 ++++++++++++++++ 9 files changed, 186 insertions(+), 91 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncode.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncode.java index 92eaeb231..6eb220125 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncode.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncode.java @@ -29,7 +29,6 @@ import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @BoxBIF @@ -41,7 +40,7 @@ public class CharsetEncode extends BIF { public CharsetEncode() { super(); declaredArguments = new Argument[] { - new Argument( true, "any", Key.binary ), + new Argument( true, "byte[]", Key.binary ), new Argument( false, "string", Key.encoding, "utf-8" ) }; } @@ -57,13 +56,10 @@ public CharsetEncode() { * @argument.encoding The charset encoding to use (default: utf-8 ) */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - - if ( ! ( arguments.get( Key.binary ) instanceof byte[] ) ) { - throw new BoxRuntimeException( "The binary value passed to the function [charsetEncode] is not a valid binary object" ); - } + byte[] binary = ( byte[] ) arguments.get( Key.binary ); return StringUtils.toEncodedString( - ( byte[] ) arguments.get( Key.binary ), + binary, Charset.forName( arguments.getAsString( Key.encoding ) ) ); diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/ByteCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/ByteCaster.java index 7ef5a6f39..cc008cce0 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/ByteCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/ByteCaster.java @@ -65,15 +65,23 @@ public static Byte cast( Object object, Boolean fail ) { } } - // TODO: Find a way to check if the string can be cast without throwing an exception here - try { - return Byte.valueOf( StringCaster.cast( object ) ); - } catch ( NumberFormatException e ) { - if ( fail ) { - throw e; - } else { - return null; - } + // Check if it's already a byte + if ( object instanceof Byte b ) { + return b; + } + + // Check if the object is already a number + if ( object instanceof Number num ) { + return num.byteValue(); + } + + CastAttempt number = NumberCaster.attempt( object ); + if ( number.wasSuccessful() ) { + return number.get().byteValue(); + } else if ( fail ) { + throw new BoxCastException( "Can't cast " + object.getClass().getName() + " to a byte." ); + } else { + return null; } } diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/FloatCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/FloatCaster.java index 6a4d0b376..9c3543fe7 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/FloatCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/FloatCaster.java @@ -75,23 +75,13 @@ public static Float cast( Object object, Boolean fail ) { return Float.valueOf( bool ? 1 : 0 ); } - // TODO: Find a way to check if the string can be cast without throwing an exception here - try { - String value = StringCaster.cast( object, fail ); - if ( value == null ) { - if ( fail ) { - throw new BoxCastException( "Can't cast null to a float." ); - } else { - return null; - } - } - return Float.valueOf( value ); - } catch ( NumberFormatException e ) { - if ( fail ) { - throw e; - } else { - return null; - } + CastAttempt number = NumberCaster.attempt( object ); + if ( number.wasSuccessful() ) { + return number.get().floatValue(); + } else if ( fail ) { + throw new BoxCastException( "Can't cast " + object.getClass().getName() + " to a float." ); + } else { + return null; } } diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java index a6ed5726b..30bcb8e14 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java @@ -17,7 +17,6 @@ */ package ortus.boxlang.runtime.dynamic.casters; -import java.lang.reflect.Array; import java.math.BigDecimal; import java.util.List; import java.util.Optional; @@ -57,9 +56,9 @@ public class GenericCaster implements IBoxCaster { public static CastAttempt attempt( IBoxContext context, Object object, Object oType, boolean strict ) { String type; if ( oType instanceof BoxLangType boxType ) { - type = boxType.name().toLowerCase(); + type = boxType.name(); } else { - type = StringCaster.cast( oType ).toLowerCase(); + type = StringCaster.cast( oType ); } // Represent legit null values in a NullValue instance @@ -138,7 +137,8 @@ public static Object cast( IBoxContext context, Object object, Object oType, Boo String newType = type.substring( 0, type.length() - 2 ); originalCaseType = originalCaseType.substring( 0, originalCaseType.length() - 2 ); Class newTypeClass = getClassFromType( context, newType, originalCaseType, false ); - Object[] result; + // Typed as Object instead of Object[] in case we're creating an array of primitives + Object result; Boolean convertToArray = false; // If we could not get the class, then we are casting to an array of objects @@ -148,9 +148,14 @@ public static Object cast( IBoxContext context, Object object, Object oType, Boo } if ( object.getClass().isArray() ) { + // If our incoming object is already an array of the new type, just return it + if ( object.getClass().getComponentType().equals( newTypeClass ) ) { + return object; + } result = castNativeArrayToNativeArray( context, object, newType, fail, newTypeClass ); - } else if ( object instanceof List ) { - result = castListToNativeArray( context, object, newType, fail, newTypeClass ); + } else if ( object instanceof List incomingList ) { + Object[] incomingArray = incomingList.toArray(); + result = castNativeArrayToNativeArray( context, incomingArray, newType, fail, newTypeClass ); } else { throw new BoxCastException( String.format( "You asked for type %s, but input %s cannot be cast to an array.", type, @@ -158,7 +163,9 @@ public static Object cast( IBoxContext context, Object object, Object oType, Boo ); } if ( convertToArray ) { - return ortus.boxlang.runtime.types.Array.fromArray( result ); + // unsafe cast to Object[] is OK here because the convertToArray flag will never be true + // if our target type is an array of primitives, so we know it will have boxed types in it + return ortus.boxlang.runtime.types.Array.fromArray( ( Object[] ) result ); } return result; } @@ -302,20 +309,23 @@ public static Object cast( IBoxContext context, Object object, Object oType, Boo } - private static Object[] castNativeArrayToNativeArray( IBoxContext context, Object object, String newType, boolean fail, Class newTypeClass ) { - Object[] result = ( Object[] ) java.lang.reflect.Array.newInstance( newTypeClass, Array.getLength( object ) ); - for ( int i = Array.getLength( object ) - 1; i >= 0; i-- ) { - result[ i ] = GenericCaster.cast( context, Array.get( object, i ), newType, fail ); - } - return result; - } - - private static Object[] castListToNativeArray( IBoxContext context, Object object, String newType, boolean fail, Class newTypeClass ) { - List l = ( List ) object; - Object[] incomingList = l.toArray(); - Object[] result = ( Object[] ) java.lang.reflect.Array.newInstance( newTypeClass, incomingList.length ); - for ( int i = incomingList.length - 1; i >= 0; i-- ) { - result[ i ] = GenericCaster.cast( context, incomingList[ i ], newType, fail ); + /** + * Cast a native array to a native array + * We are accepting Object and returning Object so we can pass arrays of primitives + * + * @param object The object to cast + * @param newType The new type + * @param fail True to throw exception when type is invalid + * @param newTypeClass The new type class + * + * @return The casted object + */ + private static Object castNativeArrayToNativeArray( IBoxContext context, Object object, String newType, boolean fail, Class newTypeClass ) { + int len = java.lang.reflect.Array.getLength( object ); + Object result = java.lang.reflect.Array.newInstance( newTypeClass, len ); + for ( int i = len - 1; i >= 0; i-- ) { + Object v = GenericCaster.cast( context, java.lang.reflect.Array.get( object, i ), newType, fail ); + java.lang.reflect.Array.set( result, i, v ); } return result; } @@ -354,6 +364,33 @@ public static Class getClassFromType( IBoxContext context, String type, Strin */ public static Class getClassFromType( IBoxContext context, String type, String originalCaseType, Boolean fail ) { + // Check for primitive types first + if ( originalCaseType.equals( "byte" ) ) { + return byte.class; + } + if ( originalCaseType.equals( "char" ) ) { + return char.class; + } + if ( originalCaseType.equals( "short" ) ) { + return short.class; + } + if ( originalCaseType.equals( "int" ) ) { + return int.class; + } + if ( originalCaseType.equals( "long" ) ) { + return long.class; + } + if ( originalCaseType.equals( "float" ) ) { + return float.class; + } + if ( originalCaseType.equals( "double" ) ) { + return double.class; + } + if ( originalCaseType.equals( "boolean" ) ) { + return boolean.class; + } + + // Check for boxed types if ( type.equals( "bigdecimal" ) || type.equals( "java.math.bigdecimal" ) ) { return BigDecimal.class; } diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/LongCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/LongCaster.java index e84d3c30f..d093812e1 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/LongCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/LongCaster.java @@ -75,23 +75,13 @@ public static Long cast( Object object, Boolean fail ) { return Long.valueOf( bool ? 1 : 0 ); } - // TODO: Find a way to check if the string can be cast without throwing an exception here - try { - String value = StringCaster.cast( object, fail ); - if ( value == null ) { - if ( fail ) { - throw new BoxCastException( "Can't cast null to a long." ); - } else { - return null; - } - } - return Long.valueOf( value ); - } catch ( NumberFormatException e ) { - if ( fail ) { - throw e; - } else { - return null; - } + CastAttempt number = NumberCaster.attempt( object ); + if ( number.wasSuccessful() ) { + return number.get().longValue(); + } else if ( fail ) { + throw new BoxCastException( "Can't cast " + object.getClass().getName() + " to a long." ); + } else { + return null; } } diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/ShortCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/ShortCaster.java index f5ec5d790..7259669b4 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/ShortCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/ShortCaster.java @@ -75,17 +75,14 @@ public static Short cast( Object object, Boolean fail ) { return Short.valueOf( ( short ) ( bool ? 1 : 0 ) ); } - // TODO: Find a way to check if the string can be cast without throwing an exception here - try { - return Short.valueOf( StringCaster.cast( object ) ); - } catch ( NumberFormatException e ) { - if ( fail ) { - throw e; - } else { - return null; - } + CastAttempt number = NumberCaster.attempt( object ); + if ( number.wasSuccessful() ) { + return number.get().shortValue(); + } else if ( fail ) { + throw new BoxCastException( "Can't cast " + object.getClass().getName() + " to a short." ); + } else { + return null; } - } } diff --git a/src/main/java/ortus/boxlang/runtime/types/Array.java b/src/main/java/ortus/boxlang/runtime/types/Array.java index f2577cb8a..6e50d7536 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Array.java +++ b/src/main/java/ortus/boxlang/runtime/types/Array.java @@ -139,15 +139,6 @@ public Array( Object[] arr ) { this.wrapped = Collections.synchronizedList( new ArrayList( Arrays.asList( arr ) ) ); } - /** - * Constructor to create an Array from a Java byte array - * - * @param arr The array to create the Array from - */ - public Array( byte[] arr ) { - this.wrapped = Collections.synchronizedList( new ArrayList( Arrays.asList( arr ) ) ); - } - /** * Constructor to create an Array from a List * @@ -216,7 +207,7 @@ public static Array fromSet( Set set ) { } /** - * Create an Array from a Java array + * Create an Array from a Java array of boxed objects * * @param arr The array to create the Array from */ diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncodeTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncodeTest.java index fcbebd4d6..5d9f035e3 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncodeTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/string/CharsetEncodeTest.java @@ -19,6 +19,7 @@ package ortus.boxlang.runtime.bifs.global.string; +import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.Charset; @@ -80,4 +81,23 @@ public void testBif() { } + @Test + public void testByteArray() { + + instance.executeSource( + """ + result = charsetEncode( [0], "utf-8"); + """, + context ); + assertThat( variables.get( result ).equals( new byte[] { 0 } ) ); + + instance.executeSource( + """ + result = charsetEncode( javacast("byte[]", [0] ), "utf-8"); + """, + context ); + assertThat( variables.get( result ).equals( new byte[] { 0 } ) ); + + } + } diff --git a/src/test/java/ortus/boxlang/runtime/operators/CastAsTest.java b/src/test/java/ortus/boxlang/runtime/operators/CastAsTest.java index 918f180d7..82413965b 100644 --- a/src/test/java/ortus/boxlang/runtime/operators/CastAsTest.java +++ b/src/test/java/ortus/boxlang/runtime/operators/CastAsTest.java @@ -31,6 +31,7 @@ import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.runnables.RunnableLoader; +import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.util.MathUtil; @@ -234,6 +235,58 @@ void testItCanCastArrayToArray() { assertThat( arrResult[ 1 ] ).isEqualTo( "Wood" ); } + @DisplayName( "It can cast primitive Array to boxed array" ) + @Test + void testItCanCastPrimitiveArrayToBoxedArray() { + Object result = CastAs.invoke( context, new int[] { 1, 2, 3 }, "Integer[]" ); + assertThat( result.getClass().isArray() ).isTrue(); + assertThat( result ).isInstanceOf( Integer[].class ); + Integer[] arrResult = ( ( Integer[] ) result ); + assertThat( arrResult.length ).isEqualTo( 3 ); + assertThat( arrResult[ 0 ] ).isEqualTo( 1 ); + assertThat( arrResult[ 1 ] ).isEqualTo( 2 ); + assertThat( arrResult[ 2 ] ).isEqualTo( 3 ); + } + + @DisplayName( "It can cast primitive Array to primitive array" ) + @Test + void testItCanCastPrimitiveArrayToPrimitiveArray() { + Object result = CastAs.invoke( context, new int[] { 1, 2, 3 }, "int[]" ); + assertThat( result.getClass().isArray() ).isTrue(); + assertThat( result ).isInstanceOf( int[].class ); + int[] arrResult = ( ( int[] ) result ); + assertThat( arrResult.length ).isEqualTo( 3 ); + assertThat( arrResult[ 0 ] ).isEqualTo( 1 ); + assertThat( arrResult[ 1 ] ).isEqualTo( 2 ); + assertThat( arrResult[ 2 ] ).isEqualTo( 3 ); + } + + @DisplayName( "It can cast boxed Array to boxed array" ) + @Test + void testItCanCastBoxedArrayToBoxedArray() { + Object result = CastAs.invoke( context, new Integer[] { 1, 2, 3 }, "Integer[]" ); + assertThat( result.getClass().isArray() ).isTrue(); + assertThat( result ).isInstanceOf( Integer[].class ); + Integer[] arrResult = ( ( Integer[] ) result ); + assertThat( arrResult.length ).isEqualTo( 3 ); + assertThat( arrResult[ 0 ] ).isEqualTo( 1 ); + assertThat( arrResult[ 1 ] ).isEqualTo( 2 ); + assertThat( arrResult[ 2 ] ).isEqualTo( 3 ); + } + + @DisplayName( "It can cast boxed Array to primitive array" ) + @Test + void testItCanCastBoxedArrayToPrimitiveArray() { + Object result = CastAs.invoke( context, new Integer[] { 1, 2, 3 }, "int[]" ); + assertThat( result.getClass().isArray() ).isTrue(); + assertThat( result ).isInstanceOf( int[].class ); + int[] arrResult = ( ( int[] ) result ); + assertThat( arrResult.length ).isEqualTo( 3 ); + assertThat( arrResult[ 0 ] ).isEqualTo( 1 ); + assertThat( arrResult[ 1 ] ).isEqualTo( 2 ); + assertThat( arrResult[ 2 ] ).isEqualTo( 3 ); + } + @DisplayName( "It can cast List to array" ) @Test void testItCanCastListToArray() { @@ -247,6 +300,19 @@ void testItCanCastListToArray() { assertThat( arrResult[ 1 ] ).isEqualTo( "Wood" ); } + @DisplayName( "It can cast array to array of primitives" ) + @Test + void testItCanCastArrayToArrayOfPrimitives() { + Object result = CastAs.invoke( context, Array.of( 0, 1, 2 ), "byte[]" ); + assertThat( result.getClass().isArray() ).isTrue(); + assertThat( result ).isInstanceOf( byte[].class ); + byte[] arrResult = ( ( byte[] ) result ); + assertThat( arrResult.length ).isEqualTo( 3 ); + assertThat( arrResult[ 0 ] ).isEqualTo( 0 ); + assertThat( arrResult[ 1 ] ).isEqualTo( 1 ); + assertThat( arrResult[ 2 ] ).isEqualTo( 2 ); + } + @DisplayName( "It can cast class to class" ) @Test void testItCanCastClassToClass() { From ea726c68a0201869491811cadc045eda98e25440 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 17:16:56 -0500 Subject: [PATCH 38/71] BL-650 --- .../boxlang/runtime/bifs/global/system/JavaCastTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/JavaCastTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/JavaCastTest.java index 072ed0f15..40b8a9a40 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/JavaCastTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/JavaCastTest.java @@ -228,8 +228,8 @@ public void testItCastsToNativeArrayOfInt() { result = javaCast('int[]', [1,2,3]); """, context ); - assertThat( variables.get( result ) ).isInstanceOf( new Integer[] {}.getClass() ); - Integer[] castedArr = ( Integer[] ) variables.get( result ); + assertThat( variables.get( result ) ).isInstanceOf( new int[] {}.getClass() ); + int[] castedArr = ( int[] ) variables.get( result ); assertThat( castedArr[ 0 ] ).isEqualTo( 1 ); assertThat( castedArr[ 1 ] ).isEqualTo( 2 ); assertThat( castedArr[ 2 ] ).isEqualTo( 3 ); From ef351b06ef992d9291f1c80125c65e1563532b5d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 17:17:15 -0500 Subject: [PATCH 39/71] BL-636 --- .../bifs/global/io/ExpandPathTest.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java index f4152eaf3..4a5108a53 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/io/ExpandPathTest.java @@ -135,19 +135,21 @@ public void testMapping() { @Test public void testFSCase() { - // path relative to custom mapping - String abs; - try { - abs = Path.of( "src/test/java/ortus/boxlang/runtime/bifs/global/io/expandPathTest.txt" ).toAbsolutePath().toRealPath().toString(); - } catch ( IOException e ) { - throw new RuntimeException( e ); + if ( FileSystemUtil.IS_WINDOWS ) { + // path relative to custom mapping + String abs; + try { + abs = Path.of( "src/test/java/ortus/boxlang/runtime/bifs/global/io/expandPathTest.txt" ).toAbsolutePath().toRealPath().toString(); + } catch ( IOException e ) { + throw new RuntimeException( e ); + } + instance.executeSource( + """ + result = ExpandPath( "/EXPAND/PATH/TEST/EXPANDPATHTEST.TXT" ); + """, + context ); + assertThat( variables.get( result ) ).isEqualTo( abs ); } - instance.executeSource( - """ - result = ExpandPath( "/EXPAND/PATH/TEST/EXPANDPATHTEST.TXT" ); - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( abs ); } @Test From d9370623f8c0c228b134d511649345ceabcf6567 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 17:17:43 -0500 Subject: [PATCH 40/71] BL-651 --- .../java/ortus/boxlang/runtime/util/DumpUtil.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java index 418897919..12f9c2d26 100644 --- a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java @@ -242,7 +242,11 @@ public static void dumpToConsole( IBoxContext context, Object target, String lab } } else { // out.println( "> " + target.getClass().getName() ); - out.println( target.toString() ); + if ( target == null ) { + out.println( "[null]" ); + } else { + out.println( target.toString() ); + } } } @@ -253,7 +257,11 @@ public static void dumpToConsole( IBoxContext context, Object target, String lab * @param target The target object */ public static void dumpTextToBuffer( IBoxContext context, Object target ) { - context.writeToBuffer( target.toString(), true ); + if ( target == null ) { + context.writeToBuffer( "[null]", true ); + } else { + context.writeToBuffer( target.toString(), true ); + } } /** From faf6f84c72b80aa5a2a7b34e05cdef84c3734f59 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 14 Oct 2024 17:18:48 -0500 Subject: [PATCH 41/71] BL-652 --- .../java/ortus/boxlang/runtime/context/FunctionBoxContext.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java index 7f8c706ac..f8275919b 100644 --- a/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java @@ -645,6 +645,9 @@ protected Function findFunction( Key name ) { Object value = result.value(); if ( value instanceof Function fun ) { return fun; + } else if ( value == null ) { + throw new BoxRuntimeException( + "Variable '" + name + "' is null and cannot be used as a function." ); } else { throw new BoxRuntimeException( "Variable '" + name + "' of type '" + value.getClass().getName() + "' is not a function." ); From 96f34878b4853ef9e496a476ee719a0558f3da01 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 15 Oct 2024 19:37:22 +0200 Subject: [PATCH 42/71] BL-653 #resolve Java wildcard imports from loaded jars --- .../loader/resolvers/JavaResolver.java | 2 +- .../runtime/loader/util/ClassDiscovery.java | 25 +++++++++++--- .../loader/resolvers/JavaResolverTest.java | 33 +++++++++++++++---- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java index 614e03e8e..a5bff8ac9 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java @@ -313,7 +313,7 @@ protected boolean importHasMulti( ImportDefinition thisImport, String className return false; } } else { - // Use the base resolver + // Use the base resolver for NON jdk resolution classes return super.importHasMulti( thisImport, className ); } } diff --git a/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java b/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java index 4ce3ec880..228409f35 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java +++ b/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java @@ -70,6 +70,7 @@ public class ClassDiscovery { * * @throws IOException If the classpath cannot be read */ + @SuppressWarnings( "unchecked" ) public static Stream getClassFilesAsStream( String packageName, Boolean recursive ) throws IOException { ClassLoader classLoader = BoxRuntime.getInstance().getClass().getClassLoader(); String path = packageName.replace( '.', '/' ); @@ -77,9 +78,16 @@ public static Stream getClassFilesAsStream( String packageName, Boolean return Collections.list( resources ) .stream() - .map( URL::getFile ) - .map( File::new ) - .flatMap( directory -> Stream.of( findClassNames( directory, packageName, recursive ) ) ); + .flatMap( url -> { + // We do different things based on the protocol + if ( url.getProtocol().equals( JAR_FILE_EXTENSION ) ) { + List> classesFound = findClassesInJar( url, path, classLoader, new Class[ 0 ] ); + return classesFound.stream().map( clazz -> clazz.getName() ); + } else { + File directory = new File( url.getFile() ); + return Stream.of( findClassNames( directory, packageName, recursive ) ); + } + } ); } /** @@ -273,8 +281,15 @@ public static Path getPathFromResource( String resourceName ) { * @throws ClassNotFoundException */ public static String[] findClassNames( File directory, String packageName, Boolean recursive ) { - return Arrays.stream( directory.listFiles() ) - .parallel() + // Do we have anything? + File[] aClassNames = directory.listFiles(); + if ( aClassNames == null ) { + return new String[ 0 ]; + } + + return Arrays.stream( aClassNames ) + // .parallel() + .peek( file -> System.out.println( "File: " + file ) ) .flatMap( file -> { if ( file.isDirectory() ) { if ( recursive ) { diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java index f86357aff..a7d11409a 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java @@ -189,8 +189,7 @@ public void testResolveModuleClassExplicitly() { ); Optional classLocation = javaResolver.resolve( context, className, imports ); - System.out.println( classLocation ); - + // System.out.println( classLocation ); assertThat( classLocation.isPresent() ).isTrue(); assertThat( classLocation.get().name() ).isEqualTo( "Hola" ); assertThat( classLocation.get().packageName() ).isEqualTo( "com.ortussolutions.bifs" ); @@ -200,7 +199,7 @@ public void testResolveModuleClassExplicitly() { @DisplayName( "It can resolve wildcard imports from the JDK itself" ) @Test - void testItCanResolveWildcardImports() throws Exception { + void testItCanResolveWildcardImports() { List imports = Arrays.asList( ImportDefinition.parse( "java:java.lang.*" ), ImportDefinition.parse( "java:java.util.*" ) @@ -208,16 +207,38 @@ void testItCanResolveWildcardImports() throws Exception { assertThat( javaResolver.getJdkImportCacheSize() ).isEqualTo( 0 ); - String fqn = javaResolver.expandFromImport( new ScriptingRequestBoxContext(), "String", imports ); + String fqn = javaResolver.expandFromImport( context, "String", imports ); assertThat( fqn ).isEqualTo( "java.lang.String" ); - fqn = javaResolver.expandFromImport( new ScriptingRequestBoxContext(), "Integer", imports ); + fqn = javaResolver.expandFromImport( context, "Integer", imports ); assertThat( fqn ).isEqualTo( "java.lang.Integer" ); - fqn = javaResolver.expandFromImport( new ScriptingRequestBoxContext(), "List", imports ); + fqn = javaResolver.expandFromImport( context, "List", imports ); assertThat( fqn ).isEqualTo( "java.util.List" ); } + @DisplayName( "It can resolve wildcard imports from NON JDK classes on disk path" ) + @Test + void testItCanResolveWildcardImportsFromNonJDK() { + List imports = Arrays.asList( + ImportDefinition.parse( "java:ortus.boxlang.runtime.util.*" ) + ); + + String fqn = javaResolver.expandFromImport( context, "DumpUtil", imports ); + assertThat( fqn ).isEqualTo( "ortus.boxlang.runtime.util.DumpUtil" ); + } + + @DisplayName( "It can resolve wildcard imports from NON JDK classes in a JAR" ) + @Test + void testItCanResolveWildcardImportsFromNonJDKInAJar() { + List imports = Arrays.asList( + ImportDefinition.parse( "java:com.zaxxer.hikari.util.*" ) + ); + + String fqn = javaResolver.expandFromImport( context, "ConcurrentBag", imports ); + assertThat( fqn ).isEqualTo( "com.zaxxer.hikari.util.ConcurrentBag" ); + } + @DisplayName( "It can load libs from the 'home/libs' convention" ) @Test void testItCanLoadLibsFromHomeLibs() throws IOException { From 6970c753d8cc63475b18cd5919bff2dece60febf Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Tue, 15 Oct 2024 12:45:06 -0500 Subject: [PATCH 43/71] Final commit to get ColdBox running --- .../compiler/asmboxpiler/ASMBoxpiler.java | 1 + .../compiler/asmboxpiler/AsmHelper.java | 11 ++- .../asmboxpiler/MethodContextTracker.java | 53 ++++++++++++-- .../compiler/asmboxpiler/Transpiler.java | 13 ++++ .../expression/BoxBreakTransformer.java | 35 +++++++--- .../expression/BoxContinueTransformer.java | 45 +++++++++--- .../BoxStatementBlockTransformer.java | 11 ++- .../expression/BoxSwitchTransformer.java | 33 +++++++-- .../statement/BoxComponentTransformer.java | 43 +++++++++++- .../statement/BoxForInTransformer.java | 18 ++--- .../statement/BoxForIndexTransformer.java | 28 +++++--- .../BoxFunctionDeclarationTransformer.java | 7 ++ .../statement/BoxIfElseTransformer.java | 2 + .../statement/BoxWhileTransformer.java | 14 ++-- .../asm/integration/ControllerTest.java | 70 +++++++++++++++++-- .../TestCases/asm/phase1/LabeledLoopTest.java | 2 +- 16 files changed, 323 insertions(+), 63 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index b7cab5291..640e2836d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -25,6 +25,7 @@ public class ASMBoxpiler extends Boxpiler { public static final boolean DEBUG = Boolean.getBoolean( "asmboxpiler.debug" ); + // public static final boolean DEBUG = true; /** * -------------------------------------------------------------------------- diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index aab45a3ce..08de73833 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -46,6 +46,15 @@ public class AsmHelper { + public static void addDebugLabel( List nodes, String label ) { + if ( !ASMBoxpiler.DEBUG ) { + return; + } + + nodes.add( new LdcInsnNode( label ) ); + nodes.add( new InsnNode( Opcodes.POP ) ); + } + public static List getDefaultExpression( AsmTranspiler transpiler, BoxExpression body ) { Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + "/" + transpiler.getProperty( "classname" ) @@ -659,8 +668,6 @@ public static List array( Type type, List values, BiFun } nodes.addAll( toAdd ); - nodes.add( new LdcInsnNode( "DEBUG - ASMHelper 349" ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); nodes.add( new InsnNode( Opcodes.AASTORE ) ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java index 1ca3e00a5..f75a8c003 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/MethodContextTracker.java @@ -13,6 +13,8 @@ import org.objectweb.asm.tree.TryCatchBlockNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.ast.BoxNode; + public class MethodContextTracker { private int varCount = 0; @@ -21,11 +23,58 @@ public class MethodContextTracker { private List tryCatchBlockNodes = new ArrayList(); private Map breaks = new LinkedHashMap<>(); private Map continues = new LinkedHashMap<>(); + private final CompilationType type; + private Map nodeBreaks = new LinkedHashMap<>(); + private Map nodeContinues = new LinkedHashMap<>(); + private Map stringLabel = new LinkedHashMap<>(); + + public enum CompilationType { + BoxClass, + Component, + Function + } public record VarStore( int index, List nodes ) { } + public MethodContextTracker( boolean isStatic ) { + this( CompilationType.BoxClass, isStatic ); + } + + public MethodContextTracker( CompilationType type, boolean isStatic ) { + this.type = type; + varCount = isStatic ? -1 : 0; + } + + public boolean canReturn() { + return this.type == CompilationType.Function; + } + + public void setStringLabel( String label, BoxNode target ) { + stringLabel.put( label, target ); + } + + public BoxNode getStringLabel( String label ) { + return stringLabel.get( label ); + } + + public void setBreak( BoxNode node, LabelNode label ) { + nodeBreaks.put( node, label ); + } + + public LabelNode getBreak( BoxNode node ) { + return nodeBreaks.get( node ); + } + + public void setContinue( BoxNode node, LabelNode label ) { + nodeContinues.put( node, label ); + } + + public LabelNode getContinue( BoxNode node ) { + return nodeContinues.get( node ); + } + public LabelNode getCurrentBreak( String label ) { return breaks.get( label == null ? "" : label ); } @@ -66,10 +115,6 @@ public int getUnusedStackCount() { return unusedStackEntries; } - public MethodContextTracker( boolean isStatic ) { - varCount = isStatic ? -1 : 0; - } - public void trackUnusedStackEntry() { unusedStackEntries += 1; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 60daf1ed6..5f59bd019 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -46,6 +46,7 @@ public abstract class Transpiler implements ITranspiler { private List tryCatchBlockNodes = new ArrayList(); private int lambdaCounter = 0; private int componentCounter = 0; + private int functionBodyCounter = 0; private Map breaks = new LinkedHashMap<>(); private Map continues = new LinkedHashMap<>(); private List imports = new ArrayList<>(); @@ -62,6 +63,18 @@ public void setProperty( String key, String value ) { properties.put( key, value ); } + public boolean canReturn() { + return functionBodyCounter > 0; + } + + public void incrementfunctionBodyCounter() { + functionBodyCounter++; + } + + public void decrementfunctionBodyCounter() { + functionBodyCounter--; + } + public boolean isInsideComponent() { return componentCounter > 0; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java index c78cec5cc..a4749603a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java @@ -26,12 +26,22 @@ import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.expression.BoxClosure; +import ortus.boxlang.compiler.ast.expression.BoxLambda; import ortus.boxlang.compiler.ast.statement.BoxBreak; +import ortus.boxlang.compiler.ast.statement.BoxDo; +import ortus.boxlang.compiler.ast.statement.BoxForIn; +import ortus.boxlang.compiler.ast.statement.BoxForIndex; +import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxSwitch; +import ortus.boxlang.compiler.ast.statement.BoxWhile; +import ortus.boxlang.compiler.ast.statement.component.BoxComponent; import ortus.boxlang.runtime.components.Component; public class BoxBreakTransformer extends AbstractTransformer { @@ -42,22 +52,23 @@ public BoxBreakTransformer( Transpiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxBreak breakNode = ( BoxBreak ) node; - ExitsAllowed exitsAllowed = getExitsAllowed( node ); + BoxBreak breakNode = ( BoxBreak ) node; + ExitsAllowed exitsAllowed = getExitsAllowed( node ); - LabelNode currentBreak = null; - - if ( transpiler.getCurrentMethodContextTracker().isPresent() ) { - currentBreak = transpiler.getCurrentMethodContextTracker().get().getCurrentBreak( breakNode.getLabel() ); - } - - List nodes = new ArrayList(); + MethodContextTracker tracker = transpiler.getCurrentMethodContextTracker().get(); + List nodes = new ArrayList(); if ( returnContext.nullable || exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } + BoxNode labelTarget = tracker.getStringLabel( breakNode.getLabel() ); + LabelNode currentBreak = tracker.getBreak( labelTarget != null ? labelTarget : getTargetAncestor( breakNode ) ); + if ( currentBreak != null ) { + if ( returnContext.nullable && nodes.size() == 0 ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } nodes.add( new JumpInsnNode( Opcodes.GOTO, currentBreak ) ); return nodes; } @@ -89,4 +100,10 @@ public List transform( BoxNode node, TransformerContext contex throw new RuntimeException( "Cannot break from current location" ); } + + public BoxNode getTargetAncestor( BoxNode node ) { + return node.getFirstNodeOfTypes( BoxSwitch.class, BoxFunctionDeclaration.class, BoxClosure.class, BoxLambda.class, BoxComponent.class, BoxDo.class, + BoxForIndex.class, BoxForIn.class, + BoxWhile.class ); + } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java index 13974a46f..98e966885 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java @@ -23,12 +23,22 @@ import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.expression.BoxClosure; +import ortus.boxlang.compiler.ast.expression.BoxLambda; import ortus.boxlang.compiler.ast.statement.BoxContinue; +import ortus.boxlang.compiler.ast.statement.BoxDo; +import ortus.boxlang.compiler.ast.statement.BoxForIn; +import ortus.boxlang.compiler.ast.statement.BoxForIndex; +import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxWhile; +import ortus.boxlang.compiler.ast.statement.component.BoxComponent; public class BoxContinueTransformer extends AbstractTransformer { @@ -38,22 +48,24 @@ public BoxContinueTransformer( Transpiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxContinue continueNode = ( BoxContinue ) node; - ExitsAllowed exitsAllowed = getExitsAllowed( node ); + BoxContinue continueNode = ( BoxContinue ) node; + ExitsAllowed exitsAllowed = getExitsAllowed( node ); - LabelNode currentBreak = transpiler.getCurrentMethodContextTracker().get().getCurrentContinue( continueNode.getLabel() ); - - if ( transpiler.getCurrentMethodContextTracker().isPresent() ) { - currentBreak = transpiler.getCurrentMethodContextTracker().get().getCurrentContinue( continueNode.getLabel() ); - } - - List nodes = new ArrayList(); + List nodes = new ArrayList(); + AsmHelper.addDebugLabel( nodes, "BoxContinue" ); if ( returnContext.nullable || exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } + MethodContextTracker tracker = transpiler.getCurrentMethodContextTracker().get(); + BoxNode labelTarget = tracker.getStringLabel( continueNode.getLabel() ); + LabelNode currentBreak = tracker.getContinue( labelTarget != null ? labelTarget : getTargetAncestor( continueNode ) ); + if ( currentBreak != null ) { + if ( returnContext.nullable && nodes.size() == 0 ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } nodes.add( new JumpInsnNode( Opcodes.GOTO, currentBreak ) ); return nodes; } @@ -61,6 +73,8 @@ public List transform( BoxNode node, TransformerContext contex if ( exitsAllowed.equals( ExitsAllowed.COMPONENT ) ) { // template = "if(true) return Component.BodyResult.ofBreak(" + componentLabel + ");"; } else if ( exitsAllowed.equals( ExitsAllowed.LOOP ) ) { + nodes.add( new JumpInsnNode( Opcodes.GOTO, currentBreak ) ); + return nodes; // template = "if(true) break " + breakLabel + ";"; } else if ( exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ARETURN ) ); @@ -72,7 +86,18 @@ public List transform( BoxNode node, TransformerContext contex // throw new RuntimeException( "Cannot break from current location" ); // } - throw new RuntimeException( "Cannot break from current location" ); + throw new RuntimeException( "Cannot continue from current location" ); + + } + + @SuppressWarnings( "unchecked" ) + public BoxNode getTargetAncestor( BoxNode node ) { + return node.getFirstNodeOfTypes( BoxFunctionDeclaration.class, BoxClosure.class, BoxLambda.class, BoxComponent.class, BoxDo.class, + BoxForIndex.class, BoxForIn.class, + BoxWhile.class ); + } + private boolean isLoop( BoxNode node ) { + return node instanceof BoxForIndex; } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java index 457d79b17..74ef035b3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java @@ -14,6 +14,7 @@ */ package ortus.boxlang.compiler.asmboxpiler.transformer.expression; +import java.util.ArrayList; import java.util.List; import org.objectweb.asm.tree.AbstractInsnNode; @@ -36,13 +37,17 @@ public BoxStatementBlockTransformer( Transpiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxStatementBlock boxStatementBlock = ( BoxStatementBlock ) node; + BoxStatementBlock boxStatementBlock = ( BoxStatementBlock ) node; + List nodes = new ArrayList(); + + AsmHelper.addDebugLabel( nodes, "BoxStatementBlock" ); if ( boxStatementBlock.getBody().size() == 0 && ReturnValueContext.VALUE_OR_NULL == returnContext ) { - return List.of( new InsnNode( Opcode.ACONST_NULL ) ); + nodes.add( new InsnNode( Opcode.ACONST_NULL ) ); + return nodes; } - List nodes = AsmHelper.transformBodyExpressions( transpiler, boxStatementBlock.getBody(), context, returnContext ); + nodes.addAll( AsmHelper.transformBodyExpressions( transpiler, boxStatementBlock.getBody(), context, returnContext ) ); return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java index 89f5f484f..cfd415e3d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java @@ -26,6 +26,7 @@ import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -49,15 +50,21 @@ public List transform( BoxNode node, TransformerContext contex List condition = transpiler.transform( boxSwitch.getCondition(), TransformerContext.NONE, ReturnValueContext.VALUE ); List nodes = new ArrayList<>(); + AsmHelper.addDebugLabel( nodes, "BoxSwitch" ); nodes.addAll( condition ); nodes.add( new LdcInsnNode( 0 ) ); - LabelNode endLabel = new LabelNode(); + LabelNode endLabel = new LabelNode(); + LabelNode breakTarget = new LabelNode(); + + transpiler.getCurrentMethodContextTracker().ifPresent( t -> t.setBreak( boxSwitch, endLabel ) ); + boxSwitch.getCases().forEach( c -> { if ( c.getCondition() == null ) { return; } + AsmHelper.addDebugLabel( nodes, "BoxSwitch - case start" ); LabelNode startOfCase = new LabelNode(), endOfCase = new LabelNode(), endOfAll = new LabelNode(); nodes.add( new JumpInsnNode( Opcodes.IFNE, startOfCase ) ); // this dupes the condition @@ -96,14 +103,15 @@ public List transform( BoxNode node, TransformerContext contex false ) ); nodes.add( new JumpInsnNode( Opcodes.IFEQ, endOfCase ) ); nodes.add( startOfCase ); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, endLabel ); + c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE ) ) ); - transpiler.getCurrentMethodContextTracker().get().removeCurrentBreak( null ); // TODO: label name? + nodes.add( new LdcInsnNode( 1 ) ); nodes.add( new JumpInsnNode( Opcodes.GOTO, endOfAll ) ); nodes.add( endOfCase ); nodes.add( new LdcInsnNode( 0 ) ); nodes.add( endOfAll ); + AsmHelper.addDebugLabel( nodes, "BoxSwitch - case end" ); } ); // TODO: Can there be more than one default case? @@ -113,18 +121,33 @@ public List transform( BoxNode node, TransformerContext contex if ( hasDefault ) { throw new ExpressionException( "Multiple default cases not supported", c.getPosition(), c.getSourceText() ); } + AsmHelper.addDebugLabel( nodes, "BoxSwitch - default case" ); hasDefault = true; + + // pop the initial 0 constant in case we didn't match any cases nodes.add( new InsnNode( Opcodes.POP ) ); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( null, endLabel ); + // pop the condition off the stack + // nodes.add( new InsnNode( Opcodes.POP ) ); + c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE ) ) ); - transpiler.getCurrentMethodContextTracker().get().removeCurrentBreak( null ); + nodes.add( new JumpInsnNode( Opcodes.GOTO, endLabel ) ); } } + // pop the initial 0 constant in case we didn't match any cases nodes.add( new InsnNode( Opcodes.POP ) ); nodes.add( endLabel ); + + // pop the condition off the stack nodes.add( new InsnNode( Opcodes.POP ) ); + // nodes.add( breakTarget ); + + if ( !returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } + + AsmHelper.addDebugLabel( nodes, "BoxSwitch - done" ); return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index c7fb895cf..00b24c71f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -10,6 +10,8 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; @@ -64,8 +66,47 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.getType( Component.BodyResult.class ), Type.getType( Key.class ), Type.getType( IStruct.class ), Type.getType( Component.ComponentBody.class ) ), true ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); + if ( boxComponent.getBody() == null || boxComponent.getBody().size() == 0 ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + + transpiler.decrementComponentCounter(); + + return nodes; + } + + if ( transpiler.canReturn() ) { + LabelNode ifLabel = new LabelNode(); + + nodes.add( new InsnNode( Opcodes.DUP ) ); + + nodes.add( + new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( Component.BodyResult.class ), + "isEarlyExit", + Type.getMethodDescriptor( Type.BOOLEAN_TYPE ), + false + ) + ); + + nodes.add( new JumpInsnNode( Opcodes.IFEQ, ifLabel ) ); + + nodes.add( + new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( Component.BodyResult.class ), + "returnValue", + Type.getMethodDescriptor( Type.getType( Object.class ) ), + false + ) + ); + + nodes.add( new InsnNode( Opcodes.ARETURN ) ); + + nodes.add( ifLabel ); + } + nodes.add( new InsnNode( Opcodes.POP ) ); transpiler.decrementComponentCounter(); return nodes; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index c07a9f0e8..09991d0fa 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -30,6 +30,7 @@ import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker.VarStore; @@ -54,9 +55,10 @@ public BoxForInTransformer( Transpiler transpiler ) { } public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) { - BoxForIn forIn = ( BoxForIn ) node; - List nodes = new ArrayList<>(); - Optional trackerOption = transpiler.getCurrentMethodContextTracker(); + BoxForIn forIn = ( BoxForIn ) node; + List nodes = new ArrayList<>(); + AsmHelper.addDebugLabel( nodes, "BoxForIn" ); + Optional trackerOption = transpiler.getCurrentMethodContextTracker(); if ( trackerOption.isEmpty() ) { throw new IllegalStateException(); @@ -67,11 +69,11 @@ public List transform( BoxNode node, TransformerContext contex LabelNode loopStart = new LabelNode(); LabelNode loopEnd = new LabelNode(); - tracker.setCurrentContinue( null, loopStart ); - tracker.setCurrentContinue( forIn.getLabel(), loopStart ); - - tracker.setCurrentBreak( null, loopEnd ); - tracker.setCurrentBreak( forIn.getLabel(), loopEnd ); + tracker.setContinue( forIn, loopStart ); + tracker.setBreak( forIn, loopEnd ); + if ( forIn.getLabel() != null ) { + tracker.setStringLabel( forIn.getLabel(), forIn ); + } // access the collection nodes.addAll( transpiler.transform( forIn.getExpression(), context, ReturnValueContext.VALUE_OR_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java index 73ff84ae2..3c05b4f32 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java @@ -28,6 +28,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import javassist.bytecode.Opcode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; @@ -45,9 +46,10 @@ public BoxForIndexTransformer( Transpiler transpiler ) { public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) { - BoxForIndex forIn = ( BoxForIndex ) node; - List nodes = new ArrayList<>(); - Optional trackerOption = transpiler.getCurrentMethodContextTracker(); + BoxForIndex forIn = ( BoxForIndex ) node; + List nodes = new ArrayList<>(); + AsmHelper.addDebugLabel( nodes, "BoxForIndex" ); + Optional trackerOption = transpiler.getCurrentMethodContextTracker(); if ( trackerOption.isEmpty() ) { throw new IllegalStateException(); @@ -60,13 +62,14 @@ public List transform( BoxNode node, TransformerContext contex LabelNode loopStart = new LabelNode(); LabelNode loopEnd = new LabelNode(); - tracker.setCurrentBreak( forIn.getLabel(), breakTarget ); - tracker.setCurrentBreak( null, breakTarget ); - - tracker.setCurrentContinue( null, loopStart ); - tracker.setCurrentContinue( forIn.getLabel(), loopStart ); + tracker.setContinue( forIn, loopStart ); + tracker.setBreak( forIn, breakTarget ); + if ( forIn.getLabel() != null ) { + tracker.setStringLabel( forIn.getLabel(), forIn ); + } if ( forIn.getInitializer() != null ) { + AsmHelper.addDebugLabel( nodes, "BoxForIndex - initializer" ); nodes.addAll( transpiler.transform( forIn.getInitializer(), context, ReturnValueContext.EMPTY ) ); } @@ -79,17 +82,21 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new JumpInsnNode( Opcode.GOTO, firstLoop ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - loopStart" ); nodes.add( loopStart ); if ( forIn.getStep() != null ) { + AsmHelper.addDebugLabel( nodes, "BoxForIndex - step" ); nodes.addAll( transpiler.transform( forIn.getStep(), context, ReturnValueContext.EMPTY ) ); } + AsmHelper.addDebugLabel( nodes, "BoxForIndex - firstLoop" ); nodes.add( firstLoop ); nodes.add( new InsnNode( Opcodes.SWAP ) ); nodes.add( new InsnNode( Opcodes.POP ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - condition" ); if ( forIn.getCondition() != null ) { nodes.addAll( transpiler.transform( forIn.getCondition(), context, ReturnValueContext.VALUE ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, @@ -109,15 +116,18 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new JumpInsnNode( Opcodes.IFEQ, loopEnd ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - body" ); nodes.addAll( transpiler.transform( forIn.getBody(), context, ReturnValueContext.VALUE_OR_NULL ) ); nodes.add( new JumpInsnNode( Opcode.GOTO, loopStart ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - breakTarget" ); nodes.add( breakTarget ); nodes.add( new InsnNode( Opcodes.SWAP ) ); nodes.add( new InsnNode( Opcodes.POP ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - loopEnd" ); nodes.add( loopEnd ); if ( returnValueContext.empty ) { @@ -128,6 +138,8 @@ public List transform( BoxNode node, TransformerContext contex tracker.setCurrentContinue( null, null ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - done" ); + return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index ce18080b9..63615cbf5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -21,6 +21,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -133,6 +134,7 @@ public List transform( BoxNode node, TransformerContext contex "getSourceType", Type.getType( BoxSourceType.class ) ); + transpiler.incrementfunctionBodyCounter(); AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, transpiler, true, () -> { @@ -146,6 +148,7 @@ public List transform( BoxNode node, TransformerContext contex .flatMap( statement -> transpiler.transform( statement, safe, ReturnValueContext.EMPTY ).stream() ) .toList(); } ); + transpiler.decrementfunctionBodyCounter(); AsmHelper.complete( classNode, type, methodVisitor -> { transpiler.createKey( function.getName() ).forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); @@ -203,6 +206,10 @@ public List transform( BoxNode node, TransformerContext contex true ) ); + if ( returnContext.nullable ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + } + return nodes; } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java index e806e107f..4c08827c8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -44,6 +45,7 @@ public List transform( BoxNode node, TransformerContext contex BoxIfElse ifElse = ( BoxIfElse ) node; List nodes = new ArrayList<>(); + AsmHelper.addDebugLabel( nodes, "BoxIf" ); nodes.addAll( transpiler.transform( ifElse.getCondition(), TransformerContext.NONE, ReturnValueContext.VALUE ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( BooleanCaster.class ), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java index 37849793e..01e1c478b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -48,14 +49,13 @@ public List transform( BoxNode node, TransformerContext contex breakTarget = new LabelNode(); List nodes = new ArrayList<>(); - // if ( boxWhile.getLabel() != null ) { + MethodContextTracker tracker = transpiler.getCurrentMethodContextTracker().get(); + tracker.setBreak( boxWhile, breakTarget ); + tracker.setContinue( boxWhile, start ); - // } - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( boxWhile.getLabel(), breakTarget ); - transpiler.getCurrentMethodContextTracker().get().setCurrentBreak( "", breakTarget ); - - transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( null, start ); - transpiler.getCurrentMethodContextTracker().get().setCurrentContinue( boxWhile.getLabel(), start ); + if ( boxWhile.getLabel() != null ) { + tracker.setStringLabel( boxWhile.getLabel(), boxWhile ); + } // push two nulls onto the stack in order to initialize our strategy for keeping the stack height consistent // this is to allow the statement to return an expression in the case of a BoxScript execution diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java index c2fd0e97a..494c6cd9d 100644 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ b/src/test/java/TestCases/asm/integration/ControllerTest.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import ortus.boxlang.runtime.BoxRuntime; @@ -63,12 +62,35 @@ public void teardownEach() { } @Test - @Disabled( "Needs update for new constructor argument in Property record" ) - public void testControllerCompilation() { + public void testSwitchInLoopInFunc() { instance.executeStatement( """ - controller = new src.test.java.TestCases.asm.integration.Controller(); - """, + function a(){ + for ( x = 1; x < 5; x++ ) { + switch( x ){ + } + } + } + """, + context ); + } + + @Test + public void testSwitchWithCaseInLoopInFunc() { + instance.executeStatement( + """ + function a(){ + for ( x = 1; x < 5; x++ ) { + switch( x ){ + case "id": { + + } + } + } + + } + a() + """, context ); } @@ -115,4 +137,42 @@ public void testNestedLFunctionInComponent() { assertThat( variables.get( result ) ).isEqualTo( "test" ); } + @Test + public void testReturnFromComponentInFunction() { + + instance.executeStatement( + """ + function a(){ + lock name="what" timeout=300 { + return "test"; + } + } + + result = a(); + """, + context ); + + assertThat( variables.get( result ) ).isEqualTo( "test" ); + } + + @Test + public void testContinueInForIndex() { + + instance.executeStatement( + """ + a = [ "orange", "red", "yellow" ] + + for( color in a ){ + + switch( color ){ + case "orange": result = color; break; + default: continue; + } + } + """, + context ); + + assertThat( variables.get( result ) ).isEqualTo( "orange" ); + } + } diff --git a/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java b/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java index af32a6fba..18fa1333e 100644 --- a/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java +++ b/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java @@ -154,7 +154,7 @@ public void testSimpleLabeledForIn() { """ data = [1,2,3] result = 0 - mylabel : for( var x in data ) { + mylabel : for( x in data ) { result ++ break mylabel; result ++ From feb5a732ab9e9ef7f79068b7e7e8d244a7270ee5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 15 Oct 2024 17:49:50 -0500 Subject: [PATCH 44/71] BL-655 --- .../ortus/boxlang/compiler/BXCompiler.java | 2 +- .../compiler/javaboxpiler/JavaBoxpiler.java | 1 - .../runtime/loader/DiskClassLoader.java | 72 +++++++++-- .../compiler/LoadPrecompiledTemplateTest.java | 121 ++++++++++++++++++ .../boxlang/compiler/Precompiled-source.bx | 5 + .../boxlang/compiler/Precompiled-source.bxs | 1 + .../ortus/boxlang/compiler/Precompiled.bx | Bin 0 -> 19468 bytes .../ortus/boxlang/compiler/Precompiled.bxs | Bin 0 -> 4104 bytes 8 files changed, 187 insertions(+), 15 deletions(-) create mode 100644 src/test/java/ortus/boxlang/compiler/LoadPrecompiledTemplateTest.java create mode 100644 src/test/java/ortus/boxlang/compiler/Precompiled-source.bx create mode 100644 src/test/java/ortus/boxlang/compiler/Precompiled-source.bxs create mode 100644 src/test/java/ortus/boxlang/compiler/Precompiled.bx create mode 100644 src/test/java/ortus/boxlang/compiler/Precompiled.bxs diff --git a/src/main/java/ortus/boxlang/compiler/BXCompiler.java b/src/main/java/ortus/boxlang/compiler/BXCompiler.java index 346ead9bb..658080c3c 100644 --- a/src/main/java/ortus/boxlang/compiler/BXCompiler.java +++ b/src/main/java/ortus/boxlang/compiler/BXCompiler.java @@ -166,7 +166,7 @@ public static void main( String[] args ) { } } - private static void compileFile( Path sourcePath, Path targetPath, Boolean stopOnError, BoxRuntime runtime, Path basePath, String mapping ) { + public static void compileFile( Path sourcePath, Path targetPath, Boolean stopOnError, BoxRuntime runtime, Path basePath, String mapping ) { try { Path directoryPath = targetPath.getParent(); if ( directoryPath != null && !Files.exists( directoryPath ) ) { diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java index 11900b398..d7be6d9b4 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java @@ -181,7 +181,6 @@ public void compileClassInfo( String classPoolName, String FQN ) { File sourceFile = classInfo.resolvedFilePath().absolutePath().toFile(); // Check if the source file contains Java bytecode by reading the first few bytes if ( diskClassUtil.isJavaBytecode( sourceFile ) ) { - System.out.println( "Loading bytecode direct from pre-compiled source file for " + FQN ); classInfo.getClassLoader().defineClasses( FQN, sourceFile ); return; } diff --git a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java index 099357540..1a450a244 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java @@ -22,8 +22,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; import ortus.boxlang.compiler.ClassInfo; import ortus.boxlang.compiler.IBoxpiler; @@ -80,18 +81,26 @@ public DiskClassLoader( URL[] urls, ClassLoader parent, Path diskStore, IBoxpile */ @Override protected Class findClass( String name ) throws ClassNotFoundException { + Path diskPath = generateDiskPath( name ); // JIT compile - ClassInfo classInfo = boxPiler.getClassPool( classPoolName ).get( IBoxpiler.getBaseFQN( name ) ); - if ( !hasClass( diskPath ) || ( classInfo != null && ( classInfo.lastModified() > diskPath.toFile().lastModified() ) ) ) { - // After this call, the class files will exist on disk - boxPiler.compileClassInfo( classPoolName, name ); + String baseName = IBoxpiler.getBaseFQN( name ); + ClassInfo classInfo = boxPiler.getClassPool( classPoolName ).get( baseName ); + // Do we need to compile the class? + // Pre-compiled source files will follow this path, but will be discovered as already compiled when we try to parse them + if ( needsCompile( classInfo, diskPath, name, baseName ) ) { + // After this call, the class files will exist on disk, or will have been side-loaded into this classloader via the + // defineClass method (for pre-compiled source files) + boxPiler.compileClassInfo( classPoolName, baseName ); } + // If there is no class file on disk, then we assume pre-compiled class bytes were already side loaded in, so we just get them + // TODO: Change this to use the same flag discussed in the needsCompile method if ( !diskPath.toFile().exists() ) { return loadClass( name ); } - // Read file as byte array + + // In all other scenarios, the BoxPiler should have compiled the class and written it to disk byte[] bytes; try { bytes = Files.readAllBytes( diskPath ); @@ -102,6 +111,40 @@ protected Class findClass( String name ) throws ClassNotFoundException { return defineClass( name, bytes, 0, bytes.length ); } + /** + * Abstract out the logic for determining if a class needs to be compiled + * + * @param classInfo the class info, may be null if there is none + * @param diskPath the path to the class file on disk, may not exist + * @param name the fully qualified class name + * @param baseName the base name of the class + * + * @return true if the class needs to be compiled + */ + private boolean needsCompile( ClassInfo classInfo, Path diskPath, String name, String baseName ) { + // TODO: need to add some mutable flags to the classInfo object to track if it has been compiled or not + // and in what manner. For example, precompiled sources read directly into the class loader won't + // have a class file on disk and we should be able to just skip that check entirely if we know that. + // Using the existence of the class file on disk is not a good indicator of whether or not the class + // needs to be compiled or not, as a precompiled source file will not have a class file on disk. + + // If we're loading an inner class, we know the compilation has already happened + if ( !name.equals( baseName ) ) { + return false; + } + // There is no class file cached on disk + if ( !hasClass( diskPath ) ) { + return true; + } + + // If the class file is older than the source file + if ( classInfo != null && classInfo.lastModified() > diskPath.toFile().lastModified() ) { + return true; + } + // The class file exists on disk and is up to date + return false; + } + /** * Check if a class exists on disk * @@ -175,8 +218,7 @@ public void defineClasses( String fqn, File sourceFile ) { // remove initial magic number buffer.getInt(); - List classBytesList = new ArrayList<>(); - boolean first = true; + boolean first = true; while ( buffer.hasRemaining() ) { // Read the length of the class file @@ -195,18 +237,22 @@ public void defineClasses( String fqn, File sourceFile ) { + "]. Pre-compiled source code must have the same path and name as the original file." ); } } else { + // String className = getClassName( classBytes ); // Define the class defineClass( null, classBytes, 0, classBytes.length ); } } - for ( byte[] classBytes : classBytesList ) { - // Define the class - defineClass( null, classBytes, 0, classBytes.length ); - } } catch ( IOException e ) { throw new RuntimeException( "Failed to read file", e ); } } + public static String getClassName( byte[] classBytes ) { + ClassReader classReader = new ClassReader( classBytes ); + ClassNode classNode = new ClassNode(); + classReader.accept( classNode, 0 ); + return classNode.name.replace( '/', '.' ); + } + } diff --git a/src/test/java/ortus/boxlang/compiler/LoadPrecompiledTemplateTest.java b/src/test/java/ortus/boxlang/compiler/LoadPrecompiledTemplateTest.java new file mode 100644 index 000000000..f1c41851a --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/LoadPrecompiledTemplateTest.java @@ -0,0 +1,121 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.compiler; + +import static com.google.common.truth.Truth.assertThat; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; + +public class LoadPrecompiledTemplateTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + } + + @Test + public void testPrecompiledTemplate() { + Path source = Path.of( "src/test/java/ortus/boxlang/compiler/Precompiled-source.bxs" ); + Path target = Path.of( "src/test/java/ortus/boxlang/compiler/Precompiled.bxs" ); + // delete target, if exists + if ( target.toFile().exists() ) { + target.toFile().delete(); + } + // copy source over target on disk + try { + Files.copy( source, target ); + } catch ( Exception e ) { + e.printStackTrace(); + } + + BXCompiler.compileFile( + target, + target, + true, + instance, + Path.of( "" ), + "/" + ); + instance.executeSource( + """ + include 'src/test/java/ortus/boxlang/compiler/Precompiled.bxs' + """, + context, BoxSourceType.BOXSCRIPT ); + assertThat( variables.get( result ) ).isEqualTo( 4 ); + } + + @Test + public void testPrecompiledClass() { + Path source = Path.of( "src/test/java/ortus/boxlang/compiler/Precompiled-source.bx" ); + Path target = Path.of( "src/test/java/ortus/boxlang/compiler/Precompiled.bx" ); + // delete target, if exists + if ( target.toFile().exists() ) { + target.toFile().delete(); + } + // copy source over target on disk + try { + Files.copy( source, target ); + } catch ( Exception e ) { + e.printStackTrace(); + } + + BXCompiler.compileFile( + target, + target, + true, + instance, + Path.of( "" ), + "/" + ); + instance.executeSource( + """ + foo = new src.test.java.ortus.boxlang.compiler.Precompiled(); + result = foo.bar() + """, + context, BoxSourceType.BOXSCRIPT ); + assertThat( variables.get( result ) ).isEqualTo( "brad" ); + + } +} diff --git a/src/test/java/ortus/boxlang/compiler/Precompiled-source.bx b/src/test/java/ortus/boxlang/compiler/Precompiled-source.bx new file mode 100644 index 000000000..7f9f4e1ad --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Precompiled-source.bx @@ -0,0 +1,5 @@ +class { + function bar() { + return "brad"; + } +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/compiler/Precompiled-source.bxs b/src/test/java/ortus/boxlang/compiler/Precompiled-source.bxs new file mode 100644 index 000000000..1b03bf6e6 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Precompiled-source.bxs @@ -0,0 +1 @@ +result = 2 + 2; \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/compiler/Precompiled.bx b/src/test/java/ortus/boxlang/compiler/Precompiled.bx new file mode 100644 index 0000000000000000000000000000000000000000..dab7fe845ac676d6c1617d166f8b1d46d8ade97c GIT binary patch literal 19468 zcmd5^3!GeKbw6izvv+oK^V%iBB;)}kB)ds=^F+-C%x062CD|lo^B}MgW@m4*LuO}| znc0x=QcxZ$Ao5UD@QsR!LY9z$h^UAtK3l8U+G<;CZMCg!ZMCgd{h#l<_s%zW?%iaQ zP@3N_b7$_o=X~co=X~couWw)a{WG5@qR!sj!F^8F$tR0WUkiSuGRZ=rrI1gx6rDn` zY%htT(e(#&pdyuGY=1UrBJAX4lF{-hs)}}F3 z$23v!vzIi3#r_oQ)ulF z!geO}>11!lDRj%t1X+}j5ymnto4+X>9B8r%q_tC_*yjFjYuZ^)<7m7^6KtADlem%> zLWbf+D|pc=yl7Q(s2s%OMQbH5n8qKdppDz-Z;3ptER(MKf)hMYEY^k4W)G=MYmYx0mVs z`J2iL(Ot}^v-{ebMrIIz+hmgG*mN$f1yGegC6rnNnvPafKoE))~AOpExnHocBs&y+}K zi%x!T5^z{tdO!stZ?`3=jaFH7flU|E8?+O-p{lR=Gfn-PRTaUSwECb?deTI*>^&$rynnL zV}P<2}DKv*F+lutR^p}Ey) z{4I7cnJ>UzK})***A<%EY`R^HqLm&HbyahIt4(jCw}YWDWyN$#xeL*2#@!9O%kL`0 zciD6|y#q*+*=$apLI0WNM(D8?N+-56tPlnNPMhu(_;G!?)X;#F)hI+H_uKSlv>m*Ei<#D*#F)wiDjm^S zl-c=!Y>*#hT6anay=_!@^kLaXKf*LPYK#$b3t5P8M#{4>2YX{wY#_)(im^fYV?d@*M){wv4pqyKNH_q0EP{9`tKj-F*Y z!yvys+o%0orlYU!JT9*mgK_6LQ+rOP_64TlS9cDYVBdc{ar(bx)ARIYXci2H>?GLW zv777~^=zW7Mdf}cCh99Ty+~h$8{&B}onDl}lpOWA5sR#Bq_9K!11Lx6CHlHW->~VM z^ev{DWec8G(N-&%fG}c1GTVm)0!&554NjUo?_O}OMk}n zn(`1>yBVZ4+%docFnz6q1cJo2uCZ&Z7)RYy7{71R%k%^IFMtvEAFHY2O@BN64*i8q zKa`Qj;ni(UaVVb^r%A~AE1P~qKh^_pP3D0HUZgz4Oo7f^3Es$mLw{@0Pi*=-`YF@& z@?0u7ASmFF03CRzf2=X1ca5wXy=gD15MtUf`k77tAajXhE(%M6eonuz=$~x*XZjcT zSrHVu0(4~p{ob3-_H_?BDeNpv`D)^fCF8QvxKmA=@la1Law?h2(#7k5EjG}-llV8K70dG zdcU^mH}qeKln0Xg9RyT}?02P${rDzfeG&R_p2;JZCFUf3h5Hr#kEGXr3(q+O#9-9+ z{(SCQ$+D?+_;dPSn|@D!Ko~oebyvnkD#*v=Ohn$QS)Xn_A*YK}-jLmgyAvD?V9KwXpy*$F07(4b@uIGfsW0A^Y8?K5Hl`^}MQ;|sC zWs!fcEzaYaxHZlbnUKJ2Ko$}Sv>r(o_6T^75@b)WPMr35kxY+~ENtmL80RyXF6&s? zvjg5yp{F;W?CV+U1`LIsi_*mnL%lwZlfa+gDSW2IuaSvOWm*ufU20arD2?e_Q(V=n zXB2m$wvo_*+v<5bpJnk3n`cUd7$Dl?WNTPVh-}=NEtht8Z0p>*JGDNO z7B5<|V*BD1E0@1+<+5c7K8Me>c#h3;`8-dds)J$V`qU6Azg%9t9sS9Cw{y*qIBTBT zRs*+LjAY?C;r!OC8T>=AnBaLx@9{idVDmx=7{{#Z+PZyrf?K%N;>9*E;iXuDBOy{; zM1E<5lTlI0)QaTTmc8YSfTR^nb6x44dU)Do@Z~nIkdYhR9Gi(I&aXv!0BMx5{CZw# zahs^us)|e)@>W=SrUHeGCCM#|Q3jYFKE3MDJi!;JWGJ`WyoM1(Ptq}dC&~#rxah;G zyv*WtObf%2_8mg@IGt+k+~&F5V!JH9i0SNb2raHo*#Ir>#Fna*A)RaOb|V@=fK;pw zaCYhlXh-K-P;xPt;u+ta$;^;521Z)`!JO51BUnjd9|vr^wx>!0VschR*eo~oisUg! zUF%@?!@P6ZwhP-}wcM5A62P?EKnnGU!fDvSh*32ITs1nR21}k^++NmI@^k)#mKkF} zRZf6@VZ*p|;bPd|bq9-1wy#j5m`$q!od{0wH>)WNNNHWm$P|jpcfvNmV1Lr zp?O6d_hzlsVVE|az8oV+s8aSk^4w9rHG=Vw)G9np_W=xRgt)^Xk=0L05za3bL;;5t z2f#J>dD;u`ssfi+Y^kSPJ!L$oj8TPXz#UJ^w0j2eq)s;x-Ut!T zUQlH<4Yw#t6SeuM0BhQbYM>0(uqULvAXA2St4g=-)P19diZP6;6l(%+^hsuWx;Ocd z9a*Q(JgD~W?ZH^f-J!I$dhbcX8fBA6o`kzFI!Y_&s>3jh=*U2jk_gs@9-7+WP3)Am z2#HKRL!}GIVT9!?i|>^HAWZ6ee(_ksgD8D*cNYI$z@7nED7crF*I!4{xoRH(SR@Z> zV9__)%|!jYNTS@`Tzg+GMFyxx(L@a4dEzSjFTT@pCMwB4EqT-Bn(T>A#VlUpNwcw| zTspQ3Sm))RQ%v8<1CnL4I%=7PnQb$&^N=u|g}Gs<&d@?M9&1XX9!t#b=D(Llas)aQ z-gyDPXAkhZ2vqzp1$CWP@6ccEYxC)%v&|XIpvaAd;D@*ciyvZ|;rU;B!9tQwa=?Fw zsW2K2MVLC%DJuK^l|HPJr*AZB5N^VIKOkNnQ}Gp}!4sn?TAzs8LUJ!gzy0)%9uhKW zf2u;GLkVge%oUW2igZVQpDKjGnl*-CWP0=IrP^@$u$#7>D8=Iu9yomlvFj=koq!u^ z4CP8j9LT3%$?2UWRR*Oa>eZ4c+RN)akdei*-Qzc^z)g)P!R(E+r?NKHzLY zOs+N%-$H?qI(S3KsWMG7BoG+X;-`?VD;uAYP{NU_n3PJYNrzM!JUrZn_ZBq>TDHf%+?H=&2fGnTsY3lY6i1;LLI-5;IHtD z7Jn6qQ2rYB>@ovf!C||Qw7klVu1AAWE)TQ)peef$2^YL7fWc6W$h0dJfRZjSAG)uH zSU%Hmx+ppjGIV1Pgm~=EELt>GHd^xe@p21GosOmGY%~?bsFpVKT3*i^XgW=m3@9Ss zX-Gh9M6O{IzQ?$Wrb)8|es58~x8gUNIOIL)#gHps5_liYBkDOiLev#K5$cNG26aUz zgO6_;@Sp*#;y1dyjNb`q?(%Wa=hbw$^mx}tN=A7>Xb#b~C&{pI+E9#uTK6u<5T50T8JwarI~ zn~#yT8(->=kd5yRyN}WtM`&_$!_*Q@KSF0W^AVaWA5HSHP(Bvpy=8cB#SuFH3E+wG zZhSUl7DNYtI|b?;NM|2tPE!-@M|;Eoy8CkI>$?Vgxt1@(o3^HbFXtZos^=^CN|(M1 z@Fa$NV@kAgQ+WWXrx0C-0j^gFrs$V=4_}4*m=Y(s7of~3+6JOzsx9(co*t&vkK?LI z1takNCWDMxO@^y5eZb*_;aD5Qam$D}_WC#$yL8kw?>bBu1+m;_V5zI1WFO$r=I74l z9hkB3V8JoE82sO~srgBo`xRtBr^~4H7@XlE|=<>7aXB0g8K6i*zmA1X_wy^ zw2t#wETS)tuZjk)5+#GUJ_uYNj=&{aDY{tjaa{qrBu&@2phWwgrc8{Q@huM}?}26|KtFk6GV(*+Wx_SgK#eL58O2EMTx_U0h$ zv!Spzv0bgLw+3OqSPkoH{2MXyoA_pTd{bNPAd(jhBxP;Ah2IRd1R)@m*t-vRrTHJKRwW*gJz1z8nHu&u{f%4GI;k zN#PHH|84%8#whm$NBMEcC`>Q&+g%Dx37j1S{)quxmcX69Rjshi_XVMUW-H09cd}Q)5W#2Y_&`Dp&%uH2*=|ZLFRiy zC$u<#hHFD7bYJ*{76&H8aTDiRK$TdqIR^=`!w7-NA6w1Y5`8pC z$V3p*5J8AIh(gG_!f?Tr3*wp#TvLvN>)oM~Y6?zjYS^Us!Ei#Ff`rTfAu}Tg5pP%2 z<&zw&I@b*74>A z3m28>a1hf9g{fJ;@m^Aof+G!2Tgl8HNFRe=Do&I;5_=x1-1!)P9C)|jX^cMs8lLy}7E|C~3{JFL4X?wI zvT=TqA9Yz}Sh*6t5LDY;P}|)KLzPkBPx7Zg2J@$VccVjFB2b*!S=7K!@L`2_iLYKuc$l9Au@YurN>^G4H<^#p zAKVGt2<4IqOxF|mokuFuGmQJ1RN)N{Tlyrb$cJwLi8qpkYn*RFy!l3-lzK%NK)!_3wnu~Nq3vI-if-YJ` zTadiCgf>w(?W7%a6%Kb@O}i1eUqNreuUh^b;A8wO%h6d`%KiKtzKzGdFJp`^sLwC) zOSsnnmi40(?du3veoim(H)tx+#0R_+vh;90UNBC`z9I*lMsr9qMsXXoCQzJ7mb3Kp zqB`ev3C&B9==adk;`u(A3~57M)$IIEF(h4MM=>UA8QiBW@1 zjfB3q`hi?u8mT>%MAV5lKl!U(oZ=pOJ>kPmF`8y00dv*}hno^u!?P@!Z6mFA4q7`$ zs)Vb9S`}xR>tXumTBMGgf(e1vwGpQdmTld!W>aT}u%O9C-e>_53~B#7=JwOqO5z`b zQ@yyeWWW|18`NT?ZcpmCQ;e3{NMr#%c-(0Poo|skxe9?+95_FvgM8+JuNAb?mP4;6 zeHd*8U1-xAXf;X}Cw~|%A*oM`*4nTuIKKCPb?!D!8!&I_8;ukCB5Kf-mw*lHvS_mn zi?)?t>2ukZ9JZY^(kk)nMETh47TSf9y~@n{3p%vP)}q}f)ak(2p@zDN zGERF?q6q5%r6l!Ql(MN0NiFwWadpcG@SH*iDAXzMx9MYg)ujV07t|pN(wVKA11oz1 zcLYpcu*B#0kKmkUE07c&IcDe{{6%p|A3;#1r!Lo!hW82b#YR_R`5?jxwmY0uU!1g% zczS`N22Np|;9jE(gXQB7!5$vB$^uTx2<{dX-MtgO3F+Ppk7=N9*Kv!FCo7CsyVdCx zItC3fp^?UHW0RI0qs;~dz{uVaEfnuCEeePL4YcCC0=-evGPup{T6y)FzAO~Y<x9BPtcdh_R@ABeo{)0G=hBEG{0JDa6i%h@b98m%xYUg| z_Z?@u@z?@?O0R!7axpk{gn%p|UKPGgL%dg~u5fzQbuX%NaOv?qhWBFP!>apmL~&oD z=@`O+Y2qZQ`*3#Dbw6c%xB)!Zb;)VsWa9ZGJj>xP0Wj)&6-75ho`NxtPaUB?Oa*+` z!FDYU^b3_tLkK^rV67nRheBbca;sq%;VQ9TO~cqA=pzPbjX&%my3PeP3oB_6;kJ$9@r>rfbnX!vr;;OT{P7Z|BZREwyYZKco-=UC zpK%bWH^G5K5kztD0^pK=XB?*aFVeVU6hnD);V{j|?=oE)=a*2ZdmfS33#yWjtky&z zxs`5nQ5j`~MP*Q}d@mVfl-Y5680?ZD?6*TH*a z_Ye~JDk*a>-3KYg>3&~Bq$(-@mNs;jXyY(3avGNm%P8H$H0c;!3L#%MOp6ZF<-@e3 z;mQ(SHB6I{AepMt8H$3L7=w_S$6*q-)l&+&7APfwpanS?+lR&8?*%$=EcU!M1!M}PnN2Y|C#dwSfk3`h5jeB3i8 ziv^zCxa;J2=X&uQ`jj5G9k1lZNm|gY@p#UjESd$wi4QwQaL#YbPP@R0dymRu`|wi? zO=wmSQPF}W0$sigEs&u(CCf7>4SBWnY{77~6ZZ6Ipk81}uW6ayF@ffe&atJ4VyS|d zie*s6LX)+s$r?@Oo$G1RWNo3kipkm{lLext<$Ai6GX%~J)CS`}N>0RRb#n|Wuu{P) z6>V58(7Dj6N)QURo;MsVSm7*wN7aKzGU~^wn=wu?;T^ ztngo*G_Cxgp*vYRcDJKJ3pKux1Sg!r}1H!MvHauXwdLRc9)(4nAH#ZGhy zteTH=ieVWyn0#eQFO-ZS=3!k&s<(pb7&$Ldqa}D!t9Y<9nKc~h?ZG|;`&AskL4l5Nz4;kSpEPsYh;iL; z3^tc^$(89fiUNN%N<&Ea5?3CdkjJDfL|RBP>b<`D?;2|Z6U0h_)L|7zkPwJfP;tm& zS(i2VXeXPy&`*FJCH5sG%TX1_@CtEGcRhi&dTS(6pNbPm3Pg&!HzCm7z`&Ac78r8Z zE=(EulSJrYlKWa-RdEuhXqao4oSZQ_BYSGGm?}mVb;l(fu{YCIa$ihJ#TlGsD$U6v z6VHNLSvJ_$JYc#W8}J+k6%0wV85U^uYrFcI9F#xlfaiA9-;nVA|Wk8}D&aY^86@<`?ajdwFyN6%;a?OaKsh?_ZOdZ$a-s*q<< z5W^K*RgjUcz1DD`g(gAZc5TB2a9We>r+Cr`dqNd6ZP)OI0$oK`7Qmt@T#2O?mU4lm zCx$MilOw6&(J1l)tM~5SyRUoC?(RMNM|bZ(vhUE5o}L)4V_d<63KKU3*891wuXTYI zUxh$VZ6J~px|23;l_W%Px-?++Yp#%>J)HpU9$GnVZcf#sZFa)t25Bxbj8sggfIQ@#lKT46LoE$V=$mSOJ^*f|SXu z;p$5heAZOR{hcSkb>^>zP9my+M#AvH7v6--0cFgCX_$n^wj!;KQ9`*UStQAdrB!5o zmTHdn<_f`@9V;6rhX&(NgRF9|_rC#{#FWMF1b<02Tm?S}B*KFbQUmpamwQj$W`xbe z|0Xt16fwE$^Rz)u90=GvH=)RTPD1}V2bzDq1&8nX@xk{V-*W={&pB3kZI`IQYm7Af z4&nbP-u3XxVS|>P4F12srAS39PvfNyuFKgZ-nxGiqGnKk1yuE70UZLzNqKzU(7ol z%KIgT85LPkHU6Gn%zGzhy6r;S#W`G_#qs-i zeHQ&~xj7iKI3#A#D~n+kH)Snwzu^B3lq55{gx0KKjc>p~Y{6k{XGb*Q8@_A8xA=}% M*$v;bK7K^|KhHOaApigX literal 0 HcmV?d00001 From ce44baaf8f7d1e4a25e3b2e8debebd3e0277c0c5 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 16 Oct 2024 12:30:04 +0200 Subject: [PATCH 45/71] BL-644 #resolve Update the allowedFileOperationExtensions and disallowedFileOperationExtensions to the security block --- .../application/BaseApplicationListener.java | 4 +- .../boxlang/runtime/config/Configuration.java | 95 +++++++------------ .../config/segments/SecurityConfig.java | 29 ++++-- .../runtime/config/util/PropertyHelper.java | 33 ++++++- src/main/resources/config/boxlang.json | 6 +- 5 files changed, 90 insertions(+), 77 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index a4a43dfd7..b038ef928 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -143,8 +143,8 @@ public abstract class BaseApplicationListener { // Stil Considering if they will be core or a module "secureJson", false, "secureJsonPrefix", "", - "allowedFileOperationExtensions", BoxRuntime.getInstance().getConfiguration().allowedFileOperationExtensions, - "disallowedFileOperationExtensions", BoxRuntime.getInstance().getConfiguration().disallowedFileOperationExtensions + "allowedFileOperationExtensions", BoxRuntime.getInstance().getConfiguration().security.allowedFileOperationExtensions, + "disallowedFileOperationExtensions", BoxRuntime.getInstance().getConfiguration().security.disallowedFileOperationExtensions ); /** diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index b76812bc6..003429a9b 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -44,7 +44,6 @@ import ortus.boxlang.runtime.config.segments.ModuleConfig; import ortus.boxlang.runtime.config.segments.SecurityConfig; import ortus.boxlang.runtime.config.util.PlaceholderHelper; -import ortus.boxlang.runtime.dynamic.casters.ArrayCaster; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.casters.KeyCaster; import ortus.boxlang.runtime.dynamic.casters.StringCaster; @@ -56,7 +55,6 @@ import ortus.boxlang.runtime.types.exceptions.BoxIOException; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.util.DateTimeHelper; -import ortus.boxlang.runtime.types.util.ListUtil; import ortus.boxlang.runtime.util.DataNavigator; import ortus.boxlang.runtime.util.DataNavigator.Navigator; import ortus.boxlang.runtime.util.LocalizationUtil; @@ -83,179 +81,173 @@ public class Configuration implements IConfigSegment { * The directory where the generated classes will be placed * The default is the system temp directory + {@code /boxlang} */ - public String classGenerationDirectory = System.getProperty( "java.io.tmpdir" ) + "boxlang"; + public String classGenerationDirectory = System.getProperty( "java.io.tmpdir" ) + "boxlang"; /** * The debug mode flag which turns on all kinds of debugging information * {@code false} by default */ - public Boolean debugMode = false; + public Boolean debugMode = false; /** * The Timezone to use for the runtime; * Uses the Java Timezone format: {@code America/New_York} * Uses the default system timezone if not set */ - public ZoneId timezone = TimeZone.getDefault().toZoneId(); + public ZoneId timezone = TimeZone.getDefault().toZoneId(); /** * The default locale to use for the runtime * Uses the default system locale if not set */ - public Locale locale = Locale.getDefault(); + public Locale locale = Locale.getDefault(); /** * Invoke implicit getters and setters when using the implicit accessor * {@code true} by default */ - public Boolean invokeImplicitAccessor = true; + public Boolean invokeImplicitAccessor = true; /** * Use high precision math for all math operations, else it relies on Double precision * {@code true} by default */ - public Boolean useHighPrecisionMath = true; + public Boolean useHighPrecisionMath = true; /** * The application timeout * {@code 0} means no timeout and is the default */ - public Duration applicationTimeout = Duration.ofDays( 0 ); + public Duration applicationTimeout = Duration.ofDays( 0 ); /** * The request timeout * {@code 0} means no timeout and is the default */ - public Duration requestTimeout = Duration.ofSeconds( 0 );; + public Duration requestTimeout = Duration.ofSeconds( 0 );; /** * The session timeout * {@code 30} minutes by default */ - public Duration sessionTimeout = Duration.ofMinutes( 30 ); + public Duration sessionTimeout = Duration.ofMinutes( 30 ); /** * This flag enables/disables session management in the runtime for all applications by default. * {@code false} by default */ - public Boolean sessionManagement = false; + public Boolean sessionManagement = false; /** * The default session storage cache. This has to be the name of a registered cache * or the keyword "memory" which indicates our internal cache. * {@code memory} is the default */ - public String sessionStorage = "memory"; + public String sessionStorage = "memory"; /** * This determines whether to send CFID and CFTOKEN cookies to the client browser. * {@code true} by default */ - public Boolean setClientCookies = true; + public Boolean setClientCookies = true; /** * Sets CFID and CFTOKEN cookies for a domain (not a host) Required, for applications running on clusters * {@code true} by default */ - public Boolean setDomainCookies = true; + public Boolean setDomainCookies = true; /** * A sorted struct of mappings */ - public IStruct mappings = new Struct( Struct.KEY_LENGTH_LONGEST_FIRST_COMPARATOR ); + public IStruct mappings = new Struct( Struct.KEY_LENGTH_LONGEST_FIRST_COMPARATOR ); /** * An array of directories where modules are located and loaded from. * {@code [ /{boxlang-home}/modules ]} */ - public List modulesDirectory = new ArrayList<>( + public List modulesDirectory = new ArrayList<>( Arrays.asList( BoxRuntime.getInstance().getRuntimeHome().toString() + "/modules" ) ); /** * The default logs directory for the runtime */ - public String logsDirectory = Paths.get( BoxRuntime.getInstance().getRuntimeHome().toString(), "/logs" ).normalize() + public String logsDirectory = Paths.get( BoxRuntime.getInstance().getRuntimeHome().toString(), "/logs" ).normalize() .toString(); /** * An array of directories where custom tags are located and loaded from. * {@code [ /{boxlang-home}/customTags ]} */ - public List customTagsDirectory = new ArrayList<>( + public List customTagsDirectory = new ArrayList<>( Arrays.asList( BoxRuntime.getInstance().getRuntimeHome().toString() + "/customTags" ) ); /** * An array of directories where jar files will be loaded from at runtime. */ - public List javaLibraryPaths = new ArrayList<>( + public List javaLibraryPaths = new ArrayList<>( Arrays.asList( BoxRuntime.getInstance().getRuntimeHome().toString() + "/lib" ) ); /** * Cache registrations */ - public IStruct caches = new Struct(); + public IStruct caches = new Struct(); /** * Default datasource registration */ - public String defaultDatasource = ""; + public String defaultDatasource = ""; /** * Global datasource registrations */ - public IStruct datasources = new Struct(); + public IStruct datasources = new Struct(); /** * Default remote class method return format when executing a method from web runtimes. * The default is JSON */ - public String defaultRemoteMethodReturnFormat = "json"; + public String defaultRemoteMethodReturnFormat = "json"; /** * Default cache registration */ - public CacheConfig defaultCache = new CacheConfig(); + public CacheConfig defaultCache = new CacheConfig(); /** * The modules configuration */ - public IStruct modules = new Struct(); + public IStruct modules = new Struct(); /** * The last config struct loaded */ - public IStruct originalConfig = new Struct(); + public IStruct originalConfig = new Struct(); /** * A collection of all the registered global executors */ - public IStruct executors = new Struct(); - - /** - * File extensions which are disallowed for file operations. The allowed array overrides any items in the disallow list. - */ - public List allowedFileOperationExtensions = new ArrayList<>(); - public List disallowedFileOperationExtensions = new ArrayList<>(); + public IStruct executors = new Struct(); /** * Valid BoxLang class extensions */ - public Set validClassExtensions = new HashSet<>(); + public Set validClassExtensions = new HashSet<>(); /** * Valid BoxLang template extensions */ - public Set validTemplateExtensions = new HashSet<>(); + public Set validTemplateExtensions = new HashSet<>(); /** * Experimental Features */ - public IStruct experimental = new Struct(); + public IStruct experimental = new Struct(); /** * The security configuration */ - public SecurityConfig security = new SecurityConfig(); + public SecurityConfig security = new SecurityConfig(); /** * -------------------------------------------------------------------------- @@ -266,7 +258,7 @@ public class Configuration implements IConfigSegment { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( Configuration.class ); + private static final Logger logger = LoggerFactory.getLogger( Configuration.class ); /** * -------------------------------------------------------------------------- @@ -544,28 +536,6 @@ public Configuration process( IStruct config ) { } } - // File operation safety keys - if ( config.containsKey( Key.allowedFileOperationExtensions ) ) { - if ( config.get( Key.allowedFileOperationExtensions ) instanceof String ) { - config.put( Key.allowedFileOperationExtensions, - ListUtil.asList( config.getAsString( Key.allowedFileOperationExtensions ), ListUtil.DEFAULT_DELIMITER ) ); - } - - // For some reason we have to re-cast this through a stream. Attempting to cast it directly throws a ClassCastException - this.allowedFileOperationExtensions = ArrayCaster.cast( config.get( Key.allowedFileOperationExtensions ) ).stream().map( StringCaster::cast ) - .toList(); - } - - if ( config.containsKey( Key.disallowedFileOperationExtensions ) ) { - if ( config.get( Key.disallowedFileOperationExtensions ) instanceof String ) { - config.put( Key.disallowedFileOperationExtensions, - ListUtil.asList( config.getAsString( Key.disallowedFileOperationExtensions ), ListUtil.DEFAULT_DELIMITER ) ); - } - // For some reason we have to re-cast this through a stream. Attempting to cast it directly throws a ClassCastException - this.disallowedFileOperationExtensions = ArrayCaster.cast( config.get( Key.disallowedFileOperationExtensions ) ).stream().map( StringCaster::cast ) - .toList(); - } - return this; } @@ -804,7 +774,7 @@ public IStruct asStruct() { this.modules.entrySet().forEach( entry -> modulesCopy.put( entry.getKey(), ( ( ModuleConfig ) entry.getValue() ).asStruct() ) ); return Struct.of( - Key.allowedFileOperationExtensions, Array.fromList( this.allowedFileOperationExtensions ), + Key.applicationTimeout, this.applicationTimeout, Key.caches, cachesCopy, Key.classGenerationDirectory, this.classGenerationDirectory, @@ -814,7 +784,6 @@ public IStruct asStruct() { Key.defaultCache, this.defaultCache.toStruct(), Key.defaultDatasource, this.defaultDatasource, Key.defaultRemoteMethodReturnFormat, this.defaultRemoteMethodReturnFormat, - Key.disallowedFileOperationExtensions, Array.fromList( this.disallowedFileOperationExtensions ), Key.executors, executorsCopy, Key.experimental, Struct.fromMap( this.experimental ), Key.invokeImplicitAccessor, this.invokeImplicitAccessor, diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java index 3375ca683..e1b178cf8 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java @@ -17,7 +17,9 @@ */ package ortus.boxlang.runtime.config.segments; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -27,6 +29,7 @@ import ortus.boxlang.runtime.config.util.PropertyHelper; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; @@ -40,19 +43,25 @@ public class SecurityConfig implements IConfigSegment { * These are a list of regular expressions that are used to match against the import statements in the code * Ex: "disallowedImports": ["java\\.lang\\.(ProcessBuilder|Reflect", "java\\.io\\.(File|FileWriter)"] */ - public Set disallowedImports = new HashSet<>(); + public Set disallowedImports = new HashSet<>(); /** * Disallowed BIFs in the runtime * Ex: "disallowedBifs": ["createObject", "systemExecute"] */ - public Set disallowedBIFs = new HashSet<>(); + public Set disallowedBIFs = new HashSet<>(); /** * Disallowed Components in the runtime * Ex: "disallowedComponents": [ "execute", "http" ] */ - public Set disallowedComponents = new HashSet<>(); + public Set disallowedComponents = new HashSet<>(); + + /** + * File extensions which are disallowed for file operations. The allowed array overrides any items in the disallow list. + */ + public List allowedFileOperationExtensions = new ArrayList<>(); + public List disallowedFileOperationExtensions = new ArrayList<>(); /** * -------------------------------------------------------------------------- @@ -63,14 +72,14 @@ public class SecurityConfig implements IConfigSegment { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( SecurityConfig.class ); + private static final Logger logger = LoggerFactory.getLogger( SecurityConfig.class ); /** * Maps of allowed BIFs so lookups get faster as we go */ - public Map allowedBIFsLookup = new ConcurrentHashMap<>(); - public Map allowedComponentsLookup = new ConcurrentHashMap<>(); - public Map allowedImportsLookup = new ConcurrentHashMap<>(); + public Map allowedBIFsLookup = new ConcurrentHashMap<>(); + public Map allowedComponentsLookup = new ConcurrentHashMap<>(); + public Map allowedImportsLookup = new ConcurrentHashMap<>(); /** * -------------------------------------------------------------------------- @@ -158,6 +167,8 @@ public IConfigSegment process( IStruct config ) { PropertyHelper.processListToSet( config, Key.disallowedImports, this.disallowedImports ); PropertyHelper.processListToSet( config, Key.disallowedBIFs, this.disallowedBIFs ); PropertyHelper.processListToSet( config, Key.disallowedComponents, this.disallowedComponents ); + PropertyHelper.processStringOrArrayToList( config, Key.allowedFileOperationExtensions, this.allowedFileOperationExtensions ); + PropertyHelper.processStringOrArrayToList( config, Key.disallowedFileOperationExtensions, this.disallowedFileOperationExtensions ); return this; } @@ -167,9 +178,11 @@ public IConfigSegment process( IStruct config ) { @Override public IStruct asStruct() { return Struct.of( + Key.allowedFileOperationExtensions, Array.fromList( this.allowedFileOperationExtensions ), Key.disallowedImports, this.disallowedImports, Key.disallowedBIFs, this.disallowedBIFs, - Key.disallowedComponents, this.disallowedComponents + Key.disallowedComponents, this.disallowedComponents, + Key.disallowedFileOperationExtensions, Array.fromList( this.disallowedFileOperationExtensions ) ); } diff --git a/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java b/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java index a7734f467..dd951b218 100644 --- a/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java +++ b/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java @@ -24,8 +24,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ortus.boxlang.runtime.dynamic.casters.ArrayCaster; +import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.util.ListUtil; /** * Helps convert from JSON to native Java/BoxLang Types @@ -38,7 +41,7 @@ public class PropertyHelper { private static final Logger logger = LoggerFactory.getLogger( PropertyHelper.class ); /** - * Process the target key + * This processes a JSON array list to a HashSet * * @param config The configuration object * @param key The target key to look and process @@ -55,4 +58,32 @@ public static void processListToSet( IStruct config, Key key, Set target } } + /** + * This processes: + * - A string to a list (comma separated or a single string) + * - A JSON array to a list + * + * @param config The configuration object + * @param key The target key to look and process + * @param target The target list to populate with the values + */ + public static void processStringOrArrayToList( IStruct config, Key key, List target ) { + if ( config.containsKey( key ) ) { + // If it's a string, convert it to an array + if ( config.get( key ) instanceof String castedStringList ) { + config.put( + key, + ListUtil.asList( PlaceholderHelper.resolve( castedStringList ), ListUtil.DEFAULT_DELIMITER ) + ); + } + + // For some reason we have to re-cast this through a stream. Attempting to cast it directly throws a ClassCastException + ArrayCaster.cast( config.get( key ) ) + .stream() + // Add each item to the incoming target + .map( StringCaster::cast ) + .forEach( target::add ); + } + } + } diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 932598757..bca19fe77 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -107,8 +107,6 @@ // "database": "test" // } }, - // An explicit whitelist of file extensions that are allowed to be uploaded - overrides any values in the disallowedWriteExtensions - "allowedFileOperationExtensions": [], // The list of file extensions that are not allowed to be uploaded. Also enforced by file relocation operations ( e.g. copy/move ) "disallowedFileOperationExtensions": [ "bat", @@ -224,7 +222,9 @@ "disallowedBifs": [], // A list of Component names that will be disallowed from execution // Ex: "disallowedComponents": [ "execute", "http" ] - "disallowedComponents": [] + "disallowedComponents": [], + // An explicit whitelist of file extensions that are allowed to be uploaded - overrides any values in the disallowedWriteExtensions + "allowedFileOperationExtensions": [] }, /** * The BoxLang module settings From fe9d1214dbd85f40879aadff79dff85413273c8d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 16 Oct 2024 12:35:22 +0200 Subject: [PATCH 46/71] BL-327 #resolve Remove CFToken and move to compat module --- src/main/java/ortus/boxlang/runtime/application/Session.java | 1 - src/main/java/ortus/boxlang/runtime/config/Configuration.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Session.java b/src/main/java/ortus/boxlang/runtime/application/Session.java index 880346835..a40404d67 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Session.java +++ b/src/main/java/ortus/boxlang/runtime/application/Session.java @@ -104,7 +104,6 @@ public Session( Key ID, Application application ) { // Move these to the COMPAT module this.sessionScope.put( Key.urlToken, String.format( URL_TOKEN_FORMAT, bxid ) ); this.sessionScope.put( Key.cfid, ID.getName() ); - this.sessionScope.put( Key.cftoken, 0 ); // Announce it's creation BoxRuntime.getInstance() diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index 003429a9b..958fad498 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -146,13 +146,13 @@ public class Configuration implements IConfigSegment { public String sessionStorage = "memory"; /** - * This determines whether to send CFID and CFTOKEN cookies to the client browser. + * This determines whether to send jSessionID cookies to the client browser. * {@code true} by default */ public Boolean setClientCookies = true; /** - * Sets CFID and CFTOKEN cookies for a domain (not a host) Required, for applications running on clusters + * Sets jSessionID cookies for a domain (not a host) Required, for applications running on clusters * {@code true} by default */ public Boolean setDomainCookies = true; From 8571f75e017f8e3c9d0a07595c31c18db1595d97 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 16 Oct 2024 12:42:24 +0200 Subject: [PATCH 47/71] BL-328 #resolve Move CFID to compat module --- .../boxlang/runtime/application/Session.java | 20 ++++--------------- .../global/system/SessionInvalidateTest.java | 2 +- .../bifs/global/system/SessionRotateTest.java | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Session.java b/src/main/java/ortus/boxlang/runtime/application/Session.java index a40404d67..651e33750 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Session.java +++ b/src/main/java/ortus/boxlang/runtime/application/Session.java @@ -41,13 +41,7 @@ public class Session implements Serializable { /** * The concatenator for session IDs */ - public static final String ID_CONCATENATOR = "_"; - - /** - * The URL token format - * MOVE TO COMPAT MODULE - */ - public static final String URL_TOKEN_FORMAT = "CFID=%s"; + public static final String ID_CONCATENATOR = "_"; /** * -------------------------------------------------------------------------- @@ -68,12 +62,12 @@ public class Session implements Serializable { /** * Flag for when session has been started */ - private final AtomicBoolean isNew = new AtomicBoolean( true ); + private final AtomicBoolean isNew = new AtomicBoolean( true ); /** * The application name linked to */ - private Key applicationName = null; + private Key applicationName = null; /** * -------------------------------------------------------------------------- @@ -91,9 +85,7 @@ public Session( Key ID, Application application ) { this.ID = ID; this.applicationName = application.getName(); this.sessionScope = new SessionScope(); - - DateTime timeNow = new DateTime(); - String bxid = this.applicationName + ID_CONCATENATOR + ID; + DateTime timeNow = new DateTime(); // Initialize the session scope this.sessionScope.put( Key.jsessionID, ID.getName() ); @@ -101,10 +93,6 @@ public Session( Key ID, Application application ) { this.sessionScope.put( Key.timeCreated, timeNow ); this.sessionScope.put( Key.lastVisit, timeNow ); - // Move these to the COMPAT module - this.sessionScope.put( Key.urlToken, String.format( URL_TOKEN_FORMAT, bxid ) ); - this.sessionScope.put( Key.cfid, ID.getName() ); - // Announce it's creation BoxRuntime.getInstance() .getInterceptorService() diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java index 126dc0ff1..e1a1f27e7 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java @@ -75,7 +75,7 @@ public void testBif() { context ); IStruct initialSession = variables.getAsStruct( Key.of( "initialSession" ) ); assertFalse( variables.getAsStruct( result ).containsKey( Key.of( "foo" ) ) ); - assertNotEquals( initialSession.getAsString( Key.of( "cfid" ) ), variables.getAsStruct( result ).getAsString( Key.of( "cfid" ) ) ); + assertNotEquals( initialSession.getAsString( Key.of( "jsessionID" ) ), variables.getAsStruct( result ).getAsString( Key.of( "jsessionID" ) ) ); assertNotEquals( initialSession.getAsDateTime( Key.of( "timeCreated" ) ), variables.getAsStruct( result ).getAsDateTime( Key.of( "timeCreated" ) ) ); assertNotEquals( initialSession.getAsString( Key.of( "sessionid" ) ), variables.getAsStruct( result ).getAsString( Key.of( "sessionid" ) ) ); } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionRotateTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionRotateTest.java index f053972c6..606077c41 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionRotateTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionRotateTest.java @@ -80,7 +80,7 @@ public void testBif() { IStruct initialSession = variables.getAsStruct( Key.of( "initialSession" ) ); assertTrue( variables.getAsStruct( result ).containsKey( Key.of( "foo" ) ) ); - assertNotEquals( initialSession.getAsString( Key.of( "cfid" ) ), variables.getAsStruct( result ).getAsString( Key.of( "cfid" ) ) ); + assertNotEquals( initialSession.getAsString( Key.of( "jsessionID" ) ), variables.getAsStruct( result ).getAsString( Key.of( "jsessionID" ) ) ); assertNotEquals( initialSession.getAsDateTime( Key.of( "timeCreated" ) ), variables.getAsStruct( result ).getAsDateTime( Key.of( "timeCreated" ) ) ); assertNotEquals( initialSession.getAsString( Key.of( "sessionid" ) ), variables.getAsStruct( result ).getAsString( Key.of( "sessionid" ) ) ); } From c794c71403cd08ff28f415338e9387d9b4e43a93 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 16 Oct 2024 15:48:03 +0200 Subject: [PATCH 48/71] removal of left over outputs --- .../boxlang/runtime/bifs/global/jdbc/PreserveSingleQuotes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/PreserveSingleQuotes.java b/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/PreserveSingleQuotes.java index 1e01a3575..5dda49cd9 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/PreserveSingleQuotes.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/PreserveSingleQuotes.java @@ -46,7 +46,7 @@ public PreserveSingleQuotes() { public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { // This BIF itself doesn't do anything. It's largely a placeholder in the AST for the QueryPreserveSingleQuotesVisitor to look for. // The Visitor actually does the work of escaping or preserving single quotes. - System.out.println( "PreserveSingleQuotes._invoke " + arguments.get( Key.variable ) ); + // System.out.println( "PreserveSingleQuotes._invoke " + arguments.get( Key.variable ) ); return arguments.get( Key.variable ); } From c0d996378ddf786548c96422715583aa49fc2b52 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 16 Oct 2024 17:43:02 +0200 Subject: [PATCH 49/71] BL-657 #resolve JavaResolver module targeted resolution --- .../runtime/loader/ImportDefinition.java | 16 ++- .../runtime/loader/util/ClassDiscovery.java | 3 +- src/test/bx/index.bxm | 3 + .../resolvers/AbstractResolverTest.java | 61 +++++++++ .../loader/resolvers/BoxResolverTest.java | 122 ++++++++++++------ .../loader/resolvers/JavaResolverTest.java | 35 +---- 6 files changed, 168 insertions(+), 72 deletions(-) create mode 100644 src/test/bx/index.bxm create mode 100644 src/test/java/ortus/boxlang/runtime/loader/resolvers/AbstractResolverTest.java diff --git a/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java b/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java index 8e28d8388..7fe0cf35b 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java +++ b/src/main/java/ortus/boxlang/runtime/loader/ImportDefinition.java @@ -64,6 +64,15 @@ public Boolean isMultiImport() { return className.endsWith( ".*" ); } + /** + * Is this import a module import? + * + * @return True if it is a module import, false otherwise + */ + public Boolean isModuleImport() { + return moduleName != null; + } + /** * Returns the package name of the import definition * @@ -75,7 +84,7 @@ public String getPackageName() { /** * Returns the fully qualified class name of the import definition - * considering if it is a wildcard import or not + * considering if it is a wildcard import or not, and if it's from a targeted module or not. * * @param targetClass The class name in code that needed qualification * @@ -84,9 +93,10 @@ public String getPackageName() { public String getFullyQualifiedClass( String targetClass ) { if ( isMultiImport() ) { return String.format( "%s.%s", className.substring( 0, className.length() - 2 ), targetClass ); - } else { - return className; + } else if ( isModuleImport() ) { + return String.format( "%s@%s", className, moduleName ); } + return className; } /** diff --git a/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java b/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java index 228409f35..7ce5592ca 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java +++ b/src/main/java/ortus/boxlang/runtime/loader/util/ClassDiscovery.java @@ -288,8 +288,7 @@ public static String[] findClassNames( File directory, String packageName, Boole } return Arrays.stream( aClassNames ) - // .parallel() - .peek( file -> System.out.println( "File: " + file ) ) + .parallel() .flatMap( file -> { if ( file.isDirectory() ) { if ( recursive ) { diff --git a/src/test/bx/index.bxm b/src/test/bx/index.bxm new file mode 100644 index 000000000..df3a3dfe7 --- /dev/null +++ b/src/test/bx/index.bxm @@ -0,0 +1,3 @@ + + This is a test template + diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/AbstractResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/AbstractResolverTest.java new file mode 100644 index 000000000..53250ab0e --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/AbstractResolverTest.java @@ -0,0 +1,61 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ortus.boxlang.runtime.loader.resolvers; + +import java.nio.file.Paths; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.modules.ModuleRecord; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.services.ModuleService; + +public abstract class AbstractResolverTest { + + public static BoxRuntime runtime; + public static IBoxContext context; + + @BeforeAll + public static void beforeAll() { + runtime = BoxRuntime.getInstance( true ); + } + + @BeforeEach + public void beforeEach() { + context = new ScriptingRequestBoxContext( runtime.getRuntimeContext() ); + } + + public void loadTestModule() { + Key moduleName = new Key( "test" ); + String physicalPath = Paths.get( "./modules/test" ).toAbsolutePath().toString(); + ModuleRecord moduleRecord = new ModuleRecord( physicalPath ); + ModuleService moduleService = runtime.getModuleService(); + + moduleRecord + .loadDescriptor( context ) + .register( context ) + .activate( context ); + + moduleService.getRegistry().put( moduleName, moduleRecord ); + } + +} diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java index 0bf37382d..a43a64e7c 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java @@ -20,87 +20,65 @@ import static com.google.common.truth.Truth.assertThat; -import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.runnables.IBoxRunnable; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.DateTime; +import ortus.boxlang.runtime.util.ResolvedFilePath; -public class BoxResolverTest { +public class BoxResolverTest extends AbstractResolverTest { - static BoxRuntime runtime; - static IBoxContext context; - static BoxResolver boxResolver; + public static BoxResolver boxResolver; @BeforeAll public static void setUp() { - runtime = BoxRuntime.getInstance( true ); - context = new ScriptingRequestBoxContext( runtime.getRuntimeContext() ); - boxResolver = runtime.getClassLocator().getBoxResolver(); - - // Create a mapping to the `resources` directory + boxResolver = runtime.getClassLocator().getBoxResolver(); + // Create a mapping to the `resources` directory as `/tests` Path resourcesDirectory = Paths.get( "src/test/resources" ).toAbsolutePath(); runtime.getConfiguration().registerMapping( "/tests", resourcesDirectory.toString() ); // System.out.println( "mappings: " + Arrays.toString( runtime.getConfiguration().getRegisteredMappings() ) ); } - @AfterAll - public static void teardown() { - } - - @DisplayName( "It can find be created" ) + @DisplayName( "It can be created correctly with the rigth prefix" ) @Test void testItCanBeCreated() { assertThat( boxResolver.getName() ).isEqualTo( "BoxResolver" ); assertThat( boxResolver.getPrefix() ).isEqualTo( "bx" ); } - @DisplayName( "It can find classes from modules" ) - @Test - @Disabled - void testFindFromModules() { - String className = "apppath.models.User"; // Example class name - assertThat( boxResolver.findFromModules( new ScriptingRequestBoxContext(), className, new ArrayList<>() ).isPresent() ).isFalse(); - } - @DisplayName( "It can find classes from local disk using a mapping" ) @Test - void testFindFromLocal() throws URISyntaxException { + void testFindFromLocal() { // You can find this in src/test/resources/tests/components/User.cfc String testComponent = "tests.components.User"; - - // System.out.println( "mappings: " + Arrays.toString( runtime.getConfiguration().getRegisteredMappings() ) ); - Optional classLocation = boxResolver.findFromLocal( context, testComponent, new ArrayList<>() ); assertThat( classLocation.isPresent() ).isTrue(); assertThat( classLocation.get().name() ).isEqualTo( "User" ); assertThat( classLocation.get().packageName() ).isEqualTo( "tests.components" ); - IClassRunnable cfc = ( IClassRunnable ) DynamicObject.of( classLocation.get().clazz() ) + IClassRunnable targetClass = ( IClassRunnable ) DynamicObject.of( classLocation.get().clazz() ) .invokeConstructor( context ) .getTargetInstance(); - assertThat( cfc ).isNotNull(); - assertThat( cfc.getThisScope().getAsString( Key.of( "name" ) ) ).isEqualTo( "test" ); - assertThat( cfc.getThisScope().get( Key.of( "created" ) ) ).isInstanceOf( DateTime.class ); + assertThat( targetClass ).isNotNull(); + assertThat( targetClass.getThisScope().getAsString( Key.of( "name" ) ) ).isEqualTo( "test" ); + assertThat( targetClass.getThisScope().get( Key.of( "created" ) ) ).isInstanceOf( DateTime.class ); } @DisplayName( "It can resolve classes using direct class names" ) @@ -113,13 +91,69 @@ void testResolve() { // Now a class that exists className = "src.test.bx.Person"; assertThat( boxResolver.resolve( context, className ).isPresent() ).isTrue(); + assertThat( boxResolver.resolve( context, className ).get().path() ).contains( "Person" ); } - @DisplayName( "It can resolve classes using imports" ) + @DisplayName( "It can resolve classes using relative paths from a template executing it" ) + @Test + void testResolveRelativeFromTemplate() { + // Push a mock template + String mockPath = Path.of( "src/test/bx/index.bxm" ).toAbsolutePath().toString(); + context.pushTemplate( new IBoxRunnable() { + + @Override + public List getImports() { + return List.of(); + } + + @Override + public long getRunnableCompileVersion() { + return 0L; + } + + @Override + public LocalDateTime getRunnableCompiledOn() { + return null; + } + + @Override + public Object getRunnableAST() { + return null; + } + + @Override + public ResolvedFilePath getRunnablePath() { + return ResolvedFilePath.of( mockPath ); + } + + @Override + public BoxSourceType getSourceType() { + return BoxSourceType.BOXTEMPLATE; + } + + } ); + + try { + // Relative to the template above + String target = "Person"; + Optional classLocation = boxResolver.findFromLocal( context, target, new ArrayList<>() ); + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().name() ).isEqualTo( "Person" ); + + // Relative to the template above but embedded + target = "models.TestClass"; + classLocation = boxResolver.findFromLocal( context, target, new ArrayList<>() ); + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().name() ).isEqualTo( "TestClass" ); + } finally { + context.popTemplate(); + } + } + + @DisplayName( "It can resolve classes using direct imports" ) @Test void testResolveWithImports() { String className = "TestClass"; - List imports = Arrays.asList( ImportDefinition.parse( "src.test.bx.models.Validation" ), ImportDefinition.parse( "src.test.bx.models.TestClass" ) @@ -129,10 +163,20 @@ void testResolveWithImports() { assertThat( classLocation.get().path() ).contains( "TestClass" ); } + @DisplayName( "It can find classes from modules" ) + @Test + void testFindFromModules() { + loadTestModule(); + String className = "models.Hello@test"; + Optional classLocation = boxResolver.resolve( context, className, new ArrayList<>() ); + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().name() ).isEqualTo( "Hello" ); + } + @DisplayName( "It can resolve classes using imports for modules" ) - @Disabled @Test void testResolveWithImportsForModules() { + loadTestModule(); String className = "Hello"; List imports = Arrays.asList( ImportDefinition.parse( "models.Hello@test" ), diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java index a7d11409a..5c6e05b48 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/JavaResolverTest.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -37,33 +36,27 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; import ortus.boxlang.runtime.loader.DynamicClassLoader; import ortus.boxlang.runtime.loader.ImportDefinition; -import ortus.boxlang.runtime.modules.ModuleRecord; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.services.ModuleService; import ortus.boxlang.runtime.types.IStruct; -public class JavaResolverTest { +public class JavaResolverTest extends AbstractResolverTest { - static BoxRuntime runtime; - static JavaResolver javaResolver; - static IBoxContext context; + public static JavaResolver javaResolver; @BeforeAll - public static void setUp() { - runtime = BoxRuntime.getInstance( true ); - javaResolver = runtime.getClassLocator().getJavaResolver(); + public static void beforeAll() { + javaResolver = runtime.getClassLocator().getJavaResolver(); } @BeforeEach - public void setup() { - context = new ScriptingRequestBoxContext( runtime.getRuntimeContext() ); + @Override + public void beforeEach() { + super.beforeEach(); javaResolver.clearJdkImportCache(); } @@ -258,18 +251,4 @@ void testItCanLoadLibsFromHomeLibs() throws IOException { assertThat( location.get().clazz().getName() ).isEqualTo( targetClass ); } - private void loadTestModule() { - Key moduleName = new Key( "test" ); - String physicalPath = Paths.get( "./modules/test" ).toAbsolutePath().toString(); - ModuleRecord moduleRecord = new ModuleRecord( physicalPath ); - ModuleService moduleService = runtime.getModuleService(); - - moduleRecord - .loadDescriptor( context ) - .register( context ) - .activate( context ); - - moduleService.getRegistry().put( moduleName, moduleRecord ); - } - } From 5dfcff6fb8e94377abfc008cbb9c45cc95c31928 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 16 Oct 2024 13:14:27 -0500 Subject: [PATCH 50/71] BL-655 --- .../ortus/boxlang/runtime/loader/DiskClassLoader.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java index 1a450a244..acb5a8c28 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java @@ -76,12 +76,12 @@ public DiskClassLoader( URL[] urls, ClassLoader parent, Path diskStore, IBoxpile /** * Find class on disk, if not found, delegate to parent + * TODO: If we move to sharing classloaders across files with trusted cache, make the locking more granular instead of synchronizing the entire method * * @param name class name */ @Override - protected Class findClass( String name ) throws ClassNotFoundException { - + protected synchronized Class findClass( String name ) throws ClassNotFoundException { Path diskPath = generateDiskPath( name ); // JIT compile String baseName = IBoxpiler.getBaseFQN( name ); @@ -107,7 +107,6 @@ protected Class findClass( String name ) throws ClassNotFoundException { } catch ( IOException e ) { throw new ClassNotFoundException( "Unable to read class file from disk", e ); } - return defineClass( name, bytes, 0, bytes.length ); } @@ -133,8 +132,8 @@ private boolean needsCompile( ClassInfo classInfo, Path diskPath, String name, S return false; } // There is no class file cached on disk - if ( !hasClass( diskPath ) ) { - return true; + if ( hasClass( diskPath ) ) { + return false; } // If the class file is older than the source file From 0554dd16aa376c594f6e7b7a41e1df7c88e47360 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 16 Oct 2024 13:52:59 -0500 Subject: [PATCH 51/71] BL-657 --- .../ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java index a43a64e7c..09a1c2c1e 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java @@ -184,7 +184,7 @@ void testResolveWithImportsForModules() { ); Optional classLocation = boxResolver.resolve( context, className, imports ); assertThat( classLocation.isPresent() ).isTrue(); - assertThat( classLocation.get().path() ).contains( "test/models/Hello" ); + assertThat( classLocation.get().path().replace( "\\", "/" ) ).contains( "test/models/Hello" ); } } From 560cc84b266d1c84d0dd99fcd89417210addb639 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 16 Oct 2024 13:54:12 -0500 Subject: [PATCH 52/71] BL-655 --- .../java/ortus/boxlang/compiler/IBoxpiler.java | 6 ++++-- .../boxlang/runtime/loader/DiskClassLoader.java | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/IBoxpiler.java b/src/main/java/ortus/boxlang/compiler/IBoxpiler.java index d5d3afde3..4cefd410c 100644 --- a/src/main/java/ortus/boxlang/compiler/IBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/IBoxpiler.java @@ -21,11 +21,13 @@ public interface IBoxpiler { - static final Set RESERVED_WORDS = new HashSet<>( Arrays.asList( "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", + static final Set RESERVED_WORDS = new HashSet<>( Arrays.asList( "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while" ) ); + static final Pattern FQNBasePattern = Pattern.compile( "(.*?)(\\$Closure_.*|\\$Func_.*|\\$Lambda_.*)$" ); + /** * Generate an MD5 hash. * TODO: Move to util class @@ -80,7 +82,7 @@ static String MD5( String md5 ) { static String getBaseFQN( String FQN ) { // If fqn ends with $Cloure_xxx or $Func_xxx, $Lambda_xxx, then we need to strip that off to get the original FQN - Matcher m = Pattern.compile( "(.*?)(\\$Closure_.*|\\$Func_.*|\\$Lambda_.*)$" ).matcher( FQN ); + Matcher m = FQNBasePattern.matcher( FQN ); if ( m.find() ) { FQN = m.group( 1 ); } diff --git a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java index acb5a8c28..7dfcd5c19 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java @@ -131,17 +131,17 @@ private boolean needsCompile( ClassInfo classInfo, Path diskPath, String name, S if ( !name.equals( baseName ) ) { return false; } - // There is no class file cached on disk + // There is a class file cached on disk if ( hasClass( diskPath ) ) { + // If the class file is older than the source file + if ( classInfo != null && classInfo.lastModified() > diskPath.toFile().lastModified() ) { + return true; + } return false; } - // If the class file is older than the source file - if ( classInfo != null && classInfo.lastModified() > diskPath.toFile().lastModified() ) { - return true; - } - // The class file exists on disk and is up to date - return false; + // There is no class file cached on disk + return true; } /** From d8ca1b9e2483da92f7a7093b1d1c90b14bee1dad Mon Sep 17 00:00:00 2001 From: jclausen Date: Wed, 16 Oct 2024 15:34:12 -0400 Subject: [PATCH 53/71] fix disallowed extensions location in boxlang.json --- src/main/resources/config/boxlang.json | 148 ++++++++++++------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index bca19fe77..21c2b9b84 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -107,79 +107,6 @@ // "database": "test" // } }, - // The list of file extensions that are not allowed to be uploaded. Also enforced by file relocation operations ( e.g. copy/move ) - "disallowedFileOperationExtensions": [ - "bat", - "exe", - "cmd", - "cfm", - "cfc", - "cfs", - "bx", - "bxm", - "bxs", - "sh", - "php", - "pl", - "cgi", - "386", - "dll", - "com", - "torrent", - "js", - "app", - "jar", - "pif", - "vb", - "vbscript", - "wsf", - "asp", - "cer", - "csr", - "jsp", - "drv", - "sys", - "ade", - "adp", - "bas", - "chm", - "cpl", - "crt", - "csh", - "fxp", - "hlp", - "hta", - "inf", - "ins", - "isp", - "jse", - "htaccess", - "htpasswd", - "ksh", - "lnk", - "mdb", - "mde", - "mdt", - "mdw", - "msc", - "msi", - "msp", - "mst", - "ops", - "pcd", - "prg", - "reg", - "scr", - "sct", - "shb", - "shs", - "url", - "vbe", - "vbs", - "wsc", - "wsf", - "wsh" - ], // The default return format for class invocations via web runtimes "defaultRemoteMethodReturnFormat": "json", // The configuration for the BoxLang `default` cache. If empty, we use the defaults @@ -224,7 +151,80 @@ // Ex: "disallowedComponents": [ "execute", "http" ] "disallowedComponents": [], // An explicit whitelist of file extensions that are allowed to be uploaded - overrides any values in the disallowedWriteExtensions - "allowedFileOperationExtensions": [] + "allowedFileOperationExtensions": [], + // The list of file extensions that are not allowed to be uploaded. Also enforced by file relocation operations ( e.g. copy/move ) + "disallowedFileOperationExtensions": [ + "bat", + "exe", + "cmd", + "cfm", + "cfc", + "cfs", + "bx", + "bxm", + "bxs", + "sh", + "php", + "pl", + "cgi", + "386", + "dll", + "com", + "torrent", + "js", + "app", + "jar", + "pif", + "vb", + "vbscript", + "wsf", + "asp", + "cer", + "csr", + "jsp", + "drv", + "sys", + "ade", + "adp", + "bas", + "chm", + "cpl", + "crt", + "csh", + "fxp", + "hlp", + "hta", + "inf", + "ins", + "isp", + "jse", + "htaccess", + "htpasswd", + "ksh", + "lnk", + "mdb", + "mde", + "mdt", + "mdw", + "msc", + "msi", + "msp", + "mst", + "ops", + "pcd", + "prg", + "reg", + "scr", + "sct", + "shb", + "shs", + "url", + "vbe", + "vbs", + "wsc", + "wsf", + "wsh" + ] }, /** * The BoxLang module settings From b4ade80173d4c34f419ec000d91f6744796189cf Mon Sep 17 00:00:00 2001 From: jclausen Date: Wed, 16 Oct 2024 16:13:06 -0400 Subject: [PATCH 54/71] add security config processing and test coverage for defaults being loaded --- .../runtime/bifs/global/io/FileCopy.java | 10 ++++++- .../runtime/bifs/global/io/FileMove.java | 5 ++++ .../boxlang/runtime/config/Configuration.java | 6 ++++ .../config/segments/SecurityConfig.java | 29 +++++++++++++++++++ .../runtime/bifs/global/io/FileCopyTest.java | 15 ++++++++++ .../runtime/bifs/global/io/FileMoveTest.java | 20 +++++++++++++ .../config/segments/SecurityConfigTest.java | 9 ++++++ 7 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileCopy.java b/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileCopy.java index 063d92882..b1f382223 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileCopy.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileCopy.java @@ -25,8 +25,9 @@ import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; -import ortus.boxlang.runtime.util.FileSystemUtil; import ortus.boxlang.runtime.types.exceptions.BoxIOException; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.util.FileSystemUtil; @BoxBIF @@ -59,8 +60,15 @@ public FileCopy() { public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { arguments.put( Key.source, FileSystemUtil.expandPath( context, arguments.getAsString( Key.source ) ).absolutePath().toString() ); arguments.put( Key.destination, FileSystemUtil.expandPath( context, arguments.getAsString( Key.destination ) ).absolutePath().toString() ); + + // Make sure there is no attempt to move a file in to disallowed ( e.g. executable ) type + if ( !runtime.getConfiguration().security.isFileOperationAllowed( arguments.getAsString( Key.destination ) ) ) { + throw new BoxRuntimeException( "The destination path contains an extension disallowed by the runtime security settings." ); + } + Path sourcePath = Path.of( arguments.getAsString( Key.source ) ); Path destinationPath = Path.of( arguments.getAsString( Key.destination ) ); + Path destinationDirectory = destinationPath.getParent(); Boolean createPaths = arguments.getAsBoolean( Key.createPath ); diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileMove.java b/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileMove.java index e02243464..fa46ca8a3 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileMove.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/io/FileMove.java @@ -21,6 +21,7 @@ import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.util.FileSystemUtil; @BoxBIF @@ -51,6 +52,10 @@ public FileMove() { public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String sourcePath = FileSystemUtil.expandPath( context, arguments.getAsString( Key.source ) ).absolutePath().toString(); String destinationPath = FileSystemUtil.expandPath( context, arguments.getAsString( Key.destination ) ).absolutePath().toString(); + // Make sure there is no attempt to move a file in to disallowed ( e.g. executable ) type + if ( !runtime.getConfiguration().security.isFileOperationAllowed( destinationPath ) ) { + throw new BoxRuntimeException( "The destination path contains an extension disallowed by the runtime security settings." ); + } FileSystemUtil.move( sourcePath, destinationPath ); return null; } diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index 958fad498..66039a7da 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -47,6 +47,7 @@ import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.casters.KeyCaster; import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.loader.DynamicClassLoader; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Array; @@ -536,6 +537,11 @@ public Configuration process( IStruct config ) { } } + // Process our security configuration + if ( config.containsKey( Key.security ) ) { + security.process( StructCaster.cast( config.get( Key.security ) ) ); + } + return this; } diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java index e1b178cf8..4e054cc2b 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/SecurityConfig.java @@ -27,6 +27,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.io.Files; + import ortus.boxlang.runtime.config.util.PropertyHelper; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Array; @@ -155,6 +157,33 @@ public boolean isClassAllowed( String name ) { return true; } + /** + * Determines whether a file operation is allowed or not based on the file extension. + * + * @param file + * + * @return + */ + public boolean isFileOperationAllowed( String file ) { + String fileExtension = Files.getFileExtension( file ); + return this.isExtensionAllowed( fileExtension ); + } + + /** + * Determines whether a file extension is allowed or not. + * + * @param extension + * + * @return + */ + public boolean isExtensionAllowed( String extension ) { + if ( this.allowedFileOperationExtensions.contains( extension ) ) { + return true; + } else { + return !this.disallowedFileOperationExtensions.contains( extension ); + } + } + /** * Processes the configuration struct. Each segment is processed individually from the initial configuration struct. * diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileCopyTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileCopyTest.java index 85c5f85c9..1ddf68623 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileCopyTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileCopyTest.java @@ -40,6 +40,7 @@ import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.util.FileSystemUtil; public class FileCopyTest { @@ -139,4 +140,18 @@ public void testFileCopy() throws IOException { ); } + @DisplayName( "It tests the BIF FileCopy security" ) + @Test + public void testBifSecurity() { + variables.put( Key.of( "source" ), Path.of( sourceFile ).toAbsolutePath().toString() ); + assertThrows( + BoxRuntimeException.class, + () -> instance.executeSource( + """ + fileCopy( source, "blah.exe" ); + """, + context ) + ); + } + } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileMoveTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileMoveTest.java index 59c1350af..bec9af550 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileMoveTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/io/FileMoveTest.java @@ -20,6 +20,7 @@ package ortus.boxlang.runtime.bifs.global.io; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; @@ -39,6 +40,7 @@ import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.util.FileSystemUtil; public class FileMoveTest { @@ -54,6 +56,10 @@ public class FileMoveTest { @BeforeAll public static void setUp() { instance = BoxRuntime.getInstance( true ); + + // We are testing this here to ensure the default configuration is set up correctly + assertFalse( instance.getConfiguration().security.isExtensionAllowed( "exe" ) ); + } @AfterAll @@ -93,4 +99,18 @@ public void testBif() { assertTrue( FileSystemUtil.exists( destination ) ); } + @DisplayName( "It tests the BIF FileMove security" ) + @Test + public void testBifSecurity() { + variables.put( Key.of( "targetFile" ), Path.of( source ).toAbsolutePath().toString() ); + assertThrows( + BoxRuntimeException.class, + () -> instance.executeSource( + """ + fileMove( targetFile, "blah.exe" ); + """, + context ) + ); + } + } diff --git a/src/test/java/ortus/boxlang/runtime/config/segments/SecurityConfigTest.java b/src/test/java/ortus/boxlang/runtime/config/segments/SecurityConfigTest.java index e5851fff9..eaf692096 100644 --- a/src/test/java/ortus/boxlang/runtime/config/segments/SecurityConfigTest.java +++ b/src/test/java/ortus/boxlang/runtime/config/segments/SecurityConfigTest.java @@ -17,6 +17,7 @@ */ package ortus.boxlang.runtime.config.segments; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -80,4 +81,12 @@ public void testIsImportDisallowed_DisallowedImport() { assertThrows( SecurityException.class, () -> securityConfig.isClassAllowed( "java.lang.String" ) ); } + @DisplayName( "Check if an exe extension is disallowed" ) + @Test + public void testIsExeDisalloweed() { + String fileName = "test.exe"; + securityConfig.disallowedFileOperationExtensions.add( "exe" ); + assertFalse( securityConfig.isFileOperationAllowed( fileName ) ); + } + } From 3159e002e4c51a3eff713e31c114eae768f2e8d9 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 16 Oct 2024 22:55:17 +0200 Subject: [PATCH 55/71] BL-656b #resolve Wildcard imports for BoxLang classes --- .../loader/resolvers/BaseResolver.java | 10 ++-- .../runtime/loader/resolvers/BoxResolver.java | 48 +++++++++---------- .../loader/resolvers/JavaResolver.java | 12 +++-- .../boxlang/runtime/util/FileSystemUtil.java | 40 +++++++++++----- .../loader/resolvers/BoxResolverTest.java | 12 +++++ 5 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java index fdb6a996e..6964f7f30 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java @@ -212,7 +212,7 @@ public String expandFromImport( IBoxContext context, String className, List importApplies( thisImport ) && importHas( thisImport, className ) ) + .filter( thisImport -> importApplies( thisImport ) && importHas( context, thisImport, className ) ) // Return the first one, the first one wins .findFirst() // Convert the import to a fully qualified class name @@ -235,12 +235,13 @@ public String expandFromImport( IBoxContext context, String className, List imports ) { - var fullyQualifiedName = imports.stream() - // Discover import by matching the resolver prefix and the class name or alias or multi-import - // This runs from concrete resolvers: bx, java, etc. - // So if the resolver prefix matches, we continue, else we skip it. - .filter( thisImport -> importApplies( thisImport ) && importHas( thisImport, className ) ) - // Return the first one, the first one wins - .findFirst() - // Convert the import to a fully qualified class name - .map( targetImport -> { - String fqn = targetImport.getFullyQualifiedClass( className ); - this.importCache.add( className + ":" + fqn ); - return fqn; - } ) - // Nothing found, return the original class name - .orElse( className ); + public boolean importHasMulti( IBoxContext context, ImportDefinition thisImport, String className ) { + String packageSlashName = getFullyQualifiedSlashName( thisImport.getPackageName() ); + + // This verifies that the package exists, else we need to expand it + if ( !FileSystemUtil.exists( packageSlashName ) ) { + packageSlashName = FileSystemUtil.expandPath( context, packageSlashName ).absolutePath().toString(); + } - // Security check - BoxRuntime.getInstance().getConfiguration().security.isClassAllowed( fullyQualifiedName ); + // Get the stream of class files in the package + // If it finds a class that matches the class name, then it returns true, else false + return FileSystemUtil.listDirectory( + packageSlashName, + false, + null, + "name", + "file" + ) + .anyMatch( path -> path.getFileName().toString().startsWith( className + "." ) ); - // Return the fully qualified class name - return fullyQualifiedName; } } diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java index a5bff8ac9..ff3c346e9 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java @@ -265,16 +265,17 @@ public Optional findFromSystem( String fullyQualifiedName, List listDirectory( String path, Boolean recurse, Object filter, String sort, String type ) { - final String theType = type.toLowerCase(); + Path targetPath = Path.of( path ); + // If path doesn't exist, return an empty stream - if ( !Files.exists( Path.of( path ) ) ) { + if ( !Files.exists( targetPath ) ) { return Stream.empty(); } + // Setup variables + final String theType = type.toLowerCase(); String[] sortElements = sort.split( ( "\\s+" ) ); String sortField = sortElements[ 0 ]; String sortDirection = sortElements.length > 1 ? sortElements[ 1 ].toLowerCase() : "asc"; @@ -333,32 +336,47 @@ public static Stream listDirectory( String path, Boolean recurse, Object f try { if ( recurse ) { - directoryStream = Files.walk( Path.of( path ) ).parallel().filter( filterPath -> !filterPath.equals( Path.of( path ) ) ); + directoryStream = Files.walk( targetPath ).parallel().filter( filterPath -> !filterPath.equals( targetPath ) ); } else { - directoryStream = Files.walk( Path.of( path ), 1 ).parallel().filter( filterPath -> !filterPath.equals( Path.of( path ) ) ); + directoryStream = Files.walk( targetPath, 1 ).parallel().filter( filterPath -> !filterPath.equals( targetPath ) ); } } catch ( IOException e ) { throw new BoxIOException( e ); } + // Apply the type filter directoryStream = directoryStream.filter( item -> matchesType( item, theType ) ); - if ( filter instanceof String && StringCaster.cast( filter ).length() > 1 ) { - ArrayList pathMatchers = ListUtil.asList( StringCaster.cast( filter ), "|" ) + // Is the filter a string or a closure? + if ( filter instanceof String castedFilter && castedFilter.length() > 1 ) { + ArrayList pathMatchers = ListUtil + .asList( castedFilter, "|" ) .stream() .map( filterString -> FileSystems.getDefault().getPathMatcher( "glob:" + filterString ) ) .collect( Collectors.toCollection( ArrayList::new ) ); directoryStream = directoryStream.filter( item -> pathMatchers.stream().anyMatch( pathMatcher -> pathMatcher.matches( item.getFileName() ) ) ); - } else if ( filter instanceof java.util.function.Predicate ) { + } + // Predicate filter + else if ( filter instanceof java.util.function.Predicate ) { directoryStream = directoryStream.filter( ( java.util.function.Predicate ) filter ); } + // Finally, sort the stream return directoryStream.sorted( pathSort ); } + /** + * Matches the type of a file or directory + * + * @param item the path to match + * @param type the type to match + * + * @return a boolean as to whether the path matches the type + */ private static Boolean matchesType( Path item, String type ) { switch ( type ) { case "directory" : + case "dir" : return Files.isDirectory( item ); case "file" : return Files.isRegularFile( item ); @@ -1079,9 +1097,9 @@ public static Object deserializeFromFile( Path filePath ) { /** * Performs case insensitive path resolution. This will return the real path, which may be different in case than the incoming path. - * + * * @param path The path to check - * + * * @return The resolved path or null if not found */ public static Path pathExistsCaseInsensitive( Path path ) { diff --git a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java index 09a1c2c1e..f06736e38 100644 --- a/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java +++ b/src/test/java/ortus/boxlang/runtime/loader/resolvers/BoxResolverTest.java @@ -163,6 +163,18 @@ void testResolveWithImports() { assertThat( classLocation.get().path() ).contains( "TestClass" ); } + @DisplayName( "It can resolve classes using wildcard imports" ) + @Test + void testResolveWithWildcardImports() { + String className = "TestClass"; + List imports = Arrays.asList( + ImportDefinition.parse( "src.test.bx.models.*" ) + ); + Optional classLocation = boxResolver.resolve( context, className, imports ); + assertThat( classLocation.isPresent() ).isTrue(); + assertThat( classLocation.get().path() ).contains( "TestClass" ); + } + @DisplayName( "It can find classes from modules" ) @Test void testFindFromModules() { From ec8548fafa943cdf862ff1ffeb39e4cabc148047 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 17 Oct 2024 16:03:23 +0200 Subject: [PATCH 56/71] BL-658 #resolve MIgrate AST Capture to it's own experimental flag --- .../java/ortus/boxlang/runtime/BoxRuntime.java | 18 ++++++++++-------- src/main/resources/config/boxlang.json | 5 ++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index 64242967c..9ad2065c9 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -53,6 +53,7 @@ import ortus.boxlang.runtime.context.RequestBoxContext; import ortus.boxlang.runtime.context.RuntimeBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.casters.CastAttempt; import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.events.BoxEvent; @@ -345,17 +346,18 @@ private void loadConfiguration( Boolean debugMode, String configPath ) { this.logger.info( "+ DebugMode detected in config, overriding to {}", this.debugMode ); } - // If in debug mode load the AST Capture listener for debugging - if ( this.debugMode ) { - this.interceptorService.register( - DynamicObject.of( new ASTCapture( false, true ) ), - Key.onParse - ); - } + // AST Capture experimental feature + BooleanCaster.attempt( + this.configuration.experimental.getOrDefault( "ASTCapture", false ) + ).ifSuccessful( + astCapture -> this.interceptorService.register( + DynamicObject.of( new ASTCapture( false, true ) ), + Key.onParse + ) + ); // Load core logger and other core interceptions this.interceptorService.register( new Logging( this ) ); - } /** diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 21c2b9b84..f2756db0a 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -74,7 +74,10 @@ "logsDirectory": "${boxlang-home}/logs", // This is the experimental features flags. // Please see the documentation to see which flags are available - "experimental": {}, + "experimental": { + // If enabled, it will generate AST JSON data under the project's /grapher/data folder + "ASTCapture": false + }, // Global Executors for the runtime // These are managed by the AsyncService and registered upon startup // The name of the executor is the key and the value is a struct of executor settings From db23cbbcb726b646b2727419c6de0a0ecc9fa5ae Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 17 Oct 2024 16:41:36 +0200 Subject: [PATCH 57/71] BL-659 #resolve Cannot invoke "java.lang.CharSequence.length()" because "this.text" is null --- .../runtime/bifs/global/string/ReReplace.java | 25 +++++++++++++------ .../bifs/global/string/ReReplaceTest.java | 12 +++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java index e147372c9..b596b3ec4 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java @@ -51,20 +51,20 @@ public ReReplace() { } /** - * + * * Uses a regular expression (regex) to search a string for a string pattern and replace it with another. The search is case-sensitive. - * + * * @param context The context in which the BIF is being invoked. * @param arguments Argument scope for the BIF. - * + * * @argument.string The string to search - * + * * @argument.regex The regular expression to search for - * + * * @argument.substring The string to replace regex with - * + * * @argument.scope The scope to search in (one, all) - * + * */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String string = arguments.getAsString( Key.string ); @@ -77,6 +77,17 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { regex = "(?i)" + regex; } + // Default string if null + if ( string == null ) { + string = ""; + } + if ( substring == null ) { + substring = ""; + } + if ( regex == null ) { + regex = ""; + } + // Posix replacement for character classes regex = RegexUtil.posixReplace( regex, noCase ); // Ignore non-quantifier curly braces like PERL diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReReplaceTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReReplaceTest.java index 6e05d97c4..5e428c001 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReReplaceTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReReplaceTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import ortus.boxlang.runtime.BoxRuntime; @@ -190,4 +191,15 @@ public void testPerlStyleCurlyLooseness() { // @formatter:on } + @DisplayName( "Doesn't throw exception if the string is null" ) + @Test + public void testNullString() { + instance.executeSource( + """ + result = reReplace( null, "[^a-z0-9]", '', "one" ); + """, + context ); + assertThat( variables.get( result ) ).isEqualTo( "" ); + } + } From 770384a6b89b250adeacc5521646d7c50b102b83 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 17 Oct 2024 22:18:02 +0200 Subject: [PATCH 58/71] BL-661 #resolve StopGaps and config from cfconfig.json to boxlang.json --- .../boxlang/runtime/config/Configuration.java | 142 ++++++++++++------ src/test/resources/test-boxlang.json | 1 + 2 files changed, 94 insertions(+), 49 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index 66039a7da..a8b755a5f 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.TimeZone; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +63,8 @@ /** * The BoxLang configuration object representing the core configuration. - * This object is responsible for processing the configuration struct and returning a new configuration object based on the overrides. + * This object is responsible for processing the configuration struct and + * returning a new configuration object based on the overrides. * Each segment is processed individually from the initial configuration struct. * The configuration object can be converted to a struct for serialization. * @@ -110,7 +112,8 @@ public class Configuration implements IConfigSegment { public Boolean invokeImplicitAccessor = true; /** - * Use high precision math for all math operations, else it relies on Double precision + * Use high precision math for all math operations, else it relies on Double + * precision * {@code true} by default */ public Boolean useHighPrecisionMath = true; @@ -134,13 +137,15 @@ public class Configuration implements IConfigSegment { public Duration sessionTimeout = Duration.ofMinutes( 30 ); /** - * This flag enables/disables session management in the runtime for all applications by default. + * This flag enables/disables session management in the runtime for all + * applications by default. * {@code false} by default */ public Boolean sessionManagement = false; /** - * The default session storage cache. This has to be the name of a registered cache + * The default session storage cache. This has to be the name of a registered + * cache * or the keyword "memory" which indicates our internal cache. * {@code memory} is the default */ @@ -153,7 +158,8 @@ public class Configuration implements IConfigSegment { public Boolean setClientCookies = true; /** - * Sets jSessionID cookies for a domain (not a host) Required, for applications running on clusters + * Sets jSessionID cookies for a domain (not a host) Required, for applications + * running on clusters * {@code true} by default */ public Boolean setDomainCookies = true; @@ -205,7 +211,8 @@ public class Configuration implements IConfigSegment { public IStruct datasources = new Struct(); /** - * Default remote class method return format when executing a method from web runtimes. + * Default remote class method return format when executing a method from web + * runtimes. * The default is JSON */ public String defaultRemoteMethodReturnFormat = "json"; @@ -268,9 +275,11 @@ public class Configuration implements IConfigSegment { */ /** - * Processes a configuration struct and returns a new configuration object based on the overrides. + * Processes a configuration struct and returns a new configuration object based + * on the overrides. * - * This method makes sure all elements in the incoming configuration struct are processed and applied to the configuration object. + * This method makes sure all elements in the incoming configuration struct are + * processed and applied to the configuration object. * * @param config the configuration struct * @@ -280,9 +289,12 @@ public Configuration process( IStruct config ) { // Store original config this.originalConfig = config; - // Debug Mode + // Debug Mode || Debbuging Enabled (cfconfig) if ( config.containsKey( "debugMode" ) ) { - this.debugMode = ( Boolean ) config.get( "debugMode" ); + this.debugMode = BooleanCaster.cast( PlaceholderHelper.resolve( config.get( "debugMode" ) ) ); + } + if ( config.containsKey( "debuggingEnabled" ) ) { + this.debugMode = BooleanCaster.cast( PlaceholderHelper.resolve( config.get( "debuggingEnabled" ) ) ); } // Compiler @@ -317,18 +329,22 @@ public Configuration process( IStruct config ) { } // Application Timeout - if ( config.containsKey( Key.applicationTimeout ) && StringCaster.cast( config.get( "applicationTimeout" ) ).length() > 0 ) { - this.applicationTimeout = DateTimeHelper.timespanToDuration( PlaceholderHelper.resolve( config.get( "applicationTimeout" ) ) ); + if ( config.containsKey( Key.applicationTimeout ) + && StringCaster.cast( config.get( "applicationTimeout" ) ).length() > 0 ) { + this.applicationTimeout = DateTimeHelper + .timespanToDuration( PlaceholderHelper.resolve( config.get( "applicationTimeout" ) ) ); } // Request Timeout if ( config.containsKey( Key.requestTimeout ) && StringCaster.cast( config.get( "requestTimeout" ) ).length() > 0 ) { - this.requestTimeout = DateTimeHelper.timespanToDuration( PlaceholderHelper.resolve( config.get( "requestTimeout" ) ) ); + this.requestTimeout = DateTimeHelper + .timespanToDuration( PlaceholderHelper.resolve( config.get( "requestTimeout" ) ) ); } // Session Timeout if ( config.containsKey( Key.sessionTimeout ) && StringCaster.cast( config.get( "sessionTimeout" ) ).length() > 0 ) { - this.sessionTimeout = DateTimeHelper.timespanToDuration( PlaceholderHelper.resolve( config.get( "sessionTimeout" ) ) ); + this.sessionTimeout = DateTimeHelper + .timespanToDuration( PlaceholderHelper.resolve( config.get( "sessionTimeout" ) ) ); } // Session Management @@ -359,8 +375,7 @@ public Configuration process( IStruct config ) { if ( config.get( Key.mappings ) instanceof Map castedMap ) { castedMap.forEach( ( key, value ) -> this.mappings.put( Key.of( key ), - PlaceholderHelper.resolve( value ) - ) ); + PlaceholderHelper.resolve( value ) ) ); } else { logger.warn( "The [runtime.mappings] configuration is not a JSON Object, ignoring it." ); } @@ -414,7 +429,8 @@ public Configuration process( IStruct config ) { // Process the default method return format if ( config.containsKey( Key.defaultRemoteMethodReturnFormat ) ) { - this.defaultRemoteMethodReturnFormat = PlaceholderHelper.resolve( config.get( Key.defaultRemoteMethodReturnFormat ) ).toLowerCase(); + this.defaultRemoteMethodReturnFormat = PlaceholderHelper + .resolve( config.get( Key.defaultRemoteMethodReturnFormat ) ).toLowerCase(); } // Process default cache configuration @@ -433,11 +449,18 @@ public Configuration process( IStruct config ) { castedCaches .entrySet() .forEach( entry -> { + + // We ignore `default` caches, not accepted in boxlang. + if ( StringUtils.equalsIgnoreCase( ( String ) entry.getKey(), "default" ) ) { + return; + } + if ( entry.getValue() instanceof Map castedMap ) { CacheConfig cacheConfig = new CacheConfig( ( String ) entry.getKey() ).process( new Struct( castedMap ) ); this.caches.put( cacheConfig.name, cacheConfig ); } else { - logger.warn( "The [caches.{}] configuration is not a JSON Object, ignoring it.", entry.getKey() ); + logger.warn( "The [caches.{}] configuration is not a JSON Object, ignoring it.", + entry.getKey() ); } } ); } else { @@ -453,10 +476,12 @@ public Configuration process( IStruct config ) { .entrySet() .forEach( entry -> { if ( entry.getValue() instanceof Map castedMap ) { - ExecutorConfig executorConfig = new ExecutorConfig( ( String ) entry.getKey() ).process( new Struct( castedMap ) ); + ExecutorConfig executorConfig = new ExecutorConfig( ( String ) entry.getKey() ) + .process( new Struct( castedMap ) ); this.executors.put( executorConfig.name, executorConfig ); } else { - logger.warn( "The [executors.{}] configuration is not a JSON Object, ignoring it.", entry.getKey() ); + logger.warn( "The [executors.{}] configuration is not a JSON Object, ignoring it.", + entry.getKey() ); } } ); } else { @@ -468,7 +493,8 @@ public Configuration process( IStruct config ) { if ( config.containsKey( Key.validClassExtensions ) ) { if ( config.get( Key.validClassExtensions ) instanceof List castedList ) { // iterate and add to the original list if it doesn't exist - castedList.forEach( item -> this.validClassExtensions.add( PlaceholderHelper.resolve( item ).toLowerCase() ) ); + castedList + .forEach( item -> this.validClassExtensions.add( PlaceholderHelper.resolve( item ).toLowerCase() ) ); } else { logger.warn( "The [validClassExtensions] configuration is not a JSON Array, ignoring it." ); } @@ -478,7 +504,8 @@ public Configuration process( IStruct config ) { if ( config.containsKey( Key.validTemplateExtensions ) ) { if ( config.get( Key.validTemplateExtensions ) instanceof List castedList ) { // iterate and add to the original list if it doesn't exist - castedList.forEach( item -> this.validTemplateExtensions.add( PlaceholderHelper.resolve( item ).toLowerCase() ) ); + castedList.forEach( + item -> this.validTemplateExtensions.add( PlaceholderHelper.resolve( item ).toLowerCase() ) ); } else { logger.warn( "The [validTemplateExtensions] configuration is not a JSON Array, ignoring it." ); } @@ -506,10 +533,13 @@ public Configuration process( IStruct config ) { .entrySet() .forEach( entry -> { if ( entry.getValue() instanceof Map castedMap ) { - DatasourceConfig datasourceConfig = new DatasourceConfig( Key.of( entry.getKey() ) ).process( new Struct( castedMap ) ); + DatasourceConfig datasourceConfig = new DatasourceConfig( Key.of( entry.getKey() ) ) + .process( new Struct( castedMap ) ); this.datasources.put( datasourceConfig.name, datasourceConfig ); } else { - logger.warn( "The [runtime.datasources.{}] configuration is not a JSON Object, ignoring it.", entry.getKey() ); + logger.warn( + "The [runtime.datasources.{}] configuration is not a JSON Object, ignoring it.", + entry.getKey() ); } } ); } else { @@ -525,10 +555,12 @@ public Configuration process( IStruct config ) { .entrySet() .forEach( entry -> { if ( entry.getValue() instanceof Map castedMap ) { - ModuleConfig moduleConfig = new ModuleConfig( KeyCaster.cast( entry.getKey() ).getName() ).process( new Struct( castedMap ) ); + ModuleConfig moduleConfig = new ModuleConfig( KeyCaster.cast( entry.getKey() ).getName() ) + .process( new Struct( castedMap ) ); this.modules.put( moduleConfig.name, moduleConfig ); } else { - logger.warn( "The [runtime.modules.{}] configuration is not a JSON Object, ignoring it.", entry.getKey() ); + logger.warn( "The [runtime.modules.{}] configuration is not a JSON Object, ignoring it.", + entry.getKey() ); } } ); @@ -565,7 +597,8 @@ public String[] getRegisteredMappings() { /** * Verify if a mapping exists * - * @param mapping The mapping to verify: {@code /myMapping}, please note the leading slash + * @param mapping The mapping to verify: {@code /myMapping}, please note the + * leading slash * * @return True if the mapping exists, false otherwise */ @@ -576,7 +609,8 @@ public boolean hasMapping( String mapping ) { /** * Verify if a mapping exists * - * @param mapping The mapping to verify: {@code /myMapping}, please note the leading slash + * @param mapping The mapping to verify: {@code /myMapping}, please note the + * leading slash * * @return True if the mapping exists, false otherwise */ @@ -591,7 +625,8 @@ public boolean hasMapping( Key mapping ) { /** * Register a mapping in the runtime configuration * - * @param mapping The mapping to register: {@code /myMapping}, please note the leading slash + * @param mapping The mapping to register: {@code /myMapping}, please note the + * leading slash * @param path The absolute path to the directory to map to the mapping * * @throws BoxRuntimeException If the path does not exist @@ -605,7 +640,8 @@ public Configuration registerMapping( String mapping, String path ) { /** * Register a mapping in the runtime configuration * - * @param mapping The mapping to register: {@code /myMapping}, please note the leading slash + * @param mapping The mapping to register: {@code /myMapping}, please note the + * leading slash * @param path The absolute path to the directory to map to the mapping * * @throws BoxRuntimeException If the path does not exist @@ -624,8 +660,7 @@ public Configuration registerMapping( Key mapping, String path ) { // Verify it exists else throw an exception if ( !pathObj.toFile().exists() ) { throw new BoxRuntimeException( - String.format( "The path [%s] does not exist.", pathObj ) - ); + String.format( "The path [%s] does not exist.", pathObj ) ); } // Now we can add it @@ -637,7 +672,8 @@ public Configuration registerMapping( Key mapping, String path ) { /** * Unregister a mapping in the runtime configuration * - * @param mapping The String mapping to unregister: {@code /myMapping}, please note the leading slash + * @param mapping The String mapping to unregister: {@code /myMapping}, please + * note the leading slash * * @return True if the mapping was removed, false otherwise */ @@ -648,7 +684,8 @@ public boolean unregisterMapping( String mapping ) { /** * Unregister a mapping in the runtime configuration using a {@link Key} * - * @param mapping The Key mapping to unregister: {@code /myMapping}, please note the leading slash + * @param mapping The Key mapping to unregister: {@code /myMapping}, please note + * the leading slash * * @return True if the mapping was removed, false otherwise */ @@ -669,7 +706,8 @@ public boolean unregisterMapping( Key mapping ) { /** * Get the java library paths as an array of URLs of Jar files - * This is usually called by the runtime to load all the JARs in the paths to the runtime classloader + * This is usually called by the runtime to load all the JARs in the paths to + * the runtime classloader * * @throws BoxIOException If a path is not a valid path * @@ -701,13 +739,17 @@ public URL[] getJavaLibraryPaths() { } /** - * Helper method to validate datasource drivers configured in the runtime configuration - * This makes sure all declared drivers are registered with the datasource service + * Helper method to validate datasource drivers configured in the runtime + * configuration + * This makes sure all declared drivers are registered with the datasource + * service * - * @throws BoxRuntimeException If a datasource driver is not registered with the datasource service + * @throws BoxRuntimeException If a datasource driver is not registered with the + * datasource service */ public void validateDatsourceDrivers() { - // iterate over all datasources and validate the drivers exists in the datasource service, else throw an exception + // iterate over all datasources and validate the drivers exists in the + // datasource service, else throw an exception this.datasources.entrySet().forEach( entry -> { DatasourceConfig datasource = ( DatasourceConfig ) entry.getValue(); if ( !BoxRuntime.getInstance().getDataSourceService().hasDriver( datasource.getDriver() ) ) { @@ -715,9 +757,7 @@ public void validateDatsourceDrivers() { String.format( "The datasource [%s] has a driver [%s] that is not registered with the datasource service.", datasource.name, - datasource.getDriver() - ) - ); + datasource.getDriver() ) ); } } ); } @@ -731,7 +771,8 @@ public void validateDatsourceDrivers() { * config.navigate( "originalConfig" ) * * - * @param path The path to the object in the data structure. By default it's the root. + * @param path The path to the object in the data structure. By default it's the + * root. * * @return The navigator with a potential navigation path set */ @@ -768,16 +809,20 @@ public IStruct asStruct() { mappingsCopy.putAll( this.mappings ); IStruct cachesCopy = new Struct(); - this.caches.entrySet().forEach( entry -> cachesCopy.put( entry.getKey(), ( ( CacheConfig ) entry.getValue() ).toStruct() ) ); + this.caches.entrySet() + .forEach( entry -> cachesCopy.put( entry.getKey(), ( ( CacheConfig ) entry.getValue() ).toStruct() ) ); IStruct executorsCopy = new Struct(); - this.executors.entrySet().forEach( entry -> executorsCopy.put( entry.getKey(), ( ( ExecutorConfig ) entry.getValue() ).toStruct() ) ); + this.executors.entrySet() + .forEach( entry -> executorsCopy.put( entry.getKey(), ( ( ExecutorConfig ) entry.getValue() ).toStruct() ) ); IStruct datsourcesCopy = new Struct(); - this.datasources.entrySet().forEach( entry -> datsourcesCopy.put( entry.getKey(), ( ( DatasourceConfig ) entry.getValue() ).asStruct() ) ); + this.datasources.entrySet() + .forEach( entry -> datsourcesCopy.put( entry.getKey(), ( ( DatasourceConfig ) entry.getValue() ).asStruct() ) ); IStruct modulesCopy = new Struct(); - this.modules.entrySet().forEach( entry -> modulesCopy.put( entry.getKey(), ( ( ModuleConfig ) entry.getValue() ).asStruct() ) ); + this.modules.entrySet() + .forEach( entry -> modulesCopy.put( entry.getKey(), ( ( ModuleConfig ) entry.getValue() ).asStruct() ) ); return Struct.of( @@ -810,7 +855,6 @@ public IStruct asStruct() { Key.useHighPrecisionMath, this.useHighPrecisionMath, Key.validExtensions, Array.fromSet( getValidExtensions() ), Key.validClassExtensions, Array.fromSet( this.validClassExtensions ), - Key.validTemplateExtensions, Array.fromSet( this.validTemplateExtensions ) - ); + Key.validTemplateExtensions, Array.fromSet( this.validTemplateExtensions ) ); } } diff --git a/src/test/resources/test-boxlang.json b/src/test/resources/test-boxlang.json index ac394ffcb..66d3cbbdd 100644 --- a/src/test/resources/test-boxlang.json +++ b/src/test/resources/test-boxlang.json @@ -22,6 +22,7 @@ "useLastAccessTimeouts": true }, "caches": { + "default": {}, "imports": { "provider": "BoxCacheProvider", "properties": { From d07a5b3cbb402ed3b3885ae362e61b2d83c024ee Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 17 Oct 2024 23:44:34 +0200 Subject: [PATCH 59/71] BL-662 #resolve NotImplemented updated to check if it's a string and if empty, then ignore it to provide leeway --- .../runtime/validation/NotImplemented.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java b/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java index 2dc850322..0bcab12da 100644 --- a/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java +++ b/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java @@ -29,10 +29,19 @@ */ public class NotImplemented implements Validator { - public void validate( IBoxContext context, Key caller, Validatable record, IStruct records ) { - if ( records.get( record.name() ) != null ) { - throw new BoxValidationException( caller, record, "is not implemented yet and should not be provided." ); + public void validate(IBoxContext context, Key caller, Validatable record, IStruct records) { + Object targetValue = records.get(record.name()); + + if (targetValue != null) { + + // If the value is a string and EMPTY, then ignore it + // LEEWAY! + if( targetValue instanceof String castedTargetValue && castedTargetValue.isEmpty() ) { + return; + } + + throw new BoxValidationException(caller, record, "is not implemented yet and should not be provided."); } } -} \ No newline at end of file +} From fd620c5d4e62d6c43fad608048b4cd1f43906d49 Mon Sep 17 00:00:00 2001 From: lmajano Date: Thu, 17 Oct 2024 21:45:28 +0000 Subject: [PATCH 60/71] Apply cfformat changes --- .../boxlang/runtime/validation/NotImplemented.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java b/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java index 0bcab12da..ed41a6feb 100644 --- a/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java +++ b/src/main/java/ortus/boxlang/runtime/validation/NotImplemented.java @@ -29,18 +29,18 @@ */ public class NotImplemented implements Validator { - public void validate(IBoxContext context, Key caller, Validatable record, IStruct records) { - Object targetValue = records.get(record.name()); + public void validate( IBoxContext context, Key caller, Validatable record, IStruct records ) { + Object targetValue = records.get( record.name() ); - if (targetValue != null) { + if ( targetValue != null ) { // If the value is a string and EMPTY, then ignore it // LEEWAY! - if( targetValue instanceof String castedTargetValue && castedTargetValue.isEmpty() ) { + if ( targetValue instanceof String castedTargetValue && castedTargetValue.isEmpty() ) { return; } - throw new BoxValidationException(caller, record, "is not implemented yet and should not be provided."); + throw new BoxValidationException( caller, record, "is not implemented yet and should not be provided." ); } } From ee1e865ea08bea583b7a52f4d0535c254cac6388 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 00:38:21 +0200 Subject: [PATCH 61/71] small license update --- src/main/resources/META-INF/boxlang/license.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/boxlang/license.txt b/src/main/resources/META-INF/boxlang/license.txt index 8e741b018..e3a27f664 100755 --- a/src/main/resources/META-INF/boxlang/license.txt +++ b/src/main/resources/META-INF/boxlang/license.txt @@ -1,6 +1,6 @@ ******************************************************************************** BoxLang Copyright Since 2023 by Ortus Solutions, Corp -www.boxlang.org | www.ortussolutions.com +www.boxlang.io | www.ortussolutions.com ******************************************************************************** Ways to Help: - Become a Sponsor of the project by visiting https://www.ortussolutions.com/about-us/sponsors From 45e53b9b37fe78d541f33fac27c3f03f4d695c02 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 17 Oct 2024 20:36:27 -0500 Subject: [PATCH 62/71] Finish fixing issues from merge conflict --- .../compiler/asmboxpiler/AsmHelper.java | 146 ++++++++++++++++++ .../compiler/asmboxpiler/AsmTranspiler.java | 49 ++++-- .../compiler/asmboxpiler/Transpiler.java | 9 +- .../BoxMethodInvocationTransformer.java | 22 +-- .../BoxStringConcatTransformer.java | 1 + .../statement/BoxClassTransformer.java | 146 +----------------- .../statement/BoxComponentTransformer.java | 2 +- .../statement/BoxForInTransformer.java | 10 +- .../BoxFunctionDeclarationTransformer.java | 35 +++++ .../statement/BoxInterfaceTransformer.java | 74 ++++++--- .../java/TestCases/asm/phase3/ClassTest.java | 20 +-- 11 files changed, 299 insertions(+), 215 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index d7a332dfa..e534066a5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; @@ -27,9 +28,14 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.ast.BoxClass; import ortus.boxlang.compiler.ast.BoxExpression; +import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxStatement; import ortus.boxlang.compiler.ast.expression.BoxArgument; +import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxReturnType; +import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; @@ -39,13 +45,153 @@ import ortus.boxlang.runtime.runnables.BoxClassSupport; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.AbstractFunction; +import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.DefaultExpression; +import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.util.MapHelper; import ortus.boxlang.runtime.util.ResolvedFilePath; public class AsmHelper { + public static List generateMapOfAbstractMethodNames( Transpiler transpiler, BoxNode classOrInterface ) { + List> methodKeyLists = classOrInterface.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .filter( func -> func.getBody() == null ) + .map( func -> { + List> absFunc = List.of( + transpiler.createKey( func.getName() ), + createAbstractFunction( transpiler, func ) + ); + + return absFunc; + } ) + .flatMap( x -> x.stream() ) + .collect( java.util.stream.Collectors.toList() ); + + List nodes = new ArrayList(); + + nodes.addAll( AsmHelper.array( Type.getType( Object.class ), methodKeyLists ) ); + + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, + Type.getInternalName( MapHelper.class ), + "LinkedHashMapOfAny", + Type.getMethodDescriptor( Type.getType( Map.class ), Type.getType( Object[].class ) ), + false ) ); + + return nodes; + } + + private static List generateSetOfCompileTimeMethodNames( Transpiler transpiler, BoxClass boxClass ) { + List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) + .stream() + .map( BoxFunctionDeclaration::getName ) + .map( transpiler::createKey ) + .collect( java.util.stream.Collectors.toList() ); + + List nodes = new ArrayList(); + + nodes.addAll( AsmHelper.array( Type.getType( Key.class ), methodKeyLists ) ); + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + Type.getInternalName( Set.class ), + "of", + Type.getMethodDescriptor( Type.getType( Set.class ), Type.getType( Object[].class ) ), + true + ) + ); + + return nodes; + + } + + private static String getFunctionReturnType( BoxReturnType returnType ) { + if ( returnType == null || returnType.getType() == null ) { + return "any"; + } + + if ( returnType.getType().equals( BoxType.Fqn ) ) { + return returnType.getFqn(); + } + + return returnType.getType().name(); + } + + public static List createAbstractFunction( Transpiler transpiler, BoxFunctionDeclaration func ) { + List nodes = new ArrayList(); + + nodes.add( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( AbstractFunction.class ) ) ); + nodes.add( new InsnNode( Opcodes.DUP ) ); + + // args + // Key name + nodes.addAll( transpiler.createKey( func.getName() ) ); + // Argument[] arguments + List> argList = func.getArgs() + .stream() + .map( arg -> transpiler.transform( arg, TransformerContext.NONE ) ) + .toList(); + nodes.addAll( AsmHelper.array( Type.getType( Argument.class ), argList ) ); + + nodes.add( new LdcInsnNode( getFunctionReturnType( func.getType() ) ) ); + + String accessModifier = "PUBLIC"; + + if ( func.getAccessModifier() != null ) { + accessModifier = func.getAccessModifier().name().toUpperCase(); + } + // Access access + nodes.add( + new FieldInsnNode( + Opcodes.GETSTATIC, + Type.getInternalName( Function.Access.class ), + accessModifier, + Type.getDescriptor( Function.Access.class ) + ) + ); + // IStruct annotations + // TODO + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // IStruct documentation + // TODO + nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, + Type.getInternalName( Struct.class ), + "EMPTY", + Type.getDescriptor( IStruct.class ) ) ); + // String sourceObjectName + nodes.add( new LdcInsnNode( transpiler.getProperty( "boxClassName" ) ) ); + // String sourceObjectType + nodes.add( new LdcInsnNode( "class" ) ); + + nodes.add( + new MethodInsnNode( + Opcodes.INVOKESPECIAL, + Type.getInternalName( AbstractFunction.class ), + "", + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getType( Key.class ), + Type.getType( Argument[].class ), + Type.getType( String.class ), + Type.getType( Function.Access.class ), + Type.getType( IStruct.class ), + Type.getType( IStruct.class ), + Type.getType( String.class ), + Type.getType( String.class ) + ), + false + ) + ); + + return nodes; + } + public static void addDebugLabel( List nodes, String label ) { if ( !ASMBoxpiler.DEBUG ) { return; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 5c7d326eb..32ccc6ef6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -143,9 +143,6 @@ import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.ClassVariablesScope; import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.StaticScope; -import ortus.boxlang.runtime.scopes.ThisScope; -import ortus.boxlang.runtime.scopes.VariablesScope; import ortus.boxlang.runtime.types.AbstractFunction; import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.DefaultExpression; @@ -386,20 +383,52 @@ public List> transformProperties( Type declaringType, Lis .findFirst() .orElse( null ); - List defaultLiteral = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); - List defaultExpression = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + // List defaultLiteral = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + // List defaultExpression = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + // if ( defaultAnnotation.getValue() != null ) { + // if ( defaultAnnotation.getValue().isLiteral() ) { + // defaultLiteral = transform( defaultAnnotation.getValue(), TransformerContext.NONE ); + // } else { + // defaultExpression = AsmHelper.getDefaultExpression( this, defaultAnnotation.getValue() ); + // } + // } + + List annotationStruct = transformAnnotations( finalAnnotations ); + List init, initLambda; if ( defaultAnnotation.getValue() != null ) { + if ( defaultAnnotation.getValue().isLiteral() ) { - defaultLiteral = transform( defaultAnnotation.getValue(), TransformerContext.NONE ); + init = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + initLambda = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); } else { - defaultExpression = AsmHelper.getDefaultExpression( this, defaultAnnotation.getValue() ); + init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + + Type type = Type.getType( "L" + getProperty( "packageName" ).replace( '.', '/' ) + + "/" + getProperty( "classname" ) + + "$Lambda_" + incrementAndGetLambdaCounter() + ";" ); + + List body = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + ClassNode classNode = new ClassNode(); + AsmHelper.init( classNode, false, type, Type.getType( Object.class ), methodVisitor -> { + }, Type.getType( DefaultExpression.class ) ); + AsmHelper.methodWithContextAndClassLocator( classNode, "evaluate", Type.getType( IBoxContext.class ), Type.getType( Object.class ), false, + this, false, + () -> body ); + setAuxiliary( type.getClassName(), classNode ); + + initLambda = List.of( + new TypeInsnNode( Opcodes.NEW, type.getInternalName() ), + new InsnNode( Opcodes.DUP ), + new MethodInsnNode( Opcodes.INVOKESPECIAL, type.getInternalName(), "", Type.getMethodDescriptor( Type.VOID_TYPE ), false ) ); } + } else { + init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + initLambda = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); } - Type type = Type.getType( "L" + getProperty( "packageName" ).replace( '.', '/' ) + String type; // name and type must be simple values String name; - String type; if ( nameAnnotation != null && nameAnnotation.getValue() instanceof BoxStringLiteral namelit ) { name = namelit.getValue().trim(); if ( name.isEmpty() ) @@ -623,7 +652,7 @@ private static String getBoxExprAsString( BoxExpression expr ) { } } - private List createAbstractFunction( BoxFunctionDeclaration func ) { + public List createAbstractFunction( BoxFunctionDeclaration func ) { List nodes = new ArrayList(); // public AbstractFunction( Key name, Argument[] arguments, String returnType, Access access, IStruct annotations, IStruct documentation, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 5f59bd019..f7fedd2da 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -260,11 +260,10 @@ public List transformAnnotations( List annotati value = transform( thisValue, TransformerContext.NONE, ReturnValueContext.VALUE ); } // gonna try commenting this out - // else if ( onlyLiteralValues ) { - // // Runtime expressions we just put this place holder text in for - // value = List.of( new LdcInsnNode( "" ) ); - // } - else { + else if ( onlyLiteralValues ) { + // Runtime expressions we just put this place holder text in for + value = List.of( new LdcInsnNode( "" ) ); + } else { value = transform( thisValue, TransformerContext.NONE, ReturnValueContext.VALUE ); } } else if ( defaultTrue ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java index 60a533afe..2b32d1771 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java @@ -57,27 +57,7 @@ public List transform( BoxNode node, TransformerContext contex name = transpiler.createKey( invocation.getName() ); } - nodes.addAll( AsmHelper.callReferencerGetAndInvoke( transpiler, invocation.getArguments(), name, context, false ) ); - - // nodes - // .addAll( AsmHelper.array( Type.getType( Object.class ), invocation.getArguments(), - // ( argument, i ) -> transpiler.transform( argument, context, ReturnValueContext.VALUE ) ) ); - - // nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, - // Type.getInternalName( Boolean.class ), - // safe.toString().toUpperCase(), - // Type.getDescriptor( Boolean.class ) ) ); - - // nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, - // Type.getInternalName( Referencer.class ), - // "getAndInvoke", - // Type.getMethodDescriptor( Type.getType( Object.class ), - // Type.getType( IBoxContext.class ), - // Type.getType( Object.class ), - // Type.getType( Key.class ), - // Type.getType( Object[].class ), - // Type.getType( Boolean.class ) ), - // false ) ); + nodes.addAll( AsmHelper.callReferencerGetAndInvoke( transpiler, invocation.getArguments(), name, context, safe ) ); if ( returnContext.empty ) { nodes.add( new InsnNode( Opcodes.POP ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java index 0786efc34..2a2fcde25 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java @@ -44,6 +44,7 @@ public BoxStringConcatTransformer( Transpiler transpiler ) { public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { BoxStringConcat interpolation = ( BoxStringConcat ) node; if ( interpolation.getValues().size() == 1 ) { + // TODO should this invoke StringCaster? return transpiler.transform( interpolation.getValues().get( 0 ), TransformerContext.NONE, returnContext ); } else { List nodes = new ArrayList<>(); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 10a7d50b5..15580be95 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -35,12 +35,8 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -73,18 +69,13 @@ import ortus.boxlang.runtime.scopes.StaticScope; import ortus.boxlang.runtime.scopes.ThisScope; import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.AbstractFunction; -import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.Array; -import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.IType; -import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.meta.BoxMeta; import ortus.boxlang.runtime.types.util.BLCollector; import ortus.boxlang.runtime.types.util.ListUtil; -import ortus.boxlang.runtime.types.util.MapHelper; import ortus.boxlang.runtime.util.ResolvedFilePath; public class BoxClassTransformer { @@ -100,15 +91,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; - String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; - String boxPackageName = transpiler.getProperty( "boxPackageName" ); - String rawBoxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ), boxClassName; - // trim leading . if exists - if ( rawBoxClassName.startsWith( "." ) ) { - boxClassName = rawBoxClassName.substring( 1 ); - } else { - boxClassName = rawBoxClassName; - } + String boxClassName = transpiler.getProperty( "boxFQN" ); transpiler.setProperty( "boxClassName", boxClassName ); String mappingName = transpiler.getProperty( "mappingName" ); String mappingPath = transpiler.getProperty( "mappingPath" ); @@ -641,41 +624,13 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th generateSetOfCompileTimeMethodNames( transpiler, boxClass ).forEach( node -> node.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "compileTimeMethodNames", Type.getDescriptor( Set.class ) ); - generateMapOfAbstractMethodNames( transpiler, boxClass ).forEach( node -> node.accept( methodVisitor ) ); + AsmHelper.generateMapOfAbstractMethodNames( transpiler, boxClass ).forEach( node -> node.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); } ); return classNode; } - private static List generateMapOfAbstractMethodNames( Transpiler transpiler, BoxClass boxClass ) { - List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) - .stream() - .filter( func -> func.getBody() == null ) - .map( func -> { - List> absFunc = List.of( - transpiler.createKey( func.getName() ), - createAbstractFunction( transpiler, func ) - ); - - return absFunc; - } ) - .flatMap( x -> x.stream() ) - .collect( java.util.stream.Collectors.toList() ); - - List nodes = new ArrayList(); - - nodes.addAll( AsmHelper.array( Type.getType( Object.class ), methodKeyLists ) ); - - nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, - Type.getInternalName( MapHelper.class ), - "LinkedHashMapOfAny", - Type.getMethodDescriptor( Type.getType( Map.class ), Type.getType( Object[].class ) ), - false ) ); - - return nodes; - } - private static List generateSetOfCompileTimeMethodNames( Transpiler transpiler, BoxClass boxClass ) { List> methodKeyLists = boxClass.getDescendantsOfType( BoxFunctionDeclaration.class ) .stream() @@ -700,103 +655,6 @@ private static List generateSetOfCompileTimeMethodNames( Trans } - private static List createAbstractFunction( Transpiler transpiler, BoxFunctionDeclaration func ) { - List nodes = new ArrayList(); - - // public AbstractFunction( Key name, Argument[] arguments, String returnType, Access access, IStruct annotations, IStruct documentation, - // String sourceObjectName, String sourceObjectType ) { - // this.name = name; - // this.arguments = arguments; - // this.returnType = returnType; - // this.access = access; - // this.annotations = annotations; - // this.documentation = documentation; - // this.sourceObjectName = sourceObjectName; - // this.sourceObjectType = sourceObjectType; - // } - - nodes.add( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( AbstractFunction.class ) ) ); - nodes.add( new InsnNode( Opcodes.DUP ) ); - - // args - // Key name - nodes.addAll( transpiler.createKey( func.getName() ) ); - // Argument[] arguments - List> argList = func.getArgs() - .stream() - .map( arg -> transpiler.transform( arg, TransformerContext.NONE ) ) - .toList(); - nodes.addAll( AsmHelper.array( Type.getType( Argument.class ), argList ) ); - // String returnType - if ( func.getType() != null ) { - nodes.addAll( transpiler.transform( func.getType(), TransformerContext.NONE ) ); - } else { - nodes.add( new LdcInsnNode( "any" ) ); - } - - String accessModifier = "PUBLIC"; - - if ( func.getAccessModifier() != null ) { - accessModifier = func.getAccessModifier().name().toUpperCase(); - } - // Access access - nodes.add( - new FieldInsnNode( - Opcodes.GETSTATIC, - Type.getInternalName( Function.Access.class ), - accessModifier, - Type.getDescriptor( Function.Access.class ) - ) - ); - // IStruct annotations - // TODO - nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, - Type.getInternalName( Struct.class ), - "EMPTY", - Type.getDescriptor( IStruct.class ) ) ); - // IStruct documentation - // TODO - nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, - Type.getInternalName( Struct.class ), - "EMPTY", - Type.getDescriptor( IStruct.class ) ) ); - // String sourceObjectName - nodes.add( new LdcInsnNode( transpiler.getProperty( "boxClassName" ) ) ); - // String sourceObjectType - nodes.add( new LdcInsnNode( "class" ) ); - - nodes.add( - new MethodInsnNode( - Opcodes.INVOKESPECIAL, - Type.getInternalName( AbstractFunction.class ), - "", - Type.getMethodDescriptor( - Type.VOID_TYPE, - Type.getType( Key.class ), - Type.getType( Argument[].class ), - Type.getType( String.class ), - Type.getType( Function.Access.class ), - Type.getType( IStruct.class ), - Type.getType( IStruct.class ), - Type.getType( String.class ), - Type.getType( String.class ) - ), - false - ) - ); - - // Key name - // Argument[] arguments - // String returnType - // Access access - // IStruct annotations - // IStruct documentation - // String sourceObjectName - // String sourceObjectType - - return nodes; - } - private static void defineLookupPrivateMethod( Transpiler transpiler, ClassNode classNode, Type thisType ) { /** * This code MUST be inside the class to allow for the lookupPrivate method to work diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 00b24c71f..8e6a5c43c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -55,7 +55,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.createKey( boxComponent.getName() ) ); // convert attributes to struct - nodes.addAll( transpiler.transformAnnotations( boxComponent.getAttributes() ) ); + nodes.addAll( transpiler.transformAnnotations( boxComponent.getAttributes(), true, false ) ); // Component.ComponentBody nodes.addAll( generateBodyNodes( boxComponent.getBody() ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index 09991d0fa..fe68e654c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -68,9 +68,10 @@ public List transform( BoxNode node, TransformerContext contex LabelNode loopStart = new LabelNode(); LabelNode loopEnd = new LabelNode(); + LabelNode breakTarget = new LabelNode(); tracker.setContinue( forIn, loopStart ); - tracker.setBreak( forIn, loopEnd ); + tracker.setBreak( forIn, breakTarget ); if ( forIn.getLabel() != null ) { tracker.setStringLabel( forIn.getLabel(), forIn ); } @@ -165,6 +166,13 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( forIn.getBody(), context, returnValueContext ) ); nodes.add( new JumpInsnNode( Opcodes.GOTO, loopStart ) ); + + nodes.add( breakTarget ); + // every iteration we will swap the values and pop in order to remove the older value + if ( returnValueContext == ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.SWAP ) ); + nodes.add( new InsnNode( Opcodes.POP ) ); + } // increment query loop nodes.add( loopEnd ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 63615cbf5..46e56deff 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -21,6 +21,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; @@ -32,6 +33,7 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.statement.BoxAccessModifier; import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxMethodDeclarationModifier; import ortus.boxlang.compiler.ast.statement.BoxReturnType; import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.compiler.parser.BoxSourceType; @@ -78,6 +80,14 @@ public List transform( BoxNode node, TransformerContext contex } ); transpiler.setAuxiliary( type.getClassName(), classNode ); + AsmHelper.addStaticFieldGetter( classNode, + type, + "modifiers", + "getModifiers", + Type.getType( List.class ), + null + ); + AsmHelper.addStaticFieldGetter( classNode, type, "name", @@ -186,6 +196,31 @@ public List transform( BoxNode node, TransformerContext contex type.getInternalName(), "documentation", Type.getDescriptor( IStruct.class ) ); + + AsmHelper.array( + Type.getType( BoxMethodDeclarationModifier.class ), + function.getModifiers(), + ( bmdm, i ) -> List.of( + new FieldInsnNode( + Opcodes.GETSTATIC, + Type.getInternalName( BoxMethodDeclarationModifier.class ), + bmdm.toString().toUpperCase(), + Type.getDescriptor( BoxMethodDeclarationModifier.class ) + ) + ) + ).stream() + .forEach( modifierNode -> modifierNode.accept( methodVisitor ) ); + + methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, + Type.getInternalName( List.class ), + "of", + Type.getMethodDescriptor( Type.getType( List.class ), Type.getType( Object[].class ) ), + true ); + + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, + type.getInternalName(), + "modifiers", + Type.getDescriptor( List.class ) ); } ); List nodes = new ArrayList(); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index 8cc21494a..9082dee2e 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -14,19 +14,29 @@ */ package ortus.boxlang.compiler.asmboxpiler.transformer.statement; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; + import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxInterface; +import ortus.boxlang.compiler.ast.BoxStaticInitializer; import ortus.boxlang.compiler.ast.Source; import ortus.boxlang.compiler.ast.SourceFile; import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; @@ -41,22 +51,17 @@ import ortus.boxlang.runtime.scopes.StaticScope; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.types.exceptions.ExpressionException; import ortus.boxlang.runtime.util.ResolvedFilePath; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - public class BoxInterfaceTransformer { public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterface ) throws BoxRuntimeException { - Source source = boxInterface.getPosition().getSource(); - String packageName = transpiler.getProperty( "packageName" ); - String boxPackageName = transpiler.getProperty( "boxPackageName" ); + Source source = boxInterface.getPosition().getSource(); + String packageName = transpiler.getProperty( "packageName" ); + String boxPackageName = transpiler.getProperty( "boxFQN" ); + transpiler.setProperty( "boxClassName", boxPackageName ); String classname = transpiler.getProperty( "classname" ); String mappingName = transpiler.getProperty( "mappingName" ); String mappingPath = transpiler.getProperty( "mappingPath" ); @@ -234,7 +239,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf type.getInternalName(), "_supers", Type.getDescriptor( List.class ) ); - addSuper.visitVarInsn( Opcodes.ALOAD, 0 ); + addSuper.visitVarInsn( Opcodes.ALOAD, 1 ); addSuper.visitMethodInsn( Opcodes.INVOKEINTERFACE, Type.getInternalName( List.class ), "add", @@ -344,14 +349,40 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf return 0; } ) - .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) + .flatMap( statement -> { + + if ( ! ( statement instanceof BoxFunctionDeclaration ) + && ! ( statement instanceof BoxImport ) + && ! ( statement instanceof BoxStaticInitializer ) ) { + throw new ExpressionException( + "Statement type not supported in an interface: " + statement.getClass().getSimpleName(), + statement + ); + } + + if ( statement instanceof BoxFunctionDeclaration bfd && bfd.getBody() == null ) { + return new ArrayList().stream(); + } + + return transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream(); + } ) .toList(); } ); AsmHelper.methodWithContextAndClassLocator( classNode, "staticInitializer", Type.getType( IBoxContext.class ), Type.VOID_TYPE, true, transpiler, false, () -> { - List staticNodes = ( List ) transpiler.getBoxStaticInitializers() + List staticNodes = new ArrayList(); + + boxInterface.getDescendantsOfType( BoxFunctionDeclaration.class, ( expr ) -> { + BoxFunctionDeclaration func = ( BoxFunctionDeclaration ) expr; + + return func.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ); + } ).forEach( func -> { + staticNodes.addAll( transpiler.transform( func, TransformerContext.NONE ) ); + } ); + + staticNodes.addAll( transpiler.getBoxStaticInitializers() .stream() .map( ( staticInitializer ) -> { if ( staticInitializer == null || staticInitializer.getBody().size() == 0 ) { @@ -365,15 +396,8 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf .collect( Collectors.toList() ); } ) .flatMap( s -> s.stream() ) - .collect( Collectors.toList() ); - - boxInterface.getDescendantsOfType( BoxFunctionDeclaration.class, ( expr ) -> { - BoxFunctionDeclaration func = ( BoxFunctionDeclaration ) expr; - - return func.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ); - } ).forEach( func -> { - staticNodes.addAll( transpiler.transform( func, TransformerContext.NONE ) ); - } ); + .collect( Collectors.toList() ) + ); return staticNodes; } @@ -395,10 +419,12 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf "sourceType", Type.getDescriptor( BoxSourceType.class ) ); - List annotations = transpiler.transformAnnotations( boxInterface.getAnnotations() ); + List annotations = transpiler.transformAnnotations( boxInterface.getAllAnnotations() ); List documenation = transpiler.transformDocumentation( boxInterface.getDocumentation() ); List name = transpiler.createKey( boxInterfacename ); + List abstractMethods = AsmHelper.generateMapOfAbstractMethodNames( transpiler, boxInterface ); + methodVisitor.visitLdcInsn( transpiler.getKeys().size() ); methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Key.class ) ); int index = 0; @@ -475,6 +501,8 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf false ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "_supers", Type.getDescriptor( List.class ) ); + abstractMethods.forEach( node -> node.accept( methodVisitor ) ); + methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), "abstractMethods", Type.getDescriptor( Map.class ) ); } ); return classNode; diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java index a8d73352d..47a389e5c 100644 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -406,9 +406,9 @@ public void testlegacyMeta() { var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); var meta = cfc.getMetaData(); - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClass" ); + assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClass" ); assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); - assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClass" ); + assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClass" ); assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClass.bx" ) ).isTrue(); // assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); assertThat( meta.get( Key.of( "properties" ) ) ).isInstanceOf( Array.class ); @@ -435,9 +435,9 @@ public void testlegacyMetaCF() { var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); var meta = cfc.getMetaData(); - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClassCF" ); + assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); - assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClassCF" ); + assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClassCF.cfc" ) ).isTrue(); // assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); assertThat( meta.get( Key.of( "properties" ) ) ).isInstanceOf( Array.class ); @@ -490,7 +490,7 @@ public void testBoxMeta() { var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); var meta = boxMeta.meta; assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); - assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.testcases.phase3.MyClass" ); + assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClass" ); assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClass.bx" ) ).isTrue(); assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); assertThat( meta.get( Key.of( "properties" ) ) instanceof Array ).isTrue(); @@ -553,7 +553,7 @@ public void testProperties() { var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); var meta = boxMeta.meta; - assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 6 ); + assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 7 ); var prop1 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); assertThat( prop1.get( "name" ) ).isEqualTo( "myProperty" ); @@ -631,7 +631,7 @@ public void testPropertiesCF() { var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); var meta = boxMeta.meta; - assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 4 ); + assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 5 ); var prop1 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); assertThat( prop1.get( "name" ) ).isEqualTo( "myProperty" ); @@ -808,11 +808,11 @@ public void testCanExtend() { // Then the concrete class inits. getCurrentTemplate() shows the concrete class. "Chihuahua init Chihuahua.cfc", // A method inherited from a base class, sees "this" as the concrete class. - "animal this is: src.test.java.testcases.phase3.Chihuahua", + "animal this is: src.test.java.TestCases.phase3.Chihuahua", // A method inherited from a base class, sees the top level "variables" scope. "animal sees inDog as: true", // A method delegated to as super.foo() sees "this" as the concrete class. - "super animal sees: src.test.java.testcases.phase3.Chihuahua", + "super animal sees: src.test.java.TestCases.phase3.Chihuahua", // A method delegated to as super.foo() sees the top level "variables" scope. "super sees inDog as: true", } ); @@ -821,7 +821,7 @@ public void testCanExtend() { var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); var meta = boxMeta.meta; - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.Chihuahua" ); + assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.Chihuahua" ); IStruct extendsMeta = meta.getAsStruct( Key.of( "extends" ) ); assertThat( extendsMeta.getAsString( Key.of( "name" ) ).endsWith( ".Dog" ) ).isTrue(); From b38f248ccc208a28cf6185265d9918dc8d09c3b1 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 17 Oct 2024 21:28:21 -0500 Subject: [PATCH 63/71] BL-654 final commit after working on ASM --- .../java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index 2e4e5aefd..b4b13e98e 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -25,7 +25,6 @@ public class ASMBoxpiler extends Boxpiler { public static final boolean DEBUG = Boolean.getBoolean( "asmboxpiler.debug" ); - // public static final boolean DEBUG = true; /** * -------------------------------------------------------------------------- From caa0045f62d3a67417ac1f51f6b64e8ff7d088c0 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 11:29:31 +0200 Subject: [PATCH 64/71] docs --- .../ortus/boxlang/runtime/BoxRuntime.java | 47 +++++++++++-------- .../runtime/bifs/global/decision/IsEmpty.java | 9 ++-- src/main/resources/config/boxlang.json | 3 ++ 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index 2ba8b0434..43f41a3c9 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -225,7 +225,11 @@ public class BoxRuntime implements java.io.Closeable { private ModuleService moduleService; /** - * The JavaBoxPiler instance + * The BoxPiler implementation the runtime will use. At this time we offer two choices: + * 1. JavaBoxpiler - Generates Java source code and compiles it via the JDK + * 2. ASMBoxpiler - Generates bytecode directly via ASM + * However, developers can create their own Boxpiler implementations and register them with the runtime + * via configuration. */ private IBoxpiler boxpiler; @@ -447,17 +451,19 @@ private void startup() { // Load the configurations and overrides loadConfiguration( this.debugMode, this.configPath ); + // Anythying below might use configuration items // Ensure home assets ensureHomeAssets(); // Load the Dynamic Class Loader for the runtime - this.runtimeLoader = new DynamicClassLoader( + this.runtimeLoader = new DynamicClassLoader( Key.runtime, getConfiguration().getJavaLibraryPaths(), this.getClass().getClassLoader() ); - + // Startup the right Compiler + this.boxpiler = chooseBoxpiler(); // Seed Mathematical Precision for the runtime MathUtil.setHighPrecisionMath( getConfiguration().useHighPrecisionMath ); @@ -469,9 +475,7 @@ private void startup() { this.applicationService.onStartup(); // Create our runtime context that will be the granddaddy of all contexts that execute inside this runtime - this.runtimeContext = new RuntimeBoxContext(); - this.boxpiler = chooseBoxpiler(); - + this.runtimeContext = new RuntimeBoxContext(); // Now startup the modules so we can have a runtime context available to them this.moduleService.onStartup(); // Now the cache service can be started, this allows for modules to register caches @@ -510,18 +514,6 @@ private void startup() { * The entry point into the runtime */ - private IBoxpiler chooseBoxpiler() { - switch ( ( String ) this.configuration.experimental.getOrDefault( "compiler", "java" ) ) { - case "asm" : - useASMBoxPiler(); - return ASMBoxpiler.getInstance(); - case "java" : - default : - useJavaBoxpiler(); - return JavaBoxpiler.getInstance(); - } - } - /** * Get or startup a BoxLang Runtime instance. *

@@ -987,7 +979,6 @@ public Key[] getGlobalServiceKeys() { /** * Switch the runtime to generate java source and compile via the JDK - * */ public void useJavaBoxpiler() { RunnableLoader.getInstance().selectBoxPiler( JavaBoxpiler.class ); @@ -995,7 +986,6 @@ public void useJavaBoxpiler() { /** * Switch the runtime to generate bytecode directly via ASM - * */ public void useASMBoxPiler() { RunnableLoader.getInstance().selectBoxPiler( ASMBoxpiler.class ); @@ -1556,4 +1546,21 @@ private IBoxContext ensureRequestTypeContext( IBoxContext context, URI template } } + /** + * Choose the Boxpiler implementation to use according to the configuration + * + * @return The Boxpiler implementation to use + */ + private IBoxpiler chooseBoxpiler() { + switch ( ( String ) this.configuration.experimental.getOrDefault( "compiler", "java" ) ) { + case "asm" : + useASMBoxPiler(); + return ASMBoxpiler.getInstance(); + case "java" : + default : + useJavaBoxpiler(); + return JavaBoxpiler.getInstance(); + } + } + } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java b/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java index b61670a2f..087fa8b43 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java @@ -51,12 +51,15 @@ public IsEmpty() { } /** - * Determine whether a given value is empty + * Determine whether a given value is empty. We check for emptiness of + * anything that can be casted to: Array, Struct, Query, or String. * * @param context The context in which the BIF is being invoked. * @param arguments Argument scope for the BIF. * - * @argument.value The value to test for emptiness. + * @argument.value The value/object to check for emptiness. + * + * @return True if the value is empty, false otherwise. */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { Object object = arguments.get( Key.value ); @@ -82,4 +85,4 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { return false; } -} \ No newline at end of file +} diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index f2756db0a..652dcc41d 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -75,6 +75,9 @@ // This is the experimental features flags. // Please see the documentation to see which flags are available "experimental": { + // This choose the compiler to use for the runtime + // Valid values are: "java", "asm" + "compiler": "java", // If enabled, it will generate AST JSON data under the project's /grapher/data folder "ASTCapture": false }, From 80237c74923657e978a16e5d03ab65740f2df1e9 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 12:17:44 +0200 Subject: [PATCH 65/71] BL-666 #resolve Rename default sessions cache to `bxSessions` to create the `bx` prefix standards. Add configuration to boxlang.json --- .../runtime/application/Application.java | 51 +++++++------- .../boxlang/runtime/application/Session.java | 3 +- .../runtime/bifs/global/decision/IsEmpty.java | 2 +- .../ortus/boxlang/runtime/scopes/Key.java | 2 +- src/main/resources/config/boxlang.json | 69 ++++++++++++++++++- 5 files changed, 94 insertions(+), 33 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Application.java b/src/main/java/ortus/boxlang/runtime/application/Application.java index e09d90601..4173de237 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Application.java +++ b/src/main/java/ortus/boxlang/runtime/application/Application.java @@ -37,7 +37,6 @@ import ortus.boxlang.runtime.context.RequestBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; -import ortus.boxlang.runtime.dynamic.casters.CastAttempt; import ortus.boxlang.runtime.dynamic.casters.LongCaster; import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.events.BoxEvent; @@ -121,17 +120,14 @@ public class Application { // Key.TOD, 2147483647 is the largest integer allowed by Java but the ConcurrentStore will allocate 2147483647/4 as the initial size of the Concurent // map and will result in OOM errors Key.maxObjects, 100000, - // Minutes Key.defaultLastAccessTimeout, 3600, - // Minutes Key.defaultTimeout, 3600, Key.objectStore, "ConcurrentStore", - // Seconds Key.reapFrequency, 120, Key.resetTimeoutOnAccess, true, - Key.useLastAccessTimeouts, true + Key.useLastAccessTimeouts, false ); - private static final Key DEFAULT_SESSION_CACHEKEY = Key.boxlangSessions; + private static final Key DEFAULT_SESSION_CACHEKEY = Key.bxSessions; /** * An application can have a collection of class loaders that it can track and manage. @@ -248,7 +244,9 @@ public BaseApplicationListener getStartingListener() { /** * Start the application if not already started * - * @param context The context + * @param context The context starting up the application + * + * @return The started application */ public Application start( IBoxContext context ) { // Apps started, just return @@ -301,31 +299,30 @@ public Application start( IBoxContext context ) { * @throws BoxRuntimeException If the session storage cache is not a string */ private void startupSessionStorage( ApplicationBoxContext appContext ) { - IStruct settings = this.startingListener.getSettings(); - CastAttempt directiveAttempt = StringCaster.attempt( settings.get( Key.sessionStorage ) ); - String sessionStorage = SESSION_STORAGE_MEMORY; - - // Let's be nice and tell them what they put is not good if not a string. - if ( directiveAttempt.ifFailed() ) { - throw new BoxRuntimeException( "Session storage directive must be a string that matches a registered cache" ); - } else { - sessionStorage = directiveAttempt.get().trim(); - } - - // If empty, default it - if ( sessionStorage.isEmpty() ) { - sessionStorage = SESSION_STORAGE_MEMORY; - } - - // Get the cache name according to the storage directive or default to memory - Key sessionCacheName = sessionStorage.equals( SESSION_STORAGE_MEMORY ) + // @formatter:off + IStruct settings = this.startingListener.getSettings(); + String sessionStorageName = StringCaster + .attempt( settings.get( Key.sessionStorage ) ) + // If not a string, advice the user + .ifEmpty( () -> { + throw new BoxRuntimeException( "Session storage directive must be a string that matches a registered cache" ); + } ) + // If present, make sure it has a value or default it + .map( ( String setting ) -> setting.trim().isEmpty() ? SESSION_STORAGE_MEMORY : setting.trim() ) + // Return the right value or the default name + .getOrDefault( SESSION_STORAGE_MEMORY ); + // @formatter:on + + // Now we can get the right cache name to use + Key sessionCacheName = sessionStorageName.equals( SESSION_STORAGE_MEMORY ) ? DEFAULT_SESSION_CACHEKEY - : Key.of( sessionStorage ); + : Key.of( sessionStorageName ); // Create the memory cache if not already created + // This is a stop-gap, it should really never run. But if it does log it so we can fix it if ( sessionCacheName.equals( DEFAULT_SESSION_CACHEKEY ) && !cacheService.hasCache( DEFAULT_SESSION_CACHEKEY ) ) { - logger.debug( "Creating default session memory cache as it doesn't exist" ); + logger.warn( "Creating default session memory cache as it doesn't exist" ); cacheService.createCache( DEFAULT_SESSION_CACHEKEY, diff --git a/src/main/java/ortus/boxlang/runtime/application/Session.java b/src/main/java/ortus/boxlang/runtime/application/Session.java index 651e33750..f9863afc9 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Session.java +++ b/src/main/java/ortus/boxlang/runtime/application/Session.java @@ -40,8 +40,9 @@ public class Session implements Serializable { /** * The concatenator for session IDs + * We leverage the `:` as it's a standard in many distributed caches like Redis and Couchbase to denote namespaces */ - public static final String ID_CONCATENATOR = "_"; + public static final String ID_CONCATENATOR = ":"; /** * -------------------------------------------------------------------------- diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java b/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java index 087fa8b43..4cb84c9a7 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsEmpty.java @@ -51,7 +51,7 @@ public IsEmpty() { } /** - * Determine whether a given value is empty. We check for emptiness of + * Determine whether a given value is empty. We check for emptiness of * anything that can be casted to: Array, Struct, Query, or String. * * @param context The context in which the BIF is being invoked. diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index cef36f56d..b6aff2b05 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -123,7 +123,7 @@ public class Key implements Comparable, Serializable { public static final Key boxBif = Key.of( "BoxBif" ); public static final Key boxCacheProvider = Key.of( "BoxCacheProvider" ); public static final Key boxlang = Key.of( "boxlang" ); - public static final Key boxlangSessions = Key.of( "boxlangSessions" ); + public static final Key bxSessions = Key.of( "bxSessions" ); public static final Key boxMember = Key.of( "BoxMember" ); public static final Key boxRuntime = Key.of( "boxRuntime" ); public static final Key buffersize = Key.of( "buffersize" ); diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 652dcc41d..f84a0dfad 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -117,8 +117,39 @@ "defaultRemoteMethodReturnFormat": "json", // The configuration for the BoxLang `default` cache. If empty, we use the defaults // See the ortus.boxlang.runtime.config.segments.CacheConfig for all the available settings - // This is used by query caching, template caching, and other internal caching, unless you override it - "defaultCache": {}, + // This is used by query caching, template caching, and other internal caching. + // You can use the cache() BIF in order to get access to the default cache. + "defaultCache": { + // How many to evict at a time once a policy is triggered + "evictCount": 1, + // The eviction policy to use: Least Recently Used + // Other policies are: LRU, LFU, FIFO, LIFO, RANDOM + "evictionPolicy": "LRU", + // The free memory percentage threshold to trigger eviction + // 0 = disabled, 1-100 = percentage of available free memory in heap + // If the threadhold is reached, the eviction policy is triggered + "freeMemoryPercentageThreshold": 0, + // The maximum number of objects to store in the cache + "maxObjects": 1000, + // The maximum in seconds to keep an object in the cache since it's last access + // So if an object is not accessed in this time or greater, it will be removed from the cache + "defaultLastAccessTimeout": 1800, + // The maximum time in seconds to keep an object in the cache regardless if it's used or not + // A default timeout of 0 = never expire, careful with this setting + "defaultTimeout": 3600, + // The object store to use to store the objects. + // The default is a ConcurrentStore which is a memory sensitive store + "objectStore": "ConcurrentStore", + // The frequency in seconds to check for expired objects and expire them using the policy + // This creates a BoxLang task that runs every X seconds to check for expired objects + "reapFrequency": 120, + // If enabled, the last access timeout will be reset on every access + // This means that the last access timeout will be reset to the defaultLastAccessTimeout on every access + // Usually for session caches or to simulate a session + "resetTimeoutOnAccess": false, + // If enabled, the last access timeout will be used to evict objects from the cache + "useLastAccessTimeouts": true + }, /** * Register any named caches here. * The key is the name of the cache and the value is the cache configuration. @@ -127,7 +158,39 @@ * The `properties` property is optional and is a struct of properties that are specific to the cache provider. */ "caches": { - "imports": { + // This is the holder of all sessions in a BoxLang runtime. + // The keys are prefixed by application to create separation. + "bxSessions": { + "provider": "BoxCacheProvider", + "properties": { + // How many objects to evict when the cache is full + "evictCount": 1, + // The eviction policy to use: FIFO, LFU, LIFO, LRU, MFU, MRU, Random + "evictionPolicy": "LRU", + // The maximum number of objects the cache can hold + "maxObjects": 100000, + // How long should sessions last for in seconds. Default is 60 minutes. + "defaultTimeout": 3600, + // The object store to use to store the objects. + // The default is a ConcurrentStore which is a thread safe and fast storage. + // Available Stores are: BlackHoleStore, ConcurrentSoftReferenceStore, ConcurrentStore, FileSystemStore, Your own. + "objectStore": "ConcurrentStore", + // The free memory percentage threshold to start evicting objects + // Only use if memory is constricted and you need to relieve cache pressure + // Please note that this only makes sense depending on which object store you use. + "freeMemoryPercentageThreshold": 0, + // The frequency in seconds to check for expired objects and expire them using the policy + // This creates a BoxLang task that runs every X seconds to check for expired objects + // Default is every 2 minutes + "reapFrequency": 120, + // This makes a session extend it's life when accessed. So if a users uses anything or puts anything + // In session, it will re-issue the timeout. + "resetTimeoutOnAccess": true, + // Sessions don't rely on the last access timeouts but on the default timeout only. + "useLastAccessTimeouts": false + } + }, + "bxImports": { "provider": "BoxCacheProvider", "properties": { "evictCount": 1, From 12fbd7161dee773734716dc793747f6fdc552e16 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 12:32:29 +0200 Subject: [PATCH 66/71] fixing cache tests --- .../ortus/boxlang/runtime/bifs/global/cache/CacheTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/cache/CacheTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/cache/CacheTest.java index 04f73f219..5cb38a8c6 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/cache/CacheTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/cache/CacheTest.java @@ -46,13 +46,13 @@ public void canGetDefaultCache() { public void canGetNamedCache() { runtime.executeSource( """ - result = cache( "imports" ) + result = cache( "bxSessions" ) """, context ); ICacheProvider cache = ( ICacheProvider ) variables.get( result ); assertNotNull( cache ); - assertThat( cache.getName().getName() ).isEqualTo( "imports" ); + assertThat( cache.getName().getName() ).isEqualTo( "bxSessions" ); } } From 6521a14860f1d5f221b73f1182db24f356e745e9 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 12:33:38 +0200 Subject: [PATCH 67/71] more test fixes --- .../java/ortus/boxlang/runtime/config/ConfigLoaderTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java b/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java index b3756f950..210955900 100644 --- a/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java +++ b/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java @@ -78,8 +78,7 @@ void testItCanLoadTheCoreConfig() { assertThat( defaultCache.properties.get( "useLastAccessTimeouts" ) ).isEqualTo( true ); // Import Cache Checks - CacheConfig importCache = ( CacheConfig ) config.caches.get( "imports" ); - assertThat( importCache.name.getNameNoCase() ).isEqualTo( "IMPORTS" ); + CacheConfig importCache = ( CacheConfig ) config.caches.get( "bxImports" ); assertThat( importCache.provider.getNameNoCase() ).isEqualTo( "BOXCACHEPROVIDER" ); assertThat( importCache.properties ).isNotNull(); assertThat( importCache.properties.get( "maxObjects" ) ).isEqualTo( 200 ); @@ -190,8 +189,7 @@ private void assertConfigTest( Configuration config ) { assertThat( defaultCache.properties.get( "useLastAccessTimeouts" ) ).isEqualTo( true ); // Import Cache Checks - CacheConfig importCache = ( CacheConfig ) config.caches.get( "imports" ); - assertThat( importCache.name.getNameNoCase() ).isEqualTo( "IMPORTS" ); + CacheConfig importCache = ( CacheConfig ) config.caches.get( "bxImports" ); assertThat( importCache.provider.getNameNoCase() ).isEqualTo( "BOXCACHEPROVIDER" ); assertThat( importCache.properties ).isNotNull(); assertThat( importCache.properties.get( "maxObjects" ) ).isEqualTo( 200 ); From a2292b918830a24d4013b2382611bb9cbffbe59e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 12:35:19 +0200 Subject: [PATCH 68/71] more test fixes --- src/test/resources/test-boxlang.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/test-boxlang.json b/src/test/resources/test-boxlang.json index 66d3cbbdd..ab24b65a9 100644 --- a/src/test/resources/test-boxlang.json +++ b/src/test/resources/test-boxlang.json @@ -23,7 +23,7 @@ }, "caches": { "default": {}, - "imports": { + "bxImports": { "provider": "BoxCacheProvider", "properties": { "evictCount": 1, From bb461819b2f2bf2642c624dac42056db0e396b06 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 18 Oct 2024 12:53:03 +0200 Subject: [PATCH 69/71] updated old test --- src/test/java/TestCases/phase3/ApplicationTest.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/test/java/TestCases/phase3/ApplicationTest.java b/src/test/java/TestCases/phase3/ApplicationTest.java index 6e1cd9abd..8aa47c085 100644 --- a/src/test/java/TestCases/phase3/ApplicationTest.java +++ b/src/test/java/TestCases/phase3/ApplicationTest.java @@ -20,8 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import java.nio.file.Path; -import java.time.Instant; -import java.time.temporal.ChronoUnit; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -37,7 +35,6 @@ import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.RequestBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.dynamic.casters.DateTimeCaster; import ortus.boxlang.runtime.scopes.ApplicationScope; import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; @@ -85,11 +82,8 @@ public void testBasicApplication() { assertThat( variables.get( result ) ).isInstanceOf( ApplicationScope.class ); assertThat( variables.get( Key.of( "result2" ) ) ).isInstanceOf( SessionScope.class ); - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); - Application app = appContext.getApplication(); - Instant actual = DateTimeCaster.cast( variables.get( Key.of( "startTime" ) ) ).getWrapped().toInstant(); - Instant now = Instant.now(); - long differenceInSeconds = ChronoUnit.SECONDS.between( actual, now ); + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); assertThat( app.getName().getName() ).isEqualTo( "myAppsdfsdf" ); assertThat( app.getSessionsCache() ).isNotNull(); @@ -97,7 +91,6 @@ public void testBasicApplication() { assertThat( app.getApplicationScope().getName().getName() ).isEqualTo( "application" ); assertThat( app.getClassLoaders() ).isNotNull(); assertThat( app.hasStarted() ).isTrue(); - assertThat( differenceInSeconds ).isAtMost( 1L ); } @Test From aa0e469191544693d81189b6f59cccc8c6c386dc Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 18 Oct 2024 07:59:37 -0500 Subject: [PATCH 70/71] BL-654 Get tests passing --- .../transformer/statement/BoxInterfaceTransformer.java | 2 +- src/test/java/TestCases/asm/phase3/ClassTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index 9082dee2e..c3b768283 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -69,7 +69,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; // trim leading . if exists - String boxInterfacename = boxPackageName + ( boxPackageName.isEmpty() ? "" : "." ) + fileName.replace( ".bx", "" ).replace( ".cfc", "" ); + String boxInterfacename = transpiler.getProperty( "boxFQN" ); String sourceType = transpiler.getProperty( "sourceType" ); Type type = Type.getType( "L" + packageName.replace( '.', '/' ) diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java index 47a389e5c..331e77a3c 100644 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -967,7 +967,7 @@ public void testInlineJavaExtends() { instance.executeSource( """ import java.util.Timer; - myTask = new src.test.java.TestCases.phase3.JavaExtends(); + myTask = new src.test.java.TestCases.asm.phase3.JavaExtends(); assert myTask instanceof "java.util.TimerTask" jtimer = new Timer(); From 5d09a9b315f333fccf389a08a7ee1f40fe872638 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 18 Oct 2024 08:39:56 -0500 Subject: [PATCH 71/71] BL-654 Get tests passing --- src/test/java/TestCases/asm/phase3/ClassTest.java | 13 ++++++++----- .../phase3/{JavaExtends.bx => JavaExtendsAsm.bx} | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) rename src/test/java/TestCases/asm/phase3/{JavaExtends.bx => JavaExtendsAsm.bx} (98%) diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java index 331e77a3c..304ebb4b6 100644 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -962,17 +963,19 @@ public void testInlineJavaImplements() { } + // disabling this as it causes a crazy concurrency issue with another test @Test - public void testInlineJavaExtends() { + @Disabled + public void testInlineJavaExtendsASM() { instance.executeSource( """ import java.util.Timer; - myTask = new src.test.java.TestCases.asm.phase3.JavaExtends(); - assert myTask instanceof "java.util.TimerTask" + myASMTask = new src.test.java.TestCases.asm.phase3.JavaExtendsAsm(); + assert myASMTask instanceof "java.util.TimerTask" jtimer = new Timer(); - jtimer.schedule(myTask, 1000); - myTask.cancel() + jtimer.schedule(myASMTask, 1000); + myASMTask.cancel() """, context ); } diff --git a/src/test/java/TestCases/asm/phase3/JavaExtends.bx b/src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx similarity index 98% rename from src/test/java/TestCases/asm/phase3/JavaExtends.bx rename to src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx index 4baaea6cc..09ca312d3 100644 --- a/src/test/java/TestCases/asm/phase3/JavaExtends.bx +++ b/src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx @@ -1,5 +1,7 @@ class extends="java:java.util.TimerTask" { + + @overrideJava void function run() { println("Hello from a custom TimerTask!" );