├── .editorconfig ├── .github └── workflows │ ├── build.yml │ └── publish-snapshot.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── buildSrc ├── build.gradle └── src │ └── main │ └── java │ └── GenerateParserTask.java ├── chasm ├── .settings │ ├── org.eclipse.jdt.core.prefs │ └── org.eclipse.jdt.ui.prefs ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── quiltmc │ │ └── chasm │ │ ├── api │ │ ├── ChasmProcessor.java │ │ ├── ClassResult.java │ │ ├── Lock.java │ │ ├── Transformation.java │ │ ├── Transformer.java │ │ ├── target │ │ │ ├── NodeTarget.java │ │ │ ├── SliceTarget.java │ │ │ └── Target.java │ │ └── util │ │ │ ├── ClassInfo.java │ │ │ └── Context.java │ │ └── internal │ │ ├── ChasmContext.java │ │ ├── ClassData.java │ │ ├── TransformationApplier.java │ │ ├── TransformationSorter.java │ │ ├── TransformerSorter.java │ │ ├── asm │ │ ├── ChasmClassWriter.java │ │ ├── package-info.java │ │ └── visitor │ │ │ ├── ChasmAnnotationVisitor.java │ │ │ ├── ChasmClassVisitor.java │ │ │ ├── ChasmFieldVisitor.java │ │ │ ├── ChasmMethodVisitor.java │ │ │ ├── ChasmModuleVisitor.java │ │ │ ├── ChasmRecordComponentVisitor.java │ │ │ ├── LocalInterpreter.java │ │ │ ├── LocalValue.java │ │ │ └── package-info.java │ │ ├── intrinsic │ │ ├── ChasmIntrinsics.java │ │ ├── FileBytesIntrinsic.java │ │ ├── FileContentIntrinsic.java │ │ └── IncludeIntrinsic.java │ │ ├── metadata │ │ ├── OriginMetadata.java │ │ ├── PathMetadata.java │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── transformer │ │ ├── ChasmLangTransformation.java │ │ ├── ChasmLangTransformer.java │ │ └── package-info.java │ │ ├── tree │ │ ├── ClassNode.java │ │ ├── LazyMap.java │ │ ├── package-info.java │ │ └── reader │ │ │ ├── AnnotationNodeReader.java │ │ │ ├── ClassNodeReader.java │ │ │ ├── FieldNodeReader.java │ │ │ ├── MethodNodeReader.java │ │ │ ├── ModuleNodeReader.java │ │ │ ├── RecordComponentNodeReader.java │ │ │ └── package-info.java │ │ └── util │ │ ├── ChasmEnvironment.java │ │ ├── NodeConstants.java │ │ ├── NodeUtils.java │ │ ├── PathInitializer.java │ │ └── package-info.java │ ├── test │ └── java │ │ └── org │ │ └── quiltmc │ │ └── chasm │ │ ├── IntrinsicsTest.java │ │ ├── TestsBase.java │ │ ├── TransformedTests.java │ │ ├── TransformerSorterTests.java │ │ └── UnchangedTests.java │ └── testData │ ├── java │ ├── empty │ │ ├── EmptyAnnotation.java │ │ ├── EmptyClass.java │ │ ├── EmptyEnum.java │ │ ├── EmptyInterface.java │ │ ├── EmptyOuterClass.java │ │ ├── EmptyRecord.java │ │ ├── EmptySealedClass.java │ │ └── EmptySealedExtendsClass.java │ └── other │ │ ├── ExampleAnnotation.java │ │ ├── ExampleClass.java │ │ ├── ExampleEnum.java │ │ ├── SimpleAnnotation.java │ │ ├── TestLocalVariables.java │ │ └── TestMergeInsns.java │ ├── results │ ├── .gitattributes │ ├── add │ │ ├── field_to_empty.result │ │ ├── fields_to_empty.result │ │ └── method_to_empty.result │ ├── other │ │ ├── test_local_variables.result │ │ └── test_merge_insns.result │ └── unchanged │ │ ├── EmptyAnnotation.result │ │ ├── EmptyClass.result │ │ ├── EmptyEnum.result │ │ ├── EmptyInterface.result │ │ ├── EmptyOuterClass$EmptyInnerClass.result │ │ ├── EmptyOuterClass$EmptyStaticNestedClass.result │ │ ├── EmptyOuterClass.result │ │ ├── EmptyRecord.result │ │ ├── EmptySealedClass.result │ │ ├── EmptySealedExtendsClass.result │ │ ├── ExampleAnnotation.result │ │ ├── ExampleClass$ExampleRecord.result │ │ ├── ExampleClass.result │ │ └── ExampleEnum.result │ └── transformers │ ├── add_field.chasm │ ├── add_field_2.chasm │ ├── add_method.chasm │ ├── test_local_variables.chasm │ ├── test_merge_insns.chasm │ └── touch.chasm ├── chassembly ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── quiltmc │ │ └── chasm │ │ └── lang │ │ ├── api │ │ ├── ast │ │ │ ├── Ast.java │ │ │ ├── BinaryNode.java │ │ │ ├── BooleanNode.java │ │ │ ├── CallNode.java │ │ │ ├── FloatNode.java │ │ │ ├── IndexNode.java │ │ │ ├── IntegerNode.java │ │ │ ├── LambdaNode.java │ │ │ ├── ListNode.java │ │ │ ├── MapNode.java │ │ │ ├── MemberNode.java │ │ │ ├── Node.java │ │ │ ├── NullNode.java │ │ │ ├── ReferenceNode.java │ │ │ ├── StringNode.java │ │ │ ├── TernaryNode.java │ │ │ ├── UnaryNode.java │ │ │ └── ValueNode.java │ │ ├── eval │ │ │ ├── ClosureNode.java │ │ │ ├── Evaluator.java │ │ │ ├── FunctionNode.java │ │ │ ├── IntrinsicFunction.java │ │ │ └── Resolver.java │ │ ├── exception │ │ │ ├── EvaluationException.java │ │ │ └── ParseException.java │ │ └── metadata │ │ │ └── Metadata.java │ │ └── internal │ │ ├── Assert.java │ │ ├── eval │ │ ├── EvaluatorImpl.java │ │ ├── LambdaReference.java │ │ ├── NodeReference.java │ │ ├── Reference.java │ │ ├── ResolverImpl.java │ │ └── package-info.java │ │ ├── intrinsics │ │ ├── BuiltInIntrinsics.java │ │ ├── CharsFunction.java │ │ ├── EntriesFunction.java │ │ ├── FlattenFunction.java │ │ ├── FromEntriesFunction.java │ │ ├── JoinFunction.java │ │ ├── LenFunction.java │ │ ├── MapFunction.java │ │ ├── ReduceFunction.java │ │ ├── SplitFloatFunction.java │ │ ├── ToFloatFunction.java │ │ ├── ToIntegerFunction.java │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── parse │ │ ├── Parser.jj │ │ ├── SourceSpan.java │ │ └── package-info.java │ │ └── render │ │ ├── RenderUtil.java │ │ ├── Renderer.java │ │ └── package-info.java │ └── test │ ├── java │ └── org │ │ └── quiltmc │ │ └── chasm │ │ └── lang │ │ ├── TestBase.java │ │ ├── TestEvaluation.java │ │ └── TestRendering.java │ └── resources │ ├── results │ ├── .gitattributes │ ├── complex │ │ ├── basic.chasm │ │ └── brainfuck.chasm │ ├── intrinsics │ │ ├── chars_join.chasm │ │ ├── conversion.chasm │ │ ├── entries.chasm │ │ ├── flatten.chasm │ │ ├── len.chasm │ │ ├── map.chasm │ │ ├── reduce.chasm │ │ └── split_float.chasm │ ├── lambdas │ │ ├── capturing │ │ │ ├── basic.chasm │ │ │ └── global.chasm │ │ ├── currying │ │ │ └── basic.chasm │ │ └── recursion │ │ │ ├── basic.chasm │ │ │ ├── indirect.chasm │ │ │ └── map_arg.chasm │ ├── literals │ │ ├── basic.chasm │ │ └── float.chasm │ ├── references │ │ └── global.chasm │ ├── syntax │ │ ├── comments.chasm │ │ └── trailing_commas.chasm │ └── ternary │ │ └── nested.chasm │ └── tests │ ├── complex │ ├── basic.chasm │ └── brainfuck.chasm │ ├── intrinsics │ ├── chars_join.chasm │ ├── conversion.chasm │ ├── entries.chasm │ ├── flatten.chasm │ ├── len.chasm │ ├── map.chasm │ ├── reduce.chasm │ └── split_float.chasm │ ├── lambdas │ ├── capturing │ │ ├── basic.chasm │ │ └── global.chasm │ ├── currying │ │ └── basic.chasm │ └── recursion │ │ ├── basic.chasm │ │ ├── indirect.chasm │ │ └── map_arg.chasm │ ├── literals │ ├── basic.chasm │ └── float.chasm │ ├── references │ └── global.chasm │ ├── syntax │ ├── comments.chasm │ └── trailing_commas.chasm │ └── ternary │ └── nested.chasm ├── config └── checkstyle │ └── checkstyle.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Test and Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v2 11 | - name: Set up JDK 17 12 | uses: actions/setup-java@v2 13 | with: 14 | java-version: '17' 15 | distribution: 'temurin' 16 | cache: gradle 17 | - name: Grant execute permission for gradlew 18 | run: chmod +x gradlew 19 | - name: Build with Gradle 20 | run: ./gradlew build 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Publish Snapshot 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | name: Publish Snapshot 11 | runs-on: ubuntu-latest 12 | container: 13 | image: openjdk:17-jdk 14 | options: --user root 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | - name: Grant execute permission for gradlew 19 | run: chmod +x gradlew 20 | - name: Publish snapshot 21 | run: ./gradlew publishSnapshotPublicationToQuiltSnapshotRepository --stacktrace 22 | env: 23 | SNAPSHOTS_URL: ${{ secrets.SNAPSHOTS_URL }} 24 | SNAPSHOTS_USERNAME: ${{ secrets.SNAPSHOTS_USERNAME }} 25 | SNAPSHOTS_PASSWORD: ${{ secrets.SNAPSHOTS_PASSWORD }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | .settings/ 4 | .classpath 5 | .project 6 | build/ 7 | bin/ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > NOTE: This project is still in its early development. 2 | > There's guaranteed bugs and missing functionality. 3 | 4 | # Chasm - Collision Handling ASM 5 | 6 | ## What is Chasm? 7 | Chasm is a Java bytecode transformation tool. 8 | In its base functionality, it's similar to [ASM](https://asm.ow2.io/). 9 | However, ASM is intended for single programs transforming some bytecode, 10 | whereas the goal of Chasm is to allow multiple users to transform the same code. 11 | This is useful in modding Java based games, where multiple mods might want to change 12 | the same code. 13 | 14 | ## Why use Chasm? 15 | There are other options for transforming bytecode. 16 | Two popular ones are [Mixins](https://github.com/SpongePowered/Mixin) and access-wideners. 17 | While they both perform well in their respective tasks, they are both limiting: 18 | Access wideners do one thing only and Mixin tries its best to be safe, 19 | preventing many useful transformations like changing control flow. 20 | 21 | Chasm aims to provide the full power of ASM without restrictions. 22 | This means that using Chasm directly might be tricky, but the goal of Chasm is to 23 | allow reimplementing Mixin and access wideners on top of Chasm. 24 | 25 | This greatly simplifies the toolchain, since only one bytecode transformation 26 | library needs to be applied. 27 | Additionally, if Mixin and AW are insufficient, people can implement 28 | another "frontend" for Chasm without requiring special toolchain support. 29 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | apply plugin: 'checkstyle' 3 | apply plugin: 'java' 4 | apply plugin: 'maven-publish' 5 | 6 | group 'org.quiltmc.chasm' 7 | version '0.1.0' 8 | 9 | java { 10 | toolchain { 11 | languageVersion = JavaLanguageVersion.of(17) 12 | } 13 | } 14 | 15 | checkstyle { 16 | toolVersion "9.1" 17 | ignoreFailures(false) 18 | } 19 | 20 | publishing { 21 | repositories { 22 | maven { 23 | name = 'quiltSnapshot' 24 | url = System.getenv('SNAPSHOTS_URL') 25 | 26 | credentials { 27 | username = System.getenv('SNAPSHOTS_USERNAME') 28 | password = System.getenv('SNAPSHOTS_PASSWORD') 29 | } 30 | } 31 | } 32 | 33 | publications { 34 | snapshot(MavenPublication) { 35 | version = project.version + "-SNAPSHOT" 36 | 37 | from components.java 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | exclusiveContent { 3 | forRepository { 4 | ivy { 5 | url 'https://javacc.com/download/' 6 | 7 | patternLayout { 8 | artifact '/[module].[ext]' 9 | } 10 | 11 | metadataSources { artifact() } 12 | } 13 | } 14 | filter { 15 | includeGroup "javacc21" 16 | } 17 | } 18 | } 19 | 20 | 21 | dependencies { 22 | implementation 'javacc21:javacc-full' 23 | } 24 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/GenerateParserTask.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | import java.util.Collections; 3 | 4 | import com.javacc.Main; 5 | import freemarker.template.TemplateException; 6 | import org.gradle.api.DefaultTask; 7 | import org.gradle.api.file.DirectoryProperty; 8 | import org.gradle.api.file.RegularFileProperty; 9 | import org.gradle.api.tasks.InputFile; 10 | import org.gradle.api.tasks.OutputDirectory; 11 | import org.gradle.api.tasks.TaskAction; 12 | 13 | public abstract class GenerateParserTask extends DefaultTask { 14 | @InputFile 15 | public abstract RegularFileProperty getGrammarFile(); 16 | 17 | @OutputDirectory 18 | public abstract DirectoryProperty getOutputDir(); 19 | 20 | @TaskAction 21 | public void run() { 22 | getProject().delete(getOutputDir()); 23 | 24 | int returnValue; 25 | try { 26 | returnValue = Main.mainProgram( 27 | getGrammarFile().get().getAsFile().toPath(), 28 | getOutputDir().get().getAsFile().toPath(), 29 | "java", 30 | 8, 31 | true, 32 | Collections.emptyMap() 33 | ); 34 | } catch (Exception e) { 35 | throw new RuntimeException(e); 36 | } 37 | 38 | if (returnValue != 0) { 39 | throw new RuntimeException("JavaCC returned a non-zero exit code."); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chasm/build.gradle: -------------------------------------------------------------------------------- 1 | compileJava { 2 | javaCompiler = javaToolchains.compilerFor { 3 | languageVersion = JavaLanguageVersion.of(8) 4 | } 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation project(":chassembly") 13 | 14 | implementation 'org.slf4j:slf4j-api:1.8.0-beta4' 15 | implementation 'org.ow2.asm:asm:9.2' 16 | implementation 'org.ow2.asm:asm-tree:9.2' 17 | implementation 'org.ow2.asm:asm-analysis:9.2' 18 | 19 | compileOnly 'org.jetbrains:annotations:23.0.0' 20 | testCompileOnly 'org.jetbrains:annotations:23.0.0' 21 | 22 | testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' 23 | testImplementation 'org.ow2.asm:asm-util:9.2' 24 | testImplementation 'com.googlecode.java-diff-utils:diffutils:1.3.0' 25 | testImplementation 'org.reflections:reflections:0.10.2' 26 | } 27 | 28 | test { 29 | useJUnitPlatform() 30 | } 31 | 32 | sourceSets { 33 | testData { 34 | java { 35 | compileClasspath = sourceSets.main.output + configurations.testCompileClasspath 36 | runtimeClasspath = sourceSets.main.output + project.configurations.testRuntimeClasspath 37 | } 38 | } 39 | } 40 | 41 | testClasses.dependsOn("testDataClasses") 42 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/ClassResult.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | import org.quiltmc.chasm.lang.api.metadata.Metadata; 6 | 7 | /** 8 | * The result type used for {@link org.quiltmc.chasm.api.ChasmProcessor#process()}. 9 | */ 10 | public class ClassResult { 11 | private final byte @Nullable [] classBytes; 12 | 13 | private final @NotNull Metadata metadata; 14 | 15 | private final @NotNull Type type; 16 | 17 | /** 18 | * Create a new {@link ClassResult}. 19 | * 20 | * @param classBytes The bytes representing the class of this result, 21 | * or {@code null} if {@code type = Type.Removed}. 22 | * @param metadata The metadata associated with the class of this result. 23 | * @param type The {@link Type} of this result. 24 | */ 25 | public ClassResult(byte @Nullable [] classBytes, @NotNull Metadata metadata, @NotNull Type type) { 26 | this.classBytes = classBytes; 27 | this.metadata = metadata; 28 | this.type = type; 29 | } 30 | 31 | /** 32 | * Get the bytes representing the class of this result. 33 | * This will be {@code null} if and only if {@link #getType} is {@link Type#REMOVED}. 34 | * 35 | * @return The bytes representing the class of this result, 36 | * or {@code null} if {@link #getType} is {@link Type#REMOVED}. 37 | */ 38 | public byte @Nullable [] getClassBytes() { 39 | return classBytes; 40 | } 41 | 42 | /** 43 | * Get the {@link Metadata} associated with the class of this result. 44 | * 45 | * @return The {@link Metadata} of the class. 46 | */ 47 | public @NotNull Metadata getMetadata() { 48 | return metadata; 49 | } 50 | 51 | /** 52 | * Get the {@link Type} of this result. 53 | * 54 | * @return The {@link Type} of this result. 55 | */ 56 | public @NotNull Type getType() { 57 | return type; 58 | } 59 | 60 | /** 61 | * Represents the possible outcomes of {@link ClassResult}. 62 | */ 63 | public enum Type { 64 | /** 65 | * Indicates that the class hasn't been modified during processing. 66 | * Note that this doesn't apply to {@link org.quiltmc.chasm.lang.api.metadata.Metadata}. 67 | */ 68 | UNMODIFIED, 69 | 70 | /** 71 | * Indicates that the class may have been modified during processing. 72 | */ 73 | MODIFIED, 74 | 75 | /** 76 | * Indicates that this class was created during processing. 77 | */ 78 | ADDED, 79 | 80 | /** 81 | * Indicates that this class was deleted during processing. 82 | * This means that {@link #getClassBytes} will return null. 83 | */ 84 | REMOVED 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/Lock.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api; 2 | 3 | /** 4 | * Todo. 5 | */ 6 | public enum Lock { 7 | NONE, 8 | BEFORE, 9 | AFTER 10 | } 11 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/Transformation.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | 6 | import org.quiltmc.chasm.api.target.Target; 7 | import org.quiltmc.chasm.lang.api.ast.Node; 8 | 9 | /** 10 | * A Transformation of a {@link #getTarget() target} using specified {@link #getSources() sources}. 11 | */ 12 | public interface Transformation { 13 | /** 14 | * Gets this {@link Transformation Transformation's} parent {@link Transformer}. 15 | * 16 | * @return The parent {@code Transformer} of this {@code Transformation}. 17 | */ 18 | Transformer getParent(); 19 | 20 | /** 21 | * Gets this {@link Transformation Transformation's} {@link Target}. 22 | * 23 | * @return The {@code Target} of this {@code Transformation}. 24 | */ 25 | Target getTarget(); 26 | 27 | /** 28 | * Gets this {@link Transformation Transformation's} map of named source {@link Target Targets}. 29 | * 30 | *

Sources are the {@link Node Nodes} that this {@code Transformation} requests as input. 31 | * 32 | * @return This {@code Transformation Transformation's} input sources 33 | * as a map of string-named {@code Target targets}. 34 | */ 35 | default Map getSources() { 36 | return Collections.emptyMap(); 37 | } 38 | 39 | /** 40 | * Applies this {@link Transformation} to the given {@link Node}, 41 | * possibly using its {@code Node} sources. 42 | * 43 | * @param targetNode A {@code Node} to apply this {@code Transformation} to. 44 | * 45 | * @param nodeSources The sources of the target {@code Node}. 46 | * 47 | * @return The {@code Node} resulting from applying this {@code Transformation}. 48 | */ 49 | Node apply(Node targetNode, Map nodeSources); 50 | } 51 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/Transformer.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import java.util.Set; 6 | 7 | import org.quiltmc.chasm.lang.api.ast.ListNode; 8 | 9 | /** 10 | * Bulk-instantiates {@link Transformation}s of classes. 11 | * 12 | *

{@link Transformer}s also provide a {@code String} ID for use in occasionally 13 | * hard-coding relative {@code Transformer} ordering. 14 | */ 15 | public interface Transformer { 16 | /** 17 | * Applies this {@link Transformer} to the given {@link ListNode} of classes, 18 | * resulting in a {@code Collection} of {@link Transformation}s. 19 | * 20 | * @param classes The {@code ListNode} of classes available to transform. 21 | * 22 | * @return A {@code Collection} of {@code Transformation}s this 23 | * {@code Transformer} created from the given {@code ListNode} of classes. 24 | */ 25 | Collection apply(ListNode classes); 26 | 27 | /** 28 | * Gets the ID of this {@link Transformer}. 29 | * 30 | *

The ID string must be a string unique among all other transformers. 31 | * E.g. org.example.transformers.ExampleTransformer 32 | * 33 | * @return The unique ID of this Transformer. 34 | */ 35 | String getId(); 36 | 37 | /** 38 | * Defines explicit dependencies between the {@link Transformation}s of {@link Transformer}s. 39 | * All Transformations defined by this Transformer *must run after* all Transformations 40 | * defined by all the Transformers whose ID is in the returned {@link Set}. 41 | * 42 | * @param transformerIds All known transformer IDs. 43 | * @return A set of Transformer IDs this Transformer must run after. 44 | * 45 | * @see #getId() 46 | */ 47 | default Set mustRunAfter(Set transformerIds) { 48 | return Collections.emptySet(); 49 | } 50 | 51 | /** 52 | * Defines explicit dependencies between the {@link Transformation}s of {@link Transformer}s. 53 | * All Transformations defined by this Transformer *must run before* all Transformations 54 | * defined by all the Transformers whose ID is in the returned {@link Set}. 55 | * 56 | * @param transformerIds All known transformer IDs. 57 | * @return A Set of Transformer IDs this Transformer must run before. 58 | * 59 | * @see #getId() 60 | */ 61 | default Set mustRunBefore(Set transformerIds) { 62 | return Collections.emptySet(); 63 | } 64 | 65 | 66 | /** 67 | * Defines explicit dependencies between {@link Transformer}s. 68 | * This Transformer *must run after* all the Transformers whose ID is in the returned {@link Set}. 69 | * 70 | * @param transformerIds All known transformer IDs. 71 | * @return A {@link Set} of Transformer IDs this Transformer must run after. 72 | * 73 | * @see #getId() 74 | */ 75 | default Set mustRunRoundAfter(Set transformerIds) { 76 | return Collections.emptySet(); 77 | } 78 | 79 | /** 80 | * Defines explicit dependencies between {@link Transformer}s. 81 | * This Transformer *must run before* all the Transformers whose ID is in the returned {@link Set}. 82 | * 83 | * @param transformerIds All known transformer IDs. 84 | * @return A {@link Set} of Transformer IDs this Transformer must run before. 85 | * 86 | * @see #getId() 87 | */ 88 | default Set mustRunRoundBefore(Set transformerIds) { 89 | return Collections.emptySet(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/target/NodeTarget.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api.target; 2 | 3 | import org.quiltmc.chasm.api.Lock; 4 | import org.quiltmc.chasm.lang.api.ast.Node; 5 | 6 | /** 7 | * Locates a {@link Node}, as a {@link Target}. 8 | */ 9 | public class NodeTarget extends Target { 10 | /** 11 | * Makes a new {@link NodeTarget} that targets the passed {@link Node}. 12 | * 13 | * @param node The {@link Node} to target. 14 | */ 15 | public NodeTarget(Node node) { 16 | super(node, Lock.NONE); 17 | } 18 | 19 | /** 20 | * Makes a new {@link NodeTarget} that targets the passed {@link Node} and applies the specified {@link Lock}. 21 | * 22 | * @param node The {@link Node} to target. 23 | * @param lock The {@link Lock} to apply. 24 | */ 25 | public NodeTarget(Node node, Lock lock) { 26 | super(node, lock); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/target/SliceTarget.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api.target; 2 | 3 | import java.util.List; 4 | 5 | import org.quiltmc.chasm.api.Lock; 6 | import org.quiltmc.chasm.lang.api.ast.ListNode; 7 | 8 | /** 9 | * Slices a {@link ListNode}, as a {@link Target}. 10 | * 11 | *

A slice of a list is a contiguous subset of the list, like 12 | * {@link List#subList}. 13 | */ 14 | public class SliceTarget extends Target { 15 | // NOTE! "Virtual Index". Divide by two for actual list index 16 | private int startIndex; 17 | private int endIndex; 18 | 19 | /** 20 | * Creates a {@link SliceTarget} of the passed {@link ListNode}. 21 | * 22 | * @param target The {@code ListNode} to slice. 23 | * 24 | * @param start The first index of the {@code ListNode} included in the 25 | * {@code SliceTarget}. 26 | * 27 | * @param end The first index after the {@code start} of the {@link ListNode} 28 | * not included in the {@code SliceTarget}. 29 | * 30 | */ 31 | public SliceTarget(ListNode target, int start, int end) { 32 | super(target, Lock.NONE); 33 | startIndex = start; 34 | endIndex = end; 35 | } 36 | 37 | /** 38 | * Creates a {@link SliceTarget} of the passed {@link ListNode} and applies the specified {@link Lock}. 39 | * 40 | * @param target The {@code ListNode} to slice. 41 | * 42 | * @param start The first index of the {@code ListNode} included in the 43 | * {@code SliceTarget}. 44 | * 45 | * @param end The first index after the {@code start} of the {@link ListNode} 46 | * not included in the {@code SliceTarget}. 47 | * 48 | * @param lock The {@link Lock} to apply. 49 | */ 50 | public SliceTarget(ListNode target, int start, int end, Lock lock) { 51 | super(target, lock); 52 | startIndex = start; 53 | endIndex = end; 54 | } 55 | 56 | /** 57 | * Gets this {@link SliceTarget}'s slice start index. 58 | * 59 | *

The slice start index is the first index of the {@link ListNode} included in this {@code SliceTarget}. 60 | * 61 | * @return The start index of this {@code SliceTarget}' slice. 62 | */ 63 | public int getStartIndex() { 64 | return startIndex; 65 | } 66 | 67 | /** 68 | * Sets this {@link SliceTarget}'s slice start index. 69 | * 70 | *

The slice start index is the first index of the {@link ListNode} included in this {@code SliceTarget}. 71 | * 72 | * @param startIndex The new start index for this {@code SliceTarget}'s slice. 73 | */ 74 | public void setStartIndex(int startIndex) { 75 | this.startIndex = startIndex; 76 | } 77 | 78 | /** 79 | * Gets this {@link SliceTarget}'s slice end index. 80 | * 81 | *

The slice end index is the first index after the {@code start} of the {@link ListNode} 82 | * not included in this {@code SliceTarget}. 83 | * 84 | * @return The end index of this {@code SliceTarget}'s slice. 85 | */ 86 | public int getEndIndex() { 87 | return endIndex; 88 | } 89 | 90 | /** 91 | * Set this {@link SliceTarget}'s slice end index. 92 | * 93 | *

The slice end index is the first index after the {@code start} of the {@link ListNode} 94 | * not included in this {@code SliceTarget}. 95 | * 96 | * @param endIndex The new end index for this {@code SliceTarget}'s slice. 97 | */ 98 | public void setEndIndex(int endIndex) { 99 | this.endIndex = endIndex; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/target/Target.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api.target; 2 | 3 | import org.quiltmc.chasm.api.Lock; 4 | import org.quiltmc.chasm.lang.api.ast.Node; 5 | 6 | /** 7 | * Defines a range of {@link Node}s. 8 | */ 9 | public abstract class Target { 10 | private final Node node; 11 | private final Lock lock; 12 | 13 | public Target(Node node, Lock lock) { 14 | this.node = node; 15 | this.lock = lock; 16 | } 17 | 18 | /** 19 | * Gets a {@link Node} containing the {@link Target}. 20 | * 21 | * @return The targeted {@link Node}. 22 | */ 23 | public Node getTarget() { 24 | return node; 25 | } 26 | 27 | /** 28 | * Gets the {@link Lock} applied to the {@link Target}. 29 | * 30 | * @return The targeted {@link Node}[s] as a {@link Node}. 31 | */ 32 | public Lock getLock() { 33 | return lock; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/util/ClassInfo.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api.util; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.jetbrains.annotations.Contract; 6 | import org.objectweb.asm.ClassReader; 7 | import org.objectweb.asm.Opcodes; 8 | 9 | /** 10 | * Static information related to a specific class. 11 | */ 12 | public class ClassInfo { 13 | public static final String OBJECT = "java/lang/Object"; 14 | 15 | private final String className; 16 | private final String superClass; 17 | private final String[] interfaces; 18 | private final boolean isInterface; 19 | 20 | /** 21 | * Construct a new {@link ClassInfo}. 22 | * 23 | * @param className The name of the class (see {@link #getClassName()}). 24 | * @param superClass The name of the super class (see {@link #getSuperClass()}). 25 | * @param interfaces The names of the implemented interfaces (see {@link #getInterfaces()}). 26 | * @param isInterface Whether this class is an interface (see {@link #isInterface}). 27 | */ 28 | public ClassInfo(String className, String superClass, String[] interfaces, boolean isInterface) { 29 | this.className = className; 30 | this.superClass = superClass; 31 | this.interfaces = interfaces; 32 | this.isInterface = isInterface; 33 | } 34 | 35 | /** 36 | * Returns the internal name of the class (See {@link org.objectweb.asm.Type#getInternalName}). 37 | * 38 | * @return The internal name of the class. 39 | */ 40 | public String getClassName() { 41 | return className; 42 | } 43 | 44 | /** 45 | * Returns the internal name of the super class (See {@link org.objectweb.asm.Type#getInternalName}). 46 | * For interfaces, this should return {@link #OBJECT}. 47 | * Only for the {@link Object} class ({@link #OBJECT}) will this return null. 48 | * 49 | * @return The internal name of the super class. 50 | */ 51 | public String getSuperClass() { 52 | return superClass; 53 | } 54 | 55 | /** 56 | * Returns the internal names of the implemented interfaces (See {@link org.objectweb.asm.Type#getInternalName}). 57 | * 58 | * @return The internal names of the interfaces. 59 | */ 60 | public String[] getInterfaces() { 61 | return interfaces; 62 | } 63 | 64 | /** 65 | * Returns whether the class is an interface. 66 | * 67 | * @return Whether the class is an interface. 68 | */ 69 | public boolean isInterface() { 70 | return isInterface; 71 | } 72 | 73 | /** 74 | * Extract class information from the provided {@link Class} object using reflection. 75 | * 76 | * @param clazz The class from which to extract information. 77 | * @return The information extracted from the provided class. 78 | */ 79 | public static ClassInfo fromClass(Class clazz) { 80 | if (clazz == Object.class) { 81 | return new ClassInfo( 82 | ClassInfo.OBJECT, 83 | null, 84 | new String[0], 85 | false 86 | ); 87 | } 88 | 89 | return new ClassInfo( 90 | clazz.getName().replace('.', '/'), 91 | clazz.isInterface() ? ClassInfo.OBJECT : clazz.getSuperclass().getName().replace('.', '/'), 92 | Arrays.stream(clazz.getInterfaces()).map(c -> c.getName().replace('.', '/')).toArray(String[]::new), 93 | clazz.isInterface() 94 | ); 95 | } 96 | 97 | /** 98 | * Extract class information from the provided binary representation of a class. 99 | * Such a binary representation can be obtained by reading a class file. 100 | * 101 | * @param classBytes The binary representation of a class, e.g. a class file. 102 | * @return The information extracted from the provided class. 103 | */ 104 | public static ClassInfo fromBytes(byte[] classBytes) { 105 | ClassReader reader = new ClassReader(classBytes); 106 | return new ClassInfo( 107 | reader.getClassName(), 108 | reader.getSuperName(), 109 | reader.getInterfaces(), 110 | (reader.getAccess() & Opcodes.ACC_INTERFACE) != 0 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/api/util/Context.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.api.util; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | /** 7 | * The context needed for chasm to function: 8 | *

12 | * 13 | *

15 | * Binary names in JVMS 17. 16 | * 17 | *

All methods in this class must be thread-safe, as they may be called concurrently. 18 | */ 19 | public interface Context { 20 | /** 21 | * Returns information about a class. 22 | * 23 | * @param className The internal name of a class to query (See {@link org.objectweb.asm.Type#getInternalName}). 24 | * @return The {@link ClassInfo} corresponding to the specified {@code className}, 25 | * or {@code null} if it couldn't be located. 26 | */ 27 | @Contract(pure = true) 28 | @Nullable ClassInfo getClassInfo(String className); 29 | 30 | /** 31 | * Returns the contents of the file at the given path, or {@code null} if the file does not exist or if chasm 32 | * processors shouldn't have access to this file. 33 | * 34 | *

Important: like the other functions in this interface, implementations of this function must 35 | * be pure, which means you should be mindful of the possibility of the file contents changing between invocations 36 | * of this method. A possible solution is to only read the file once and store it in memory. 37 | * 38 | *

It is possible to retain the cacheability of chasm while supporting this method, by remembering all files this 39 | * method is called with and their hashes, and storing them for the next time chasm is run (checking against the 40 | * hashes of the files then). 41 | * 42 | *

The format of the {@code path} is up to the implementer, for example different symbols might signify 43 | * different types of files (whether to look in a jar or in the file system). However, the accepted format of the 44 | * path should not depend on the operating system (e.g. \ must not be accepted on Windows if it is not going to be 45 | * accepted on Linux); this is to preserve purity across operating systems. 46 | * 47 | * @param path The implementation-dependent path to the file 48 | * @return The bytes in the file, or {@code null} if the file doesn't exist or is inaccessible 49 | */ 50 | @Contract(pure = true) 51 | byte @Nullable [] readFile(String path); 52 | } 53 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/ChasmContext.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | 9 | import org.jetbrains.annotations.Nullable; 10 | import org.objectweb.asm.Opcodes; 11 | import org.quiltmc.chasm.api.util.ClassInfo; 12 | import org.quiltmc.chasm.api.util.Context; 13 | import org.quiltmc.chasm.internal.util.NodeConstants; 14 | import org.quiltmc.chasm.internal.util.NodeUtils; 15 | import org.quiltmc.chasm.lang.api.ast.ListNode; 16 | import org.quiltmc.chasm.lang.api.ast.MapNode; 17 | import org.quiltmc.chasm.lang.api.ast.Node; 18 | 19 | public class ChasmContext implements Context { 20 | private final Context parent; 21 | private final ListNode classes; 22 | 23 | public ChasmContext(Context parent, ListNode classes) { 24 | this.parent = parent; 25 | this.classes = classes; 26 | } 27 | 28 | @Override 29 | public ClassInfo getClassInfo(String className) { 30 | for (Node classNode : classes.getEntries()) { 31 | String name = NodeUtils.getAsString(classNode, NodeConstants.NAME); 32 | if (!name.equals(className)) { 33 | continue; 34 | } 35 | 36 | String superName = NodeUtils.getAsString(classNode, NodeConstants.SUPER); 37 | int access = NodeUtils.getAsInt(classNode, NodeConstants.ACCESS); 38 | ListNode interfaces = NodeUtils.getAsList(classNode, NodeConstants.INTERFACES); 39 | 40 | return new ClassInfo( 41 | className, 42 | superName == null && !className.equals(ClassInfo.OBJECT) ? ClassInfo.OBJECT : superName, 43 | interfaces.getEntries().stream().map(NodeUtils::asString).toArray(String[]::new), 44 | (access & Opcodes.ACC_INTERFACE) != 0 45 | ); 46 | } 47 | 48 | return parent.getClassInfo(className); 49 | } 50 | 51 | @Override 52 | public byte @Nullable [] readFile(String path) { 53 | return parent.readFile(path); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/ClassData.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.quiltmc.chasm.lang.api.metadata.Metadata; 6 | 7 | /** 8 | * Represents the data of a class used for transforming. 9 | */ 10 | @ApiStatus.Internal 11 | public final class ClassData { 12 | private final byte @NotNull [] classBytes; 13 | private final @NotNull Metadata metadata; 14 | 15 | public ClassData(byte @NotNull [] classBytes, @NotNull Metadata metadata) { 16 | this.classBytes = classBytes; 17 | this.metadata = metadata; 18 | } 19 | 20 | public byte @NotNull [] getClassBytes() { 21 | return classBytes; 22 | } 23 | 24 | public @NotNull Metadata getMetadata() { 25 | return metadata; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/ChasmClassWriter.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.asm; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.quiltmc.chasm.api.util.ClassInfo; 5 | import org.quiltmc.chasm.api.util.Context; 6 | 7 | public class ChasmClassWriter extends ClassWriter { 8 | private final Context context; 9 | 10 | public ChasmClassWriter(Context context) { 11 | super(COMPUTE_FRAMES); 12 | 13 | this.context = context; 14 | } 15 | 16 | @Override 17 | protected String getCommonSuperClass(String type1, String type2) { 18 | String current1 = type1; 19 | while (!current1.equals(ClassInfo.OBJECT)) { 20 | String current2 = type2; 21 | while (!current2.equals(ClassInfo.OBJECT)) { 22 | if (current1.equals(current2)) { 23 | return current1; 24 | } 25 | current2 = context.getClassInfo(current2).getSuperClass(); 26 | } 27 | current1 = context.getClassInfo(current1).getSuperClass(); 28 | } 29 | 30 | return ClassInfo.OBJECT; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.asm; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmAnnotationVisitor.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.asm.visitor; 2 | 3 | import org.objectweb.asm.AnnotationVisitor; 4 | import org.quiltmc.chasm.internal.util.NodeConstants; 5 | import org.quiltmc.chasm.internal.util.NodeUtils; 6 | import org.quiltmc.chasm.lang.api.ast.Ast; 7 | import org.quiltmc.chasm.lang.api.ast.ListNode; 8 | import org.quiltmc.chasm.lang.api.ast.MapNode; 9 | import org.quiltmc.chasm.lang.api.ast.Node; 10 | 11 | public class ChasmAnnotationVisitor extends AnnotationVisitor { 12 | private final Node values; 13 | 14 | public ChasmAnnotationVisitor(int api, Node values) { 15 | this(api, values, null); 16 | } 17 | 18 | public ChasmAnnotationVisitor(int api, Node values, AnnotationVisitor av) { 19 | super(api, av); 20 | 21 | this.values = values; 22 | } 23 | 24 | @Override 25 | public void visit(String name, Object value) { 26 | if (value instanceof Object[]) { 27 | // Process Object[] using visitor pattern 28 | AnnotationVisitor visitor = visitArray(name); 29 | for (Object entry : (Object[]) value) { 30 | visitor.visit(null, entry); 31 | } 32 | visitor.visitEnd(); 33 | return; 34 | } 35 | 36 | visitValueNode(name, NodeUtils.getValueNode(value)); 37 | 38 | super.visit(name, value); 39 | } 40 | 41 | 42 | @Override 43 | public void visitEnum(String name, String descriptor, String value) { 44 | MapNode enumValueNode = Ast.map() 45 | .put(NodeConstants.DESCRIPTOR, descriptor) 46 | .put(NodeConstants.VALUE, value) 47 | .build(); 48 | 49 | visitValueNode(name, enumValueNode); 50 | 51 | super.visitEnum(name, descriptor, value); 52 | } 53 | 54 | @Override 55 | public AnnotationVisitor visitAnnotation(String name, String descriptor) { 56 | MapNode values = Ast.emptyMap(); 57 | MapNode annotationValueNode = Ast.map() 58 | .put(NodeConstants.DESCRIPTOR, descriptor) 59 | .put(NodeConstants.VALUES, values) 60 | .build(); 61 | 62 | visitValueNode(name, annotationValueNode); 63 | 64 | return new ChasmAnnotationVisitor(api, values, super.visitAnnotation(name, descriptor)); 65 | } 66 | 67 | @Override 68 | public AnnotationVisitor visitArray(String name) { 69 | ListNode values = Ast.emptyList(); 70 | 71 | visitValueNode(name, values); 72 | 73 | return new ChasmAnnotationVisitor(api, values, super.visitArray(name)); 74 | } 75 | 76 | @Override 77 | public void visitEnd() { 78 | // Nothing to do 79 | super.visitEnd(); 80 | } 81 | 82 | private void visitValueNode(String name, Node value) { 83 | // If this is processing a map, name must be set 84 | if (values instanceof MapNode) { 85 | if (name == null) { 86 | throw new RuntimeException("Annotation value is missing name"); 87 | } 88 | 89 | NodeUtils.asMap(values).put(name, value); 90 | } 91 | 92 | // If this is processing a list, name must not be set 93 | if (values instanceof ListNode) { 94 | NodeUtils.asList(values).add(value); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmFieldVisitor.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.asm.visitor; 2 | 3 | import org.objectweb.asm.AnnotationVisitor; 4 | import org.objectweb.asm.Attribute; 5 | import org.objectweb.asm.FieldVisitor; 6 | import org.objectweb.asm.TypePath; 7 | import org.quiltmc.chasm.internal.util.NodeConstants; 8 | import org.quiltmc.chasm.lang.api.ast.Ast; 9 | import org.quiltmc.chasm.lang.api.ast.ListNode; 10 | import org.quiltmc.chasm.lang.api.ast.MapNode; 11 | 12 | public class ChasmFieldVisitor extends FieldVisitor { 13 | 14 | private final ListNode annotations = Ast.emptyList(); 15 | 16 | public ChasmFieldVisitor(int api, MapNode fieldNode) { 17 | super(api); 18 | 19 | fieldNode.put(NodeConstants.ANNOTATIONS, annotations); 20 | } 21 | 22 | @Override 23 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { 24 | MapNode values = Ast.emptyMap(); 25 | MapNode annotation = Ast.map() 26 | .put(NodeConstants.DESCRIPTOR, descriptor) 27 | .put(NodeConstants.VISIBLE, visible) 28 | .put(NodeConstants.VALUES, values) 29 | .build(); 30 | annotations.add(annotation); 31 | 32 | return new ChasmAnnotationVisitor(api, values); 33 | } 34 | 35 | @Override 36 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 37 | MapNode values = Ast.emptyMap(); 38 | MapNode annotation = Ast.map() 39 | .put(NodeConstants.DESCRIPTOR, descriptor) 40 | .put(NodeConstants.VISIBLE, visible) 41 | .put(NodeConstants.VALUES, values) 42 | .put(NodeConstants.TYPE_REF, typeRef) 43 | .put(NodeConstants.TYPE_PATH, typePath.toString()) 44 | .build(); 45 | annotations.add(annotation); 46 | 47 | return new ChasmAnnotationVisitor(api, values); 48 | } 49 | 50 | @Override 51 | public void visitAttribute(Attribute attribute) { 52 | throw new RuntimeException("Unknown attribute: " + attribute.type); 53 | } 54 | 55 | @Override 56 | public void visitEnd() { 57 | // Nothing to do here 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmModuleVisitor.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.asm.visitor; 2 | 3 | import org.objectweb.asm.ModuleVisitor; 4 | import org.quiltmc.chasm.internal.util.NodeConstants; 5 | import org.quiltmc.chasm.lang.api.ast.Ast; 6 | import org.quiltmc.chasm.lang.api.ast.ListNode; 7 | import org.quiltmc.chasm.lang.api.ast.MapNode; 8 | 9 | public class ChasmModuleVisitor extends ModuleVisitor { 10 | private final MapNode moduleNode; 11 | 12 | private final ListNode packages = Ast.emptyList(); 13 | private final ListNode requires = Ast.emptyList(); 14 | private final ListNode exports = Ast.emptyList(); 15 | private final ListNode opens = Ast.emptyList(); 16 | private final ListNode uses = Ast.emptyList(); 17 | private final ListNode provides = Ast.emptyList(); 18 | 19 | public ChasmModuleVisitor(int api, MapNode moduleNode) { 20 | super(api); 21 | this.moduleNode = moduleNode; 22 | 23 | moduleNode.put(NodeConstants.PACKAGES, packages); 24 | moduleNode.put(NodeConstants.REQUIRES, requires); 25 | moduleNode.put(NodeConstants.EXPORTS, exports); 26 | moduleNode.put(NodeConstants.OPENS, opens); 27 | moduleNode.put(NodeConstants.USES, uses); 28 | moduleNode.put(NodeConstants.PROVIDERS, provides); 29 | } 30 | 31 | @Override 32 | public void visitMainClass(String mainClass) { 33 | moduleNode.put(NodeConstants.MAIN, Ast.literal(mainClass)); 34 | } 35 | 36 | @Override 37 | public void visitPackage(String packaze) { 38 | packages.add(Ast.literal(packaze)); 39 | } 40 | 41 | @Override 42 | public void visitRequire(String module, int access, String version) { 43 | MapNode requireNode = Ast.map() 44 | .put(NodeConstants.MODULE, module) 45 | .put(NodeConstants.ACCESS, access) 46 | .put(NodeConstants.VERSION, version) 47 | .build(); 48 | requires.add(requireNode); 49 | } 50 | 51 | @Override 52 | public void visitExport(String packaze, int access, String... modules) { 53 | MapNode exportNode = Ast.map() 54 | .put(NodeConstants.PACKAGE, packaze) 55 | .put(NodeConstants.ACCESS, access) 56 | .build(); 57 | if (modules != null) { 58 | exportNode.put(NodeConstants.MODULES, Ast.list((Object[]) modules)); 59 | } 60 | exports.add(exportNode); 61 | } 62 | 63 | @Override 64 | public void visitOpen(String packaze, int access, String... modules) { 65 | MapNode openNode = Ast.map() 66 | .put(NodeConstants.PACKAGE, packaze) 67 | .put(NodeConstants.ACCESS, access) 68 | .build(); 69 | if (modules != null) { 70 | openNode.put(NodeConstants.MODULES, Ast.list((Object[]) modules)); 71 | } 72 | opens.add(openNode); 73 | } 74 | 75 | @Override 76 | public void visitUse(String service) { 77 | uses.add(Ast.literal(service)); 78 | } 79 | 80 | @Override 81 | public void visitProvide(String service, String... providers) { 82 | MapNode provideNode = Ast.map() 83 | .put(NodeConstants.SERVICE, service) 84 | .put(NodeConstants.PROVIDERS, Ast.list((Object[]) providers)) 85 | .build(); 86 | provides.add(provideNode); 87 | } 88 | 89 | @Override 90 | public void visitEnd() { 91 | // Nothing to do here 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmRecordComponentVisitor.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.asm.visitor; 2 | 3 | import org.objectweb.asm.AnnotationVisitor; 4 | import org.objectweb.asm.Attribute; 5 | import org.objectweb.asm.RecordComponentVisitor; 6 | import org.objectweb.asm.TypePath; 7 | import org.quiltmc.chasm.internal.util.NodeConstants; 8 | import org.quiltmc.chasm.lang.api.ast.Ast; 9 | import org.quiltmc.chasm.lang.api.ast.ListNode; 10 | import org.quiltmc.chasm.lang.api.ast.MapNode; 11 | 12 | public class ChasmRecordComponentVisitor extends RecordComponentVisitor { 13 | private final ListNode annotations = Ast.emptyList(); 14 | 15 | public ChasmRecordComponentVisitor(int api, MapNode recordComponentNode) { 16 | super(api); 17 | 18 | recordComponentNode.put(NodeConstants.ANNOTATIONS, annotations); 19 | } 20 | 21 | @Override 22 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { 23 | MapNode values = Ast.emptyMap(); 24 | MapNode annotation = Ast.map() 25 | .put(NodeConstants.DESCRIPTOR, descriptor) 26 | .put(NodeConstants.VISIBLE, visible) 27 | .put(NodeConstants.VALUES, values) 28 | .build(); 29 | annotations.add(annotation); 30 | 31 | return new ChasmAnnotationVisitor(api, values); 32 | } 33 | 34 | @Override 35 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 36 | MapNode values = Ast.emptyMap(); 37 | MapNode annotation = Ast.map() 38 | .put(NodeConstants.DESCRIPTOR, descriptor) 39 | .put(NodeConstants.VISIBLE, visible) 40 | .put(NodeConstants.VALUES, values) 41 | .put(NodeConstants.TYPE_REF, typeRef) 42 | .put(NodeConstants.TYPE_PATH, typePath.toString()) 43 | .build(); 44 | annotations.add(annotation); 45 | 46 | return new ChasmAnnotationVisitor(api, values); 47 | } 48 | 49 | @Override 50 | public void visitAttribute(Attribute attribute) { 51 | throw new RuntimeException("Unknown attribute: " + attribute.type); 52 | } 53 | 54 | @Override 55 | public void visitEnd() { 56 | // Nothing to do here 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/LocalValue.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.asm.visitor; 2 | 3 | import java.util.Arrays; 4 | import java.util.Objects; 5 | 6 | import org.jetbrains.annotations.Nullable; 7 | import org.objectweb.asm.Type; 8 | import org.objectweb.asm.tree.AbstractInsnNode; 9 | import org.objectweb.asm.tree.InsnList; 10 | import org.objectweb.asm.tree.analysis.Value; 11 | 12 | public final class LocalValue implements Value { 13 | private final @Nullable Type type; 14 | private final int @Nullable[] sourceStores; 15 | 16 | public LocalValue(@Nullable Type type) { 17 | this.type = type; 18 | this.sourceStores = null; 19 | } 20 | 21 | public LocalValue(@Nullable Type type, InsnList instructions, AbstractInsnNode sourceStore) { 22 | this.type = type; 23 | this.sourceStores = new int[]{instructions.indexOf(sourceStore)}; 24 | } 25 | 26 | public LocalValue(@Nullable Type type, int @Nullable[] sourceStores) { 27 | this.type = type; 28 | this.sourceStores = sourceStores; 29 | } 30 | 31 | public int @Nullable[] getSourceStores() { 32 | return sourceStores; 33 | } 34 | 35 | @Nullable 36 | public Type getType() { 37 | return type; 38 | } 39 | 40 | @Override 41 | public int getSize() { 42 | return type == null ? 1 : type.getSize(); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return (31 * Arrays.hashCode(sourceStores)) + Objects.hashCode(type); 48 | } 49 | 50 | @Override 51 | public boolean equals(Object other) { 52 | if (other == this) { 53 | return true; 54 | } 55 | if (!(other instanceof LocalValue)) { 56 | return false; 57 | } 58 | LocalValue that = (LocalValue) other; 59 | return Arrays.equals(this.sourceStores, that.sourceStores) && Objects.equals(this.type, that.type); 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return Arrays.toString(sourceStores) + " -> " + type; 65 | } 66 | 67 | public static int @Nullable[] mergeSourceStores(int @Nullable[] a, int @Nullable[] b) { 68 | if (a == null) { 69 | return b; 70 | } else if (b == null) { 71 | return a; 72 | } else { 73 | int[] ret = new int[a.length + b.length]; 74 | int indexA = 0; 75 | int indexB = 0; 76 | int retIndex = 0; 77 | while (indexA < a.length || indexB < b.length) { 78 | if (indexA < a.length && (indexB >= b.length || a[indexA] <= b[indexB])) { 79 | if (indexB < b.length && a[indexA] == b[indexB]) { 80 | // prevent duplicates 81 | indexB++; 82 | } 83 | ret[retIndex++] = a[indexA++]; 84 | } else { 85 | ret[retIndex++] = b[indexB++]; 86 | } 87 | } 88 | if (retIndex < ret.length) { 89 | return Arrays.copyOf(ret, retIndex); 90 | } 91 | return ret; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.asm.visitor; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/ChasmIntrinsics.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.intrinsic; 2 | 3 | import org.quiltmc.chasm.api.util.Context; 4 | import org.quiltmc.chasm.lang.api.ast.Node; 5 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 6 | 7 | public final class ChasmIntrinsics { 8 | private ChasmIntrinsics() { 9 | } 10 | 11 | public static Evaluator makeEvaluator(Node node, Context context) { 12 | return Evaluator.builder(node) 13 | .addIntrinsic(new FileBytesIntrinsic(context)) 14 | .addIntrinsic(new FileContentIntrinsic(context)) 15 | .addIntrinsic(new IncludeIntrinsic(context)) 16 | .build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/FileBytesIntrinsic.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.intrinsic; 2 | 3 | import org.quiltmc.chasm.api.util.Context; 4 | import org.quiltmc.chasm.lang.api.ast.Ast; 5 | import org.quiltmc.chasm.lang.api.ast.ListNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.ast.StringNode; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 11 | 12 | public class FileBytesIntrinsic extends IntrinsicFunction { 13 | private final Context context; 14 | 15 | public FileBytesIntrinsic(Context context) { 16 | this.context = context; 17 | } 18 | 19 | @Override 20 | public Node apply(Evaluator evaluator, Node arg) { 21 | if (!(arg instanceof StringNode)) { 22 | throw new EvaluationException( 23 | "Built-in function \"file_bytes\" can only be applied to strings but found " + arg); 24 | } 25 | byte[] bytes = context.readFile(((StringNode) arg).getValue()); 26 | if (bytes == null) { 27 | return Ast.nullNode(); 28 | } 29 | ListNode result = Ast.emptyList(); 30 | for (byte b : bytes) { 31 | result.add(Ast.literal(b & 0xff)); 32 | } 33 | return result; 34 | } 35 | 36 | @Override 37 | public String getName() { 38 | return "file_bytes"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/FileContentIntrinsic.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.intrinsic; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | import org.jetbrains.annotations.Nullable; 6 | import org.quiltmc.chasm.api.util.Context; 7 | import org.quiltmc.chasm.lang.api.ast.Ast; 8 | import org.quiltmc.chasm.lang.api.ast.Node; 9 | import org.quiltmc.chasm.lang.api.ast.StringNode; 10 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 11 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 12 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 13 | 14 | public class FileContentIntrinsic extends IntrinsicFunction { 15 | private final Context context; 16 | 17 | public FileContentIntrinsic(Context context) { 18 | this.context = context; 19 | } 20 | 21 | @Override 22 | public Node apply(Evaluator evaluator, Node arg) { 23 | String content = readString(arg, context); 24 | return Ast.nullableString(content); 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return "file_content"; 30 | } 31 | 32 | @Nullable 33 | static String readString(Node arg, Context context) { 34 | if (!(arg instanceof StringNode)) { 35 | throw new EvaluationException( 36 | "Built-in function \"file_content\" can only be applied to strings but found " + arg); 37 | } 38 | String path = ((StringNode) arg).getValue(); 39 | byte[] bytes = context.readFile(path); 40 | if (bytes == null) { 41 | return null; 42 | } 43 | try { 44 | return new String(bytes, StandardCharsets.UTF_8); 45 | } catch (IllegalArgumentException e) { 46 | throw new EvaluationException("File \"" + path + "\" is not utf8 encoded"); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/IncludeIntrinsic.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.intrinsic; 2 | 3 | import org.quiltmc.chasm.api.util.Context; 4 | import org.quiltmc.chasm.lang.api.ast.Node; 5 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 6 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 7 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 8 | import org.quiltmc.chasm.lang.api.exception.ParseException; 9 | 10 | public class IncludeIntrinsic extends IntrinsicFunction { 11 | private final Context context; 12 | 13 | public IncludeIntrinsic(Context context) { 14 | this.context = context; 15 | } 16 | 17 | @Override 18 | public Node apply(Evaluator evaluator, Node arg) { 19 | String content = FileContentIntrinsic.readString(arg, context); 20 | if (content == null) { 21 | throw new EvaluationException("Could not read file " + arg); 22 | } 23 | Node result; 24 | try { 25 | result = Node.parse(content); 26 | } catch (ParseException e) { 27 | throw new EvaluationException("Failed to parse file " + arg + ": " + e.getMessage()); 28 | } 29 | result.resolve(evaluator.getResolver()); 30 | return result.evaluate(evaluator); 31 | } 32 | 33 | @Override 34 | public String getName() { 35 | return "include"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/metadata/OriginMetadata.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.metadata; 2 | 3 | import org.quiltmc.chasm.api.Transformation; 4 | 5 | public class OriginMetadata { 6 | private final String transformerId; 7 | 8 | public OriginMetadata(Transformation origin) { 9 | this(origin.getParent().getId()); 10 | } 11 | 12 | private OriginMetadata(String transformerId) { 13 | this.transformerId = transformerId; 14 | } 15 | 16 | public String getTransformerId() { 17 | return transformerId; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/metadata/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.metadata; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal; 3 | 4 | import org.jetbrains.annotations.ApiStatus; 5 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/transformer/ChasmLangTransformation.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.transformer; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import org.quiltmc.chasm.api.Transformation; 7 | import org.quiltmc.chasm.api.Transformer; 8 | import org.quiltmc.chasm.api.target.NodeTarget; 9 | import org.quiltmc.chasm.api.target.SliceTarget; 10 | import org.quiltmc.chasm.api.target.Target; 11 | import org.quiltmc.chasm.internal.util.NodeUtils; 12 | import org.quiltmc.chasm.lang.api.ast.Ast; 13 | import org.quiltmc.chasm.lang.api.ast.CallNode; 14 | import org.quiltmc.chasm.lang.api.ast.IntegerNode; 15 | import org.quiltmc.chasm.lang.api.ast.ListNode; 16 | import org.quiltmc.chasm.lang.api.ast.MapNode; 17 | import org.quiltmc.chasm.lang.api.ast.Node; 18 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 19 | import org.quiltmc.chasm.lang.api.eval.FunctionNode; 20 | 21 | public class ChasmLangTransformation implements Transformation { 22 | private final Transformer parent; 23 | private final Evaluator evaluator; 24 | private final FunctionNode apply; 25 | private final Target target; 26 | private final Map sources = new LinkedHashMap<>(); 27 | 28 | public ChasmLangTransformation(Transformer parent, Node node, Evaluator evaluator) { 29 | this.parent = parent; 30 | this.evaluator = evaluator; 31 | 32 | if (!(node instanceof MapNode)) { 33 | throw new RuntimeException("Transformations must be maps"); 34 | } 35 | 36 | MapNode transformationExpression = (MapNode) node; 37 | 38 | Node targetNode = transformationExpression.get("target"); 39 | if (!(targetNode instanceof MapNode)) { 40 | throw new RuntimeException("Transformations must declare a map \"target\" in their root map"); 41 | } 42 | this.target = parseTarget((MapNode) targetNode); 43 | 44 | Node applyNode = transformationExpression.get("apply"); 45 | if (!(applyNode instanceof FunctionNode)) { 46 | throw new RuntimeException("Transformations must declare a function \"apply\" in their root map"); 47 | } 48 | this.apply = (FunctionNode) applyNode; 49 | 50 | Node sourcesNode = transformationExpression.get("sources"); 51 | if (sourcesNode != null) { 52 | if (!(sourcesNode instanceof MapNode)) { 53 | throw new RuntimeException("Element \"sources\" in transformation must be a map"); 54 | } 55 | 56 | for (Map.Entry entry : ((MapNode) sourcesNode).getEntries().entrySet()) { 57 | if (!(entry.getValue() instanceof MapNode)) { 58 | throw new RuntimeException("Transformation sources must be maps"); 59 | } 60 | 61 | Target source = parseTarget((MapNode) entry.getValue()); 62 | this.sources.put(entry.getKey(), source); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | public Transformer getParent() { 69 | return parent; 70 | } 71 | 72 | @Override 73 | public Target getTarget() { 74 | return target; 75 | } 76 | 77 | @Override 78 | public Map getSources() { 79 | return sources; 80 | } 81 | 82 | @Override 83 | public Node apply(Node targetNode, Map nodeSources) { 84 | MapNode args = Ast.map() 85 | .put("target", targetNode) 86 | .put("sources", nodeSources) 87 | .build(); 88 | CallNode callExpression = Ast.call(apply, args); 89 | return callExpression.evaluate(evaluator); 90 | } 91 | 92 | private Target parseTarget(MapNode target) { 93 | Node targetNode = NodeUtils.get(target, "node"); 94 | 95 | Integer start = null; 96 | Node startNode = NodeUtils.get(target, "start"); 97 | if (startNode instanceof IntegerNode) { 98 | start = ((IntegerNode) startNode).getValue().intValue(); 99 | } 100 | 101 | Integer end = null; 102 | Node endNode = NodeUtils.get(target, "end"); 103 | if (endNode instanceof IntegerNode) { 104 | end = ((IntegerNode) endNode).getValue().intValue(); 105 | } 106 | 107 | if (targetNode instanceof ListNode && start != null && end != null) { 108 | return new SliceTarget((ListNode) targetNode, start, end); 109 | } else { 110 | return new NodeTarget(targetNode); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/transformer/ChasmLangTransformer.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.transformer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | import org.quiltmc.chasm.api.Transformation; 7 | import org.quiltmc.chasm.api.Transformer; 8 | import org.quiltmc.chasm.api.util.Context; 9 | import org.quiltmc.chasm.internal.intrinsic.ChasmIntrinsics; 10 | import org.quiltmc.chasm.lang.api.ast.Ast; 11 | import org.quiltmc.chasm.lang.api.ast.CallNode; 12 | import org.quiltmc.chasm.lang.api.ast.LambdaNode; 13 | import org.quiltmc.chasm.lang.api.ast.ListNode; 14 | import org.quiltmc.chasm.lang.api.ast.MapNode; 15 | import org.quiltmc.chasm.lang.api.ast.Node; 16 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 17 | 18 | public class ChasmLangTransformer implements Transformer { 19 | private final Node parsed; 20 | 21 | private final String id; 22 | 23 | private final Context context; 24 | 25 | public ChasmLangTransformer(String id, Node parsed, Context context) { 26 | this.id = id; 27 | this.parsed = parsed; 28 | this.context = context; 29 | } 30 | 31 | @Override 32 | public Collection apply(ListNode classes) { 33 | LambdaNode lambdaExpression = Ast.lambda("classes", parsed); 34 | CallNode callExpression = Ast.call(lambdaExpression, classes); 35 | 36 | Evaluator evaluator = ChasmIntrinsics.makeEvaluator(callExpression, context); 37 | Node evaluated = callExpression.evaluate(evaluator); 38 | if (!(evaluated instanceof MapNode)) { 39 | throw new RuntimeException("Transformers must be maps"); 40 | } 41 | 42 | MapNode transformerExpression = (MapNode) evaluated; 43 | Node transformationsNode = transformerExpression.get("transformations"); 44 | if (!(transformationsNode instanceof ListNode)) { 45 | throw new RuntimeException("Transformers must declare a list \"transformations\" in their root map"); 46 | } 47 | 48 | ArrayList transformations = new ArrayList<>(); 49 | for (Node entry : ((ListNode) transformationsNode).getEntries()) { 50 | transformations.add(new ChasmLangTransformation(this, entry, evaluator)); 51 | } 52 | 53 | return transformations; 54 | } 55 | 56 | @Override 57 | public String getId() { 58 | return id; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/transformer/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.transformer; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/ClassNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.tree; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import org.objectweb.asm.ClassReader; 7 | import org.quiltmc.chasm.api.util.Context; 8 | import org.quiltmc.chasm.internal.asm.visitor.ChasmClassVisitor; 9 | import org.quiltmc.chasm.internal.metadata.PathMetadata; 10 | import org.quiltmc.chasm.internal.util.NodeConstants; 11 | import org.quiltmc.chasm.internal.util.PathInitializer; 12 | import org.quiltmc.chasm.lang.api.ast.Ast; 13 | import org.quiltmc.chasm.lang.api.ast.MapNode; 14 | import org.quiltmc.chasm.lang.api.ast.Node; 15 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 16 | import org.quiltmc.chasm.lang.api.eval.Resolver; 17 | 18 | public class ClassNode extends MapNode { 19 | private final ClassReader reader; 20 | 21 | public ClassNode(ClassReader reader, Context context, int index) { 22 | super(new LazyMap<>(getStaticEntries(reader), 23 | () -> getLazyEntries(reader, context, index))); 24 | this.reader = reader; 25 | 26 | PathMetadata root = new PathMetadata(null, index); 27 | PathInitializer.initialize(this, root); 28 | } 29 | 30 | // NOTE: Ensure parity with ChasmClassVisitor 31 | private static Map getStaticEntries(ClassReader reader) { 32 | Map entries = new LinkedHashMap<>(); 33 | 34 | entries.put(NodeConstants.ACCESS, Ast.literal(reader.getAccess())); 35 | entries.put(NodeConstants.NAME, Ast.literal(reader.getClassName())); 36 | entries.put(NodeConstants.SUPER, Ast.nullableString(reader.getSuperName())); 37 | entries.put(NodeConstants.INTERFACES, Ast.list((Object[]) reader.getInterfaces())); 38 | 39 | return entries; 40 | } 41 | 42 | public Map getStaticEntries() { 43 | return ((LazyMap) getEntries()).getStaticEntries(); 44 | } 45 | 46 | public Map getLazyEntries() { 47 | return ((LazyMap) getEntries()).getLazyEntries(); 48 | } 49 | 50 | private static Map getLazyEntries(ClassReader reader, Context context, 51 | int index) { 52 | ChasmClassVisitor visitor = new ChasmClassVisitor(context); 53 | reader.accept(visitor, 0); 54 | Map entries = visitor.getClassNode().getEntries(); 55 | 56 | PathMetadata root = new PathMetadata(null, index); 57 | for (Map.Entry entry : entries.entrySet()) { 58 | PathMetadata path = new PathMetadata(root, entry.getKey()); 59 | PathInitializer.initialize(entry.getValue(), path); 60 | } 61 | 62 | return entries; 63 | } 64 | 65 | public ClassReader getClassReader() { 66 | return reader; 67 | } 68 | 69 | @Override 70 | public void resolve(Resolver resolver) { 71 | // Class node can't contain references 72 | } 73 | 74 | @Override 75 | public Node evaluate(Evaluator evaluator) { 76 | // Class node can't be further simplified 77 | return this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/LazyMap.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.tree; 2 | 3 | import java.lang.ref.SoftReference; 4 | import java.util.Collection; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.function.Supplier; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public class LazyMap implements Map { 13 | private final Map staticEntries; 14 | private final Supplier> lazyEntriesSupplier; 15 | private SoftReference> lazyEntries = new SoftReference<>(null); 16 | 17 | public LazyMap(Map staticEntries, Supplier> lazyEntriesSupplier) { 18 | this.staticEntries = staticEntries; 19 | this.lazyEntriesSupplier = lazyEntriesSupplier; 20 | } 21 | 22 | public Map getStaticEntries() { 23 | return staticEntries; 24 | } 25 | 26 | public Map getLazyEntries() { 27 | Map entries = lazyEntries.get(); 28 | 29 | if (entries == null) { 30 | entries = lazyEntriesSupplier.get(); 31 | lazyEntries = new SoftReference<>(entries); 32 | } 33 | 34 | return entries; 35 | } 36 | 37 | @Override 38 | public int size() { 39 | return getLazyEntries().size(); 40 | } 41 | 42 | @Override 43 | public boolean isEmpty() { 44 | return staticEntries.isEmpty() && getLazyEntries().isEmpty(); 45 | } 46 | 47 | @Override 48 | public boolean containsKey(Object key) { 49 | return staticEntries.containsKey(key) || getLazyEntries().containsKey(key); 50 | } 51 | 52 | @Override 53 | public boolean containsValue(Object value) { 54 | return staticEntries.containsValue(value) || getLazyEntries().containsValue(value); 55 | } 56 | 57 | @Override 58 | public V get(Object key) { 59 | V val = staticEntries.get(key); 60 | 61 | if (val == null) { 62 | val = getLazyEntries().get(key); 63 | } 64 | 65 | return val; 66 | } 67 | 68 | @Nullable 69 | @Override 70 | public V put(K key, V value) { 71 | throw new UnsupportedOperationException("LazyMap is immutable."); 72 | } 73 | 74 | @Override 75 | public V remove(Object key) { 76 | throw new UnsupportedOperationException("LazyMap is immutable."); 77 | } 78 | 79 | @Override 80 | public void putAll(@NotNull Map m) { 81 | throw new UnsupportedOperationException("LazyMap is immutable."); 82 | } 83 | 84 | @Override 85 | public void clear() { 86 | throw new UnsupportedOperationException("LazyMap is immutable."); 87 | } 88 | 89 | @NotNull 90 | @Override 91 | public Set keySet() { 92 | return getLazyEntries().keySet(); 93 | } 94 | 95 | @NotNull 96 | @Override 97 | public Collection values() { 98 | return getLazyEntries().values(); 99 | } 100 | 101 | @NotNull 102 | @Override 103 | public Set> entrySet() { 104 | return getLazyEntries().entrySet(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.tree; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/reader/AnnotationNodeReader.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.tree.reader; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.AnnotationVisitor; 6 | import org.objectweb.asm.TypePath; 7 | import org.quiltmc.chasm.internal.util.NodeConstants; 8 | import org.quiltmc.chasm.internal.util.NodeUtils; 9 | import org.quiltmc.chasm.lang.api.ast.ListNode; 10 | import org.quiltmc.chasm.lang.api.ast.MapNode; 11 | import org.quiltmc.chasm.lang.api.ast.Node; 12 | 13 | public class AnnotationNodeReader { 14 | private final Node annotationNode; 15 | 16 | public AnnotationNodeReader(Node annotationNode) { 17 | this.annotationNode = annotationNode; 18 | } 19 | 20 | public static void visitAnnotationValue(AnnotationVisitor visitor, String name, Node node) { 21 | if (node instanceof ListNode) { 22 | AnnotationVisitor arrayVisitor = visitor.visitArray(name); 23 | 24 | for (Node entry : NodeUtils.asList(node).getEntries()) { 25 | visitAnnotationValue(arrayVisitor, null, entry); 26 | } 27 | arrayVisitor.visitEnd(); 28 | } else if (node instanceof MapNode) { 29 | String type = NodeUtils.getAsString(node, NodeConstants.TYPE); 30 | String descriptor = NodeUtils.getAsString(node, NodeConstants.DESCRIPTOR); 31 | MapNode annotationValues = NodeUtils.getAsMap(node, NodeConstants.VALUES); 32 | String enumValue = NodeUtils.getAsString(node, NodeConstants.VALUE); 33 | 34 | if (type != null) { 35 | visitor.visit(name, NodeUtils.fromValueNode(node)); 36 | } else if (descriptor != null && annotationValues != null) { 37 | AnnotationVisitor annotationVisitor = visitor.visitAnnotation(name, descriptor); 38 | visitValues(annotationVisitor, annotationValues); 39 | } else if (descriptor != null && enumValue != null) { 40 | visitor.visitEnum(name, descriptor, enumValue); 41 | } else { 42 | throw new RuntimeException("Invalid annotation value: " + node); 43 | } 44 | } else { 45 | throw new RuntimeException("Invalid annotation value: " + node); 46 | } 47 | } 48 | 49 | public static void visitValues(AnnotationVisitor visitor, Node node) { 50 | if (node instanceof MapNode) { 51 | for (Map.Entry entry : NodeUtils.asMap(node).getEntries().entrySet()) { 52 | visitAnnotationValue(visitor, entry.getKey(), entry.getValue()); 53 | } 54 | } else if (node instanceof ListNode) { 55 | for (Node entry : NodeUtils.asList(node).getEntries()) { 56 | visitAnnotationValue(visitor, null, entry); 57 | } 58 | } else { 59 | throw new RuntimeException("Invalid annotation values: " + node); 60 | } 61 | 62 | visitor.visitEnd(); 63 | } 64 | 65 | public void accept(AnnotationVisitorFactory factory, TypeAnnotationVisitorFactory typeFactory) { 66 | String descriptor = NodeUtils.getAsString(annotationNode, NodeConstants.DESCRIPTOR); 67 | boolean visible = NodeUtils.getAsBoolean(annotationNode, NodeConstants.VISIBLE); 68 | Long typeRef = NodeUtils.getAsLong(annotationNode, NodeConstants.TYPE_REF); 69 | String typePath = NodeUtils.getAsString(annotationNode, NodeConstants.TYPE_PATH); 70 | Node values = NodeUtils.get(annotationNode, NodeConstants.VALUES); 71 | 72 | AnnotationVisitor visitor; 73 | if (typePath != null) { 74 | visitor = typeFactory.create(typeRef.intValue(), TypePath.fromString(typePath), descriptor, visible); 75 | } else { 76 | visitor = factory.create(descriptor, visible); 77 | } 78 | 79 | visitValues(visitor, values); 80 | } 81 | 82 | public interface AnnotationVisitorFactory { 83 | AnnotationVisitor create(String descriptor, boolean visible); 84 | } 85 | 86 | public interface TypeAnnotationVisitorFactory { 87 | AnnotationVisitor create(int typeRef, TypePath typePath, String descriptor, boolean visible); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/reader/FieldNodeReader.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.tree.reader; 2 | 3 | import org.objectweb.asm.ClassVisitor; 4 | import org.objectweb.asm.FieldVisitor; 5 | import org.quiltmc.chasm.internal.util.NodeConstants; 6 | import org.quiltmc.chasm.internal.util.NodeUtils; 7 | import org.quiltmc.chasm.lang.api.ast.ListNode; 8 | import org.quiltmc.chasm.lang.api.ast.MapNode; 9 | import org.quiltmc.chasm.lang.api.ast.Node; 10 | 11 | public class FieldNodeReader { 12 | private final MapNode fieldNode; 13 | 14 | public FieldNodeReader(MapNode fieldNode) { 15 | this.fieldNode = fieldNode; 16 | } 17 | 18 | private void visitAnnotations(FieldVisitor visitor) { 19 | ListNode annotationsListNode = NodeUtils.getAsList(fieldNode, NodeConstants.ANNOTATIONS); 20 | if (annotationsListNode == null) { 21 | return; 22 | } 23 | for (Node n : annotationsListNode.getEntries()) { 24 | AnnotationNodeReader reader = new AnnotationNodeReader(n); 25 | reader.accept(visitor::visitAnnotation, visitor::visitTypeAnnotation); 26 | } 27 | } 28 | 29 | public void visitField(ClassVisitor visitor) { 30 | int access = NodeUtils.getAsInt(fieldNode, NodeConstants.ACCESS); 31 | String name = NodeUtils.getAsString(fieldNode, NodeConstants.NAME); 32 | String descriptor = NodeUtils.getAsString(fieldNode, NodeConstants.DESCRIPTOR); 33 | 34 | String signature = NodeUtils.getAsString(fieldNode, NodeConstants.SIGNATURE); 35 | 36 | Node valueNode = NodeUtils.get(fieldNode, NodeConstants.VALUE); 37 | Object value = valueNode == null ? null : NodeUtils.fromValueNode(valueNode); 38 | 39 | FieldVisitor fieldVisitor = visitor.visitField(access, name, descriptor, signature, value); 40 | 41 | // visitAnnotation/visitTypeAnnotation 42 | visitAnnotations(fieldVisitor); 43 | 44 | // visitEnd 45 | fieldVisitor.visitEnd(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/reader/RecordComponentNodeReader.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.tree.reader; 2 | 3 | import org.objectweb.asm.ClassVisitor; 4 | import org.objectweb.asm.RecordComponentVisitor; 5 | import org.quiltmc.chasm.internal.util.NodeConstants; 6 | import org.quiltmc.chasm.internal.util.NodeUtils; 7 | import org.quiltmc.chasm.lang.api.ast.ListNode; 8 | import org.quiltmc.chasm.lang.api.ast.MapNode; 9 | import org.quiltmc.chasm.lang.api.ast.Node; 10 | 11 | public class RecordComponentNodeReader { 12 | private final MapNode componentNode; 13 | 14 | public RecordComponentNodeReader(MapNode componentNode) { 15 | this.componentNode = componentNode; 16 | } 17 | 18 | private void visitAnnotations(RecordComponentVisitor visitor) { 19 | ListNode annotationsListNode = NodeUtils.getAsList(componentNode, NodeConstants.ANNOTATIONS); 20 | if (annotationsListNode == null) { 21 | return; 22 | } 23 | for (Node n : annotationsListNode.getEntries()) { 24 | AnnotationNodeReader reader = new AnnotationNodeReader(n); 25 | reader.accept(visitor::visitAnnotation, visitor::visitTypeAnnotation); 26 | } 27 | } 28 | 29 | public void visitRecordComponent(ClassVisitor visitor) { 30 | String name = NodeUtils.getAsString(componentNode, NodeConstants.NAME); 31 | String descriptor = NodeUtils.getAsString(componentNode, NodeConstants.DESCRIPTOR); 32 | String signature = NodeUtils.getAsString(componentNode, NodeConstants.SIGNATURE); 33 | 34 | RecordComponentVisitor componentVisitor = visitor.visitRecordComponent(name, descriptor, signature); 35 | 36 | // visitAnnotation/visitTypeAnnotation 37 | visitAnnotations(componentVisitor); 38 | 39 | // visitEnd 40 | componentVisitor.visitEnd(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/tree/reader/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.tree.reader; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/util/ChasmEnvironment.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.util; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.nio.file.FileSystem; 6 | import java.nio.file.FileSystems; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.Iterator; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.stream.Stream; 16 | 17 | import org.quiltmc.chasm.api.Transformer; 18 | import org.quiltmc.chasm.api.util.Context; 19 | import org.quiltmc.chasm.internal.transformer.ChasmLangTransformer; 20 | import org.quiltmc.chasm.lang.api.ast.Node; 21 | 22 | public class ChasmEnvironment implements Closeable { 23 | private final Context context; 24 | 25 | private final List rootDirectories = new ArrayList<>(); 26 | 27 | private final List toClose = new ArrayList<>(); 28 | 29 | public ChasmEnvironment(Context context) { 30 | this.context = context; 31 | } 32 | 33 | /** 34 | * Add a directory or jar to the classpath. 35 | * 36 | * @param path A path to a directory or jar file, representing the classpath entry. 37 | */ 38 | public void addToClasspath(Path path) throws IOException { 39 | if (Files.isRegularFile(path) && path.toString().endsWith(".jar")) { 40 | FileSystem fileSystem = FileSystems.newFileSystem(path, (ClassLoader) null); 41 | fileSystem.getRootDirectories().forEach(rootDirectories::add); 42 | toClose.add(fileSystem); 43 | } else if (Files.isDirectory(path)) { 44 | rootDirectories.add(path); 45 | } else { 46 | throw new IllegalArgumentException("Path must be either jar or directory: " + path); 47 | } 48 | } 49 | 50 | public Collection createTransformers() throws IOException { 51 | 52 | Map transformers = new LinkedHashMap<>(); 53 | 54 | for (Path rootDirectory : rootDirectories) { 55 | Path transformerRoot = rootDirectory.resolve("org/quiltmc/chasm/transformers"); 56 | Stream fileStream = Files.find(transformerRoot, Integer.MAX_VALUE, 57 | (path, attr) -> attr.isRegularFile() && path.toString().endsWith(".chasm") 58 | ); 59 | Iterator chasmFiles = fileStream.iterator(); 60 | while (chasmFiles.hasNext()) { 61 | Path path = chasmFiles.next(); 62 | String id = transformerRoot.relativize(path).toString(); 63 | Node node = Node.parse(path); 64 | ChasmLangTransformer transformer = new ChasmLangTransformer(id, node, context); 65 | Transformer previous = transformers.put(id, transformer); 66 | if (previous != null) { 67 | throw new RuntimeException("Duplicate chasm transformer: " + id); 68 | } 69 | } 70 | fileStream.close(); 71 | } 72 | 73 | return transformers.values(); 74 | } 75 | 76 | public void collectClasses() { 77 | for (Path rootDirectory : rootDirectories) { 78 | 79 | } 80 | } 81 | 82 | private int getJavaVersion() { 83 | String versionString = System.getProperty("java.version"); 84 | String[] parts = versionString.split("\\."); 85 | if (parts[0].equals("1")) { 86 | // Pre java 9 87 | return Integer.parseInt(parts[1]); 88 | } else { 89 | return Integer.parseInt(parts[0]); 90 | } 91 | } 92 | 93 | @Override 94 | public void close() throws IOException { 95 | for (Closeable closeable : toClose) { 96 | closeable.close(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/util/PathInitializer.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.internal.util; 2 | 3 | import java.util.Map; 4 | 5 | import org.quiltmc.chasm.internal.metadata.PathMetadata; 6 | import org.quiltmc.chasm.internal.tree.ClassNode; 7 | import org.quiltmc.chasm.lang.api.ast.ListNode; 8 | import org.quiltmc.chasm.lang.api.ast.MapNode; 9 | import org.quiltmc.chasm.lang.api.ast.Node; 10 | 11 | public abstract class PathInitializer { 12 | private PathInitializer() { 13 | } 14 | 15 | public static void initialize(Node root, PathMetadata path) { 16 | // Set the path for the root 17 | root.getMetadata().put(PathMetadata.class, path); 18 | 19 | if (root instanceof ClassNode) { 20 | ClassNode lazyClassNode = (ClassNode) root; 21 | 22 | // Recursively set the path for all non-lazy entries 23 | for (Map.Entry entry : lazyClassNode.getStaticEntries().entrySet()) { 24 | initialize(entry.getValue(), new PathMetadata(path, entry.getKey())); 25 | } 26 | } else if (root instanceof MapNode) { 27 | MapNode mapNode = (MapNode) root; 28 | 29 | // Recursively set the path for all entries 30 | for (Map.Entry entry : mapNode.getEntries().entrySet()) { 31 | initialize(entry.getValue(), new PathMetadata(path, entry.getKey())); 32 | } 33 | } else if (root instanceof ListNode) { 34 | ListNode listNode = (ListNode) root; 35 | 36 | // Recursively set the path for all entries 37 | for (int i = 0; i < listNode.size(); i++) { 38 | initialize(listNode.get(i), new PathMetadata(path, i)); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chasm/src/main/java/org/quiltmc/chasm/internal/util/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.internal.util; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chasm/src/test/java/org/quiltmc/chasm/IntrinsicsTest.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | import org.jetbrains.annotations.Nullable; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.quiltmc.chasm.api.util.ClassInfo; 9 | import org.quiltmc.chasm.api.util.Context; 10 | import org.quiltmc.chasm.internal.intrinsic.ChasmIntrinsics; 11 | import org.quiltmc.chasm.internal.util.NodeUtils; 12 | import org.quiltmc.chasm.lang.api.ast.IntegerNode; 13 | import org.quiltmc.chasm.lang.api.ast.ListNode; 14 | import org.quiltmc.chasm.lang.api.ast.MapNode; 15 | import org.quiltmc.chasm.lang.api.ast.Node; 16 | import org.quiltmc.chasm.lang.api.ast.NullNode; 17 | import org.quiltmc.chasm.lang.api.ast.StringNode; 18 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 19 | 20 | public class IntrinsicsTest { 21 | private Node evaluate(String chassembly) { 22 | Context context = new Context() { 23 | @Override 24 | public @Nullable ClassInfo getClassInfo(String className) { 25 | try { 26 | return ClassInfo.fromClass(Class.forName(className, false, getClass().getClassLoader())); 27 | } catch (ClassNotFoundException e) { 28 | return null; 29 | } 30 | } 31 | 32 | @Override 33 | public byte @Nullable [] readFile(String path) { 34 | return switch (path) { 35 | case "hello.txt" -> "Hello World!".getBytes(StandardCharsets.UTF_8); 36 | case "hello.chasm" -> "file_content(\"hello.txt\")".getBytes(StandardCharsets.UTF_8); 37 | case "lib.chasm" -> "{inc: val -> val + 1, result: inc(2)}".getBytes(StandardCharsets.UTF_8); 38 | case "lib_invalid.chasm" -> "{invalid: outer}".getBytes(StandardCharsets.UTF_8); 39 | default -> null; 40 | }; 41 | } 42 | }; 43 | Node node = Node.parse(chassembly); 44 | return node.evaluate(ChasmIntrinsics.makeEvaluator(node, context)); 45 | } 46 | 47 | @Test 48 | public void testFileBytes() { 49 | ListNode bytes = (ListNode) evaluate("file_bytes(\"hello.txt\")"); 50 | int[] byteArray = bytes.getEntries().stream() 51 | .mapToInt(NodeUtils::asInt).toArray(); 52 | Assertions.assertArrayEquals(new int[] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 }, byteArray); 53 | } 54 | 55 | @Test 56 | public void testFileContent() { 57 | StringNode string = (StringNode) evaluate("file_content(\"hello.txt\")"); 58 | Assertions.assertEquals("Hello World!", string.getValue()); 59 | } 60 | 61 | @Test 62 | public void testInclude() { 63 | StringNode string = (StringNode) evaluate("include(\"hello.chasm\")"); 64 | Assertions.assertEquals("Hello World!", string.getValue()); 65 | } 66 | 67 | @Test 68 | public void testFileContentInvalid() { 69 | Node result = evaluate("file_content(\"doesn't exist\")"); 70 | Assertions.assertInstanceOf(NullNode.class, result); 71 | } 72 | 73 | @Test 74 | public void testLib1() { 75 | MapNode result = (MapNode) evaluate("{lib: include(\"lib.chasm\"), result: lib.inc(41)}"); 76 | Assertions.assertEquals(42, ((IntegerNode) result.get("result")).getValue()); 77 | } 78 | 79 | @Test 80 | public void testLib2() { 81 | MapNode result = (MapNode) evaluate("{lib: include(\"lib.chasm\"), result: lib.result}"); 82 | Assertions.assertEquals(3, ((IntegerNode) result.get("result")).getValue()); 83 | } 84 | 85 | @Test 86 | public void testIncludeCannotResolveOuter() { 87 | Assertions.assertThrows(EvaluationException.class, () -> { 88 | evaluate("{outer: 42, lib: include(\"lib_invalid.chasm\")}"); 89 | }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /chasm/src/test/java/org/quiltmc/chasm/TransformedTests.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm; 2 | 3 | /** 4 | * Tests for checking transformations. 5 | */ 6 | public class TransformedTests extends TestsBase { 7 | @Override 8 | protected void registerAll() { 9 | register("empty/EmptyClass", "add/field_to_empty", "add_field"); 10 | register("empty/EmptyClass", "add/method_to_empty", "add_method"); 11 | register("empty/EmptyClass", "add/fields_to_empty", "add_field", "add_field_2"); 12 | 13 | register("other/TestLocalVariables", "other/test_local_variables", "test_local_variables"); 14 | register("other/TestMergeInsns", "other/test_merge_insns", "test_merge_insns"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chasm/src/test/java/org/quiltmc/chasm/UnchangedTests.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm; 2 | 3 | /** 4 | * Tests for checking that converting a class into Chasm's internal representation and back don't change it. 5 | */ 6 | public class UnchangedTests extends TestsBase { 7 | @Override 8 | protected void registerAll() { 9 | // Empty classes 10 | register("empty/EmptyClass", "unchanged/EmptyClass", "touch"); 11 | register("empty/EmptyInterface", "unchanged/EmptyInterface", "touch"); 12 | register("empty/EmptyEnum", "unchanged/EmptyEnum", "touch"); 13 | register("empty/EmptyAnnotation", "unchanged/EmptyAnnotation", "touch"); 14 | register("empty/EmptyRecord", "unchanged/EmptyRecord", "touch"); 15 | register("empty/EmptyOuterClass", "unchanged/EmptyOuterClass", "touch"); 16 | register("empty/EmptyOuterClass$EmptyStaticNestedClass", "unchanged/EmptyOuterClass$EmptyStaticNestedClass", 17 | "touch"); 18 | register("empty/EmptyOuterClass$EmptyInnerClass", "unchanged/EmptyOuterClass$EmptyInnerClass", "touch"); 19 | register("empty/EmptySealedClass", "unchanged/EmptySealedClass", "touch"); 20 | register("empty/EmptySealedExtendsClass", "unchanged/EmptySealedExtendsClass", "touch"); 21 | 22 | register("other/ExampleClass", "unchanged/ExampleClass", "touch"); 23 | register("other/ExampleAnnotation", "unchanged/ExampleAnnotation", "touch"); 24 | register("other/ExampleClass$ExampleRecord", "unchanged/ExampleClass$ExampleRecord", "touch"); 25 | register("other/ExampleEnum", "unchanged/ExampleEnum", "touch"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptyAnnotation.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public @interface EmptyAnnotation { 4 | } 5 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptyClass.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public class EmptyClass { 4 | } 5 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptyEnum.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public enum EmptyEnum { 4 | } 5 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptyInterface.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public interface EmptyInterface { 4 | } 5 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptyOuterClass.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public class EmptyOuterClass { 4 | static class EmptyStaticNestedClass { 5 | } 6 | 7 | class EmptyInnerClass { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptyRecord.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public record EmptyRecord() { 4 | } 5 | -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptySealedClass.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | public sealed class EmptySealedClass permits EmptySealedExtendsClass { 4 | } -------------------------------------------------------------------------------- /chasm/src/testData/java/empty/EmptySealedExtendsClass.java: -------------------------------------------------------------------------------- 1 | package empty; 2 | 3 | final class EmptySealedExtendsClass extends EmptySealedClass { 4 | } -------------------------------------------------------------------------------- /chasm/src/testData/java/other/ExampleAnnotation.java: -------------------------------------------------------------------------------- 1 | package other; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface ExampleAnnotation { 8 | String value() default "Example"; 9 | 10 | String[] list() default { "First", "Second"}; 11 | 12 | SimpleAnnotation nested() default @SimpleAnnotation("Nested"); 13 | } 14 | -------------------------------------------------------------------------------- /chasm/src/testData/java/other/ExampleClass.java: -------------------------------------------------------------------------------- 1 | package other; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.ExecutionException; 5 | 6 | @ExampleAnnotation(value = "Hi", list = { "First", "Second", "Third" }, nested = @SimpleAnnotation("Inner")) 7 | public abstract class ExampleClass { 8 | public float publicField = 2.5f; 9 | public final int publicFinalField = 1; 10 | 11 | @SimpleAnnotation("This is a Field") 12 | private final boolean annotatedPrivateFinalField = true; 13 | 14 | public static double publicStaticField = 1.2d; 15 | public static final char PUBLIC_STATIC_FINAL_FIELD = 'x'; 16 | 17 | public void instanceMethod() { 18 | for (int i = 0; i < this.publicFinalField; i++) { 19 | System.out.println("Hello Chasm!"); 20 | } 21 | } 22 | 23 | public static void publicStaticMethod() { 24 | System.out.println("Static Hello Chasm!"); 25 | int five = 5; 26 | try { 27 | System.out.println("5 = " + 5); 28 | int three = 3; 29 | System.out.println(five + " + " + three + " = " + (five + three)); 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | five = 0; 33 | } 34 | 35 | publicStaticMethod(); 36 | 37 | switch (five) { 38 | case 5: 39 | System.out.println("Still 5"); 40 | break; 41 | default: 42 | System.out.println("No longer 5"); 43 | } 44 | } 45 | 46 | public static String testSwitch() { 47 | return switch ((int) Math.round(Math.random() * 100)) { 48 | case 10 -> { 49 | String nested = "Test"; 50 | yield switch (nested) { 51 | case "NotTest" -> "NotTest"; 52 | default -> throw new IllegalStateException("Unexpected value: " + "Test"); 53 | }; 54 | } 55 | default -> "Not 10"; 56 | }; 57 | } 58 | 59 | public static int testGenerics() throws ExecutionException, InterruptedException { 60 | int output = CompletableFuture.supplyAsync(() -> 5) 61 | .thenApply(i -> Integer.toString(i)) 62 | .thenAccept(System.out::println) 63 | .thenApply(v -> 7) 64 | .get(); 65 | 66 | switch (output) { 67 | case 7: 68 | output = 10; 69 | // fall through 70 | default: 71 | output = 7; 72 | } 73 | 74 | return output; 75 | } 76 | 77 | public abstract void annotationTest(@SimpleAnnotation("first") String first, 78 | @SimpleAnnotation("second") String second); 79 | 80 | public static record ExampleRecord(Integer first, String second) { 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /chasm/src/testData/java/other/ExampleEnum.java: -------------------------------------------------------------------------------- 1 | package other; 2 | 3 | public enum ExampleEnum { 4 | A(1), B(2) { 5 | public String toString() { 6 | return "b"; 7 | } 8 | }; 9 | 10 | private final int value; 11 | 12 | ExampleEnum(int value) { 13 | this.value = value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chasm/src/testData/java/other/SimpleAnnotation.java: -------------------------------------------------------------------------------- 1 | package other; 2 | 3 | public @interface SimpleAnnotation { 4 | String value(); 5 | } 6 | -------------------------------------------------------------------------------- /chasm/src/testData/java/other/TestLocalVariables.java: -------------------------------------------------------------------------------- 1 | package other; 2 | 3 | import java.util.Random; 4 | 5 | public class TestLocalVariables { 6 | static Random random = new Random(0); 7 | 8 | static void staticMethod(int param1, double param2) { 9 | int local1 = random.nextInt(); 10 | local1++; 11 | double local2 = random.nextDouble(); 12 | System.out.println(param1 + param2 + local1 + local2); 13 | } 14 | 15 | void instanceMethod(int param1, double param2) { 16 | int local1 = random.nextInt(); 17 | double local2 = random.nextDouble(); 18 | System.out.println(param1 + param2 + local1 + local2); 19 | System.out.println(this); 20 | } 21 | 22 | static void mergeVariable() { 23 | int local; 24 | if (random.nextBoolean()) { 25 | local = random.nextInt(); 26 | } else { 27 | local = random.nextInt() + 1; 28 | } 29 | System.out.println(local); 30 | local = random.nextInt(); // reassignment, will be separate local 31 | System.out.println(local); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chasm/src/testData/java/other/TestMergeInsns.java: -------------------------------------------------------------------------------- 1 | package other; 2 | 3 | public class TestMergeInsns { 4 | public static void target() { 5 | if (System.getProperty("Dummy Condition") == null) { 6 | System.out.println("Test Target Method"); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chasm/src/testData/results/.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't treat result files as text files, i.e. don't normalize line endings 2 | *.result -text 3 | -------------------------------------------------------------------------------- /chasm/src/testData/results/add/field_to_empty.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class empty/EmptyClass { 4 | 5 | // compiled from: EmptyClass.java 6 | 7 | // access flags 0x1 8 | public I field1 9 | 10 | // access flags 0x1 11 | public ()V 12 | L0 13 | LINENUMBER 3 L0 14 | ALOAD 0 15 | INVOKESPECIAL java/lang/Object. ()V 16 | RETURN 17 | L1 18 | LOCALVARIABLE this Lempty/EmptyClass; L0 L1 0 19 | MAXSTACK = 1 20 | MAXLOCALS = 1 21 | } 22 | -------------------------------------------------------------------------------- /chasm/src/testData/results/add/fields_to_empty.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class empty/EmptyClass { 4 | 5 | // compiled from: EmptyClass.java 6 | 7 | // access flags 0x1 8 | public I field1 9 | 10 | // access flags 0x1 11 | public I field2 12 | 13 | // access flags 0x1 14 | public ()V 15 | L0 16 | LINENUMBER 3 L0 17 | ALOAD 0 18 | INVOKESPECIAL java/lang/Object. ()V 19 | RETURN 20 | L1 21 | LOCALVARIABLE this Lempty/EmptyClass; L0 L1 0 22 | MAXSTACK = 1 23 | MAXLOCALS = 1 24 | } 25 | -------------------------------------------------------------------------------- /chasm/src/testData/results/add/method_to_empty.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class empty/EmptyClass { 4 | 5 | // compiled from: EmptyClass.java 6 | 7 | // access flags 0x1 8 | public returnThis()LTestClass; 9 | ALOAD 0 10 | ARETURN 11 | MAXSTACK = 1 12 | MAXLOCALS = 1 13 | 14 | // access flags 0x1 15 | public ()V 16 | L0 17 | LINENUMBER 3 L0 18 | ALOAD 0 19 | INVOKESPECIAL java/lang/Object. ()V 20 | RETURN 21 | L1 22 | LOCALVARIABLE this Lempty/EmptyClass; L0 L1 0 23 | MAXSTACK = 1 24 | MAXLOCALS = 1 25 | } 26 | -------------------------------------------------------------------------------- /chasm/src/testData/results/other/test_merge_insns.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class other/TestMergeInsns { 4 | 5 | // compiled from: TestMergeInsns.java 6 | 7 | // access flags 0x1 8 | public ()V 9 | L0 10 | LINENUMBER 3 L0 11 | ALOAD 0 12 | INVOKESPECIAL java/lang/Object. ()V 13 | RETURN 14 | L1 15 | LOCALVARIABLE this Lother/TestMergeInsns; L0 L1 0 16 | MAXSTACK = 1 17 | MAXLOCALS = 1 18 | 19 | // access flags 0x9 20 | public static target()V 21 | L0 22 | LINENUMBER 5 L0 23 | LDC "Dummy Condition" 24 | INVOKESTATIC java/lang/System.getProperty (Ljava/lang/String;)Ljava/lang/String; 25 | IFNONNULL L1 26 | L2 27 | LINENUMBER 6 L2 28 | GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 29 | LDC "Test Target Method" 30 | INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 31 | L1 32 | LINENUMBER 8 L1 33 | FRAME SAME 34 | GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 35 | LDC "Hello Merged World" 36 | INVOKESTATIC java/io/PrintStream.println (Z)V 37 | RETURN 38 | MAXSTACK = 2 39 | MAXLOCALS = 0 40 | } 41 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyAnnotation.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x2601 3 | public abstract @interface empty/EmptyAnnotation implements java/lang/annotation/Annotation { 4 | 5 | // compiled from: EmptyAnnotation.java 6 | } 7 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyClass.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class empty/EmptyClass { 4 | 5 | // compiled from: EmptyClass.java 6 | 7 | // access flags 0x1 8 | public ()V 9 | L0 10 | LINENUMBER 3 L0 11 | ALOAD 0 12 | INVOKESPECIAL java/lang/Object. ()V 13 | RETURN 14 | L1 15 | LOCALVARIABLE this Lempty/EmptyClass; L0 L1 0 16 | MAXSTACK = 1 17 | MAXLOCALS = 1 18 | } 19 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyEnum.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x4031 3 | // signature Ljava/lang/Enum; 4 | // declaration: empty/EmptyEnum extends java.lang.Enum 5 | public final enum empty/EmptyEnum extends java/lang/Enum { 6 | 7 | // compiled from: EmptyEnum.java 8 | 9 | // access flags 0x101A 10 | private final static synthetic [Lempty/EmptyEnum; $VALUES 11 | 12 | // access flags 0x9 13 | public static values()[Lempty/EmptyEnum; 14 | L0 15 | LINENUMBER 3 L0 16 | GETSTATIC empty/EmptyEnum.$VALUES : [Lempty/EmptyEnum; 17 | INVOKEVIRTUAL [Lempty/EmptyEnum;.clone ()Ljava/lang/Object; 18 | CHECKCAST [Lempty/EmptyEnum; 19 | ARETURN 20 | MAXSTACK = 1 21 | MAXLOCALS = 0 22 | 23 | // access flags 0x9 24 | public static valueOf(Ljava/lang/String;)Lempty/EmptyEnum; 25 | // parameter name 26 | L0 27 | LINENUMBER 3 L0 28 | LDC Lempty/EmptyEnum;.class 29 | ALOAD 0 30 | INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 31 | CHECKCAST empty/EmptyEnum 32 | ARETURN 33 | L1 34 | LOCALVARIABLE name Ljava/lang/String; L0 L1 0 35 | MAXSTACK = 2 36 | MAXLOCALS = 1 37 | 38 | // access flags 0x2 39 | // signature ()V 40 | // declaration: void () 41 | private (Ljava/lang/String;I)V 42 | // parameter 43 | // parameter 44 | L0 45 | LINENUMBER 3 L0 46 | ALOAD 0 47 | ALOAD 1 48 | ILOAD 2 49 | INVOKESPECIAL java/lang/Enum. (Ljava/lang/String;I)V 50 | RETURN 51 | L1 52 | LOCALVARIABLE this Lempty/EmptyEnum; L0 L1 0 53 | MAXSTACK = 3 54 | MAXLOCALS = 3 55 | 56 | // access flags 0x100A 57 | private static synthetic $values()[Lempty/EmptyEnum; 58 | L0 59 | LINENUMBER 3 L0 60 | ICONST_0 61 | ANEWARRAY empty/EmptyEnum 62 | ARETURN 63 | MAXSTACK = 1 64 | MAXLOCALS = 0 65 | 66 | // access flags 0x8 67 | static ()V 68 | L0 69 | LINENUMBER 3 L0 70 | INVOKESTATIC empty/EmptyEnum.$values ()[Lempty/EmptyEnum; 71 | PUTSTATIC empty/EmptyEnum.$VALUES : [Lempty/EmptyEnum; 72 | RETURN 73 | MAXSTACK = 1 74 | MAXLOCALS = 0 75 | } 76 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyInterface.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x601 3 | public abstract interface empty/EmptyInterface { 4 | 5 | // compiled from: EmptyInterface.java 6 | } 7 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyOuterClass$EmptyInnerClass.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x20 3 | class empty/EmptyOuterClass$EmptyInnerClass { 4 | 5 | // compiled from: EmptyOuterClass.java 6 | NESTHOST empty/EmptyOuterClass 7 | // access flags 0x0 8 | INNERCLASS empty/EmptyOuterClass$EmptyInnerClass empty/EmptyOuterClass EmptyInnerClass 9 | 10 | // access flags 0x1010 11 | final synthetic Lempty/EmptyOuterClass; this$0 12 | 13 | // access flags 0x0 14 | (Lempty/EmptyOuterClass;)V 15 | // parameter this$0 16 | L0 17 | LINENUMBER 7 L0 18 | ALOAD 0 19 | ALOAD 1 20 | PUTFIELD empty/EmptyOuterClass$EmptyInnerClass.this$0 : Lempty/EmptyOuterClass; 21 | ALOAD 0 22 | INVOKESPECIAL java/lang/Object. ()V 23 | RETURN 24 | L1 25 | LOCALVARIABLE this Lempty/EmptyOuterClass$EmptyInnerClass; L0 L1 0 26 | LOCALVARIABLE this$0 Lempty/EmptyOuterClass; L0 L1 1 27 | MAXSTACK = 2 28 | MAXLOCALS = 2 29 | } 30 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyOuterClass$EmptyStaticNestedClass.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x20 3 | class empty/EmptyOuterClass$EmptyStaticNestedClass { 4 | 5 | // compiled from: EmptyOuterClass.java 6 | NESTHOST empty/EmptyOuterClass 7 | // access flags 0x8 8 | static INNERCLASS empty/EmptyOuterClass$EmptyStaticNestedClass empty/EmptyOuterClass EmptyStaticNestedClass 9 | 10 | // access flags 0x0 11 | ()V 12 | L0 13 | LINENUMBER 4 L0 14 | ALOAD 0 15 | INVOKESPECIAL java/lang/Object. ()V 16 | RETURN 17 | L1 18 | LOCALVARIABLE this Lempty/EmptyOuterClass$EmptyStaticNestedClass; L0 L1 0 19 | MAXSTACK = 1 20 | MAXLOCALS = 1 21 | } 22 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyOuterClass.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class empty/EmptyOuterClass { 4 | 5 | // compiled from: EmptyOuterClass.java 6 | NESTMEMBER empty/EmptyOuterClass$EmptyInnerClass 7 | NESTMEMBER empty/EmptyOuterClass$EmptyStaticNestedClass 8 | // access flags 0x0 9 | INNERCLASS empty/EmptyOuterClass$EmptyInnerClass empty/EmptyOuterClass EmptyInnerClass 10 | // access flags 0x8 11 | static INNERCLASS empty/EmptyOuterClass$EmptyStaticNestedClass empty/EmptyOuterClass EmptyStaticNestedClass 12 | 13 | // access flags 0x1 14 | public ()V 15 | L0 16 | LINENUMBER 3 L0 17 | ALOAD 0 18 | INVOKESPECIAL java/lang/Object. ()V 19 | RETURN 20 | L1 21 | LOCALVARIABLE this Lempty/EmptyOuterClass; L0 L1 0 22 | MAXSTACK = 1 23 | MAXLOCALS = 1 24 | } 25 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptyRecord.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // RECORD 3 | // access flags 0x10031 4 | public final class empty/EmptyRecord extends java/lang/Record { 5 | 6 | // compiled from: EmptyRecord.java 7 | // access flags 0x19 8 | public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup 9 | 10 | // access flags 0x1 11 | public ()V 12 | L0 13 | LINENUMBER 3 L0 14 | ALOAD 0 15 | INVOKESPECIAL java/lang/Record. ()V 16 | RETURN 17 | L1 18 | LOCALVARIABLE this Lempty/EmptyRecord; L0 L1 0 19 | MAXSTACK = 1 20 | MAXLOCALS = 1 21 | 22 | // access flags 0x11 23 | public final toString()Ljava/lang/String; 24 | L0 25 | LINENUMBER 3 L0 26 | ALOAD 0 27 | INVOKEDYNAMIC toString(Lempty/EmptyRecord;)Ljava/lang/String; [ 28 | // handle kind 0x6 : INVOKESTATIC 29 | java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object; 30 | // arguments: 31 | empty.EmptyRecord.class, 32 | "" 33 | ] 34 | ARETURN 35 | L1 36 | LOCALVARIABLE this Lempty/EmptyRecord; L0 L1 0 37 | MAXSTACK = 1 38 | MAXLOCALS = 1 39 | 40 | // access flags 0x11 41 | public final hashCode()I 42 | L0 43 | LINENUMBER 3 L0 44 | ALOAD 0 45 | INVOKEDYNAMIC hashCode(Lempty/EmptyRecord;)I [ 46 | // handle kind 0x6 : INVOKESTATIC 47 | java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object; 48 | // arguments: 49 | empty.EmptyRecord.class, 50 | "" 51 | ] 52 | IRETURN 53 | L1 54 | LOCALVARIABLE this Lempty/EmptyRecord; L0 L1 0 55 | MAXSTACK = 1 56 | MAXLOCALS = 1 57 | 58 | // access flags 0x11 59 | public final equals(Ljava/lang/Object;)Z 60 | // parameter o 61 | L0 62 | LINENUMBER 3 L0 63 | ALOAD 0 64 | ALOAD 1 65 | INVOKEDYNAMIC equals(Lempty/EmptyRecord;Ljava/lang/Object;)Z [ 66 | // handle kind 0x6 : INVOKESTATIC 67 | java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object; 68 | // arguments: 69 | empty.EmptyRecord.class, 70 | "" 71 | ] 72 | IRETURN 73 | L1 74 | LOCALVARIABLE this Lempty/EmptyRecord; L0 L1 0 75 | LOCALVARIABLE o Ljava/lang/Object; L0 L1 1 76 | MAXSTACK = 2 77 | MAXLOCALS = 2 78 | } 79 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptySealedClass.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x21 3 | public class empty/EmptySealedClass { 4 | 5 | // compiled from: EmptySealedClass.java 6 | PERMITTEDSUBCLASS empty/EmptySealedExtendsClass 7 | 8 | // access flags 0x1 9 | public ()V 10 | L0 11 | LINENUMBER 3 L0 12 | ALOAD 0 13 | INVOKESPECIAL java/lang/Object. ()V 14 | RETURN 15 | L1 16 | LOCALVARIABLE this Lempty/EmptySealedClass; L0 L1 0 17 | MAXSTACK = 1 18 | MAXLOCALS = 1 19 | } 20 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/EmptySealedExtendsClass.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x30 3 | final class empty/EmptySealedExtendsClass extends empty/EmptySealedClass { 4 | 5 | // compiled from: EmptySealedExtendsClass.java 6 | 7 | // access flags 0x0 8 | ()V 9 | L0 10 | LINENUMBER 3 L0 11 | ALOAD 0 12 | INVOKESPECIAL empty/EmptySealedClass. ()V 13 | RETURN 14 | L1 15 | LOCALVARIABLE this Lempty/EmptySealedExtendsClass; L0 L1 0 16 | MAXSTACK = 1 17 | MAXLOCALS = 1 18 | } 19 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/ExampleAnnotation.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x2601 3 | public abstract @interface other/ExampleAnnotation implements java/lang/annotation/Annotation { 4 | 5 | // compiled from: ExampleAnnotation.java 6 | 7 | @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME) 8 | 9 | // access flags 0x401 10 | public abstract value()Ljava/lang/String; 11 | default="Example" 12 | 13 | // access flags 0x401 14 | public abstract list()[Ljava/lang/String; 15 | default={"First", "Second"} 16 | 17 | // access flags 0x401 18 | public abstract nested()Lother/SimpleAnnotation; 19 | default=@Lother/SimpleAnnotation;(value="Nested") 20 | } 21 | -------------------------------------------------------------------------------- /chasm/src/testData/results/unchanged/ExampleEnum.result: -------------------------------------------------------------------------------- 1 | // class version 61.0 (61) 2 | // access flags 0x4021 3 | // signature Ljava/lang/Enum; 4 | // declaration: other/ExampleEnum extends java.lang.Enum 5 | public enum other/ExampleEnum extends java/lang/Enum { 6 | 7 | // compiled from: ExampleEnum.java 8 | NESTMEMBER other/ExampleEnum$1 9 | PERMITTEDSUBCLASS other/ExampleEnum$1 10 | // access flags 0x4010 11 | final enum INNERCLASS other/ExampleEnum$1 null null 12 | 13 | // access flags 0x4019 14 | public final static enum Lother/ExampleEnum; A 15 | 16 | // access flags 0x4019 17 | public final static enum Lother/ExampleEnum; B 18 | 19 | // access flags 0x12 20 | private final I value 21 | 22 | // access flags 0x101A 23 | private final static synthetic [Lother/ExampleEnum; $VALUES 24 | 25 | // access flags 0x9 26 | public static values()[Lother/ExampleEnum; 27 | L0 28 | LINENUMBER 3 L0 29 | GETSTATIC other/ExampleEnum.$VALUES : [Lother/ExampleEnum; 30 | INVOKEVIRTUAL [Lother/ExampleEnum;.clone ()Ljava/lang/Object; 31 | CHECKCAST [Lother/ExampleEnum; 32 | ARETURN 33 | MAXSTACK = 1 34 | MAXLOCALS = 0 35 | 36 | // access flags 0x9 37 | public static valueOf(Ljava/lang/String;)Lother/ExampleEnum; 38 | // parameter name 39 | L0 40 | LINENUMBER 3 L0 41 | LDC Lother/ExampleEnum;.class 42 | ALOAD 0 43 | INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 44 | CHECKCAST other/ExampleEnum 45 | ARETURN 46 | L1 47 | LOCALVARIABLE name Ljava/lang/String; L0 L1 0 48 | MAXSTACK = 2 49 | MAXLOCALS = 1 50 | 51 | // access flags 0x2 52 | // signature (I)V 53 | // declaration: void (int) 54 | private (Ljava/lang/String;II)V 55 | // parameter 56 | // parameter 57 | // parameter value 58 | L0 59 | LINENUMBER 12 L0 60 | ALOAD 0 61 | ALOAD 1 62 | ILOAD 2 63 | INVOKESPECIAL java/lang/Enum. (Ljava/lang/String;I)V 64 | L1 65 | LINENUMBER 13 L1 66 | ALOAD 0 67 | ILOAD 3 68 | PUTFIELD other/ExampleEnum.value : I 69 | L2 70 | LINENUMBER 14 L2 71 | RETURN 72 | L3 73 | LOCALVARIABLE this Lother/ExampleEnum; L0 L3 0 74 | LOCALVARIABLE value I L0 L3 3 75 | MAXSTACK = 3 76 | MAXLOCALS = 4 77 | 78 | // access flags 0x100A 79 | private static synthetic $values()[Lother/ExampleEnum; 80 | L0 81 | LINENUMBER 3 L0 82 | ICONST_2 83 | ANEWARRAY other/ExampleEnum 84 | DUP 85 | ICONST_0 86 | GETSTATIC other/ExampleEnum.A : Lother/ExampleEnum; 87 | AASTORE 88 | DUP 89 | ICONST_1 90 | GETSTATIC other/ExampleEnum.B : Lother/ExampleEnum; 91 | AASTORE 92 | ARETURN 93 | MAXSTACK = 4 94 | MAXLOCALS = 0 95 | 96 | // access flags 0x8 97 | static ()V 98 | L0 99 | LINENUMBER 4 L0 100 | NEW other/ExampleEnum 101 | DUP 102 | LDC "A" 103 | ICONST_0 104 | ICONST_1 105 | INVOKESPECIAL other/ExampleEnum. (Ljava/lang/String;II)V 106 | PUTSTATIC other/ExampleEnum.A : Lother/ExampleEnum; 107 | NEW other/ExampleEnum$1 108 | DUP 109 | LDC "B" 110 | ICONST_1 111 | ICONST_2 112 | INVOKESPECIAL other/ExampleEnum$1. (Ljava/lang/String;II)V 113 | PUTSTATIC other/ExampleEnum.B : Lother/ExampleEnum; 114 | L1 115 | LINENUMBER 3 L1 116 | INVOKESTATIC other/ExampleEnum.$values ()[Lother/ExampleEnum; 117 | PUTSTATIC other/ExampleEnum.$VALUES : [Lother/ExampleEnum; 118 | RETURN 119 | MAXSTACK = 5 120 | MAXLOCALS = 0 121 | } 122 | -------------------------------------------------------------------------------- /chasm/src/testData/transformers/add_field.chasm: -------------------------------------------------------------------------------- 1 | { 2 | id: "add_field", 3 | target_class: classes[0], 4 | transformations: [ 5 | { 6 | target: { 7 | node: target_class.fields, 8 | start: 0, 9 | end: 0 10 | }, 11 | apply: args -> [ 12 | { 13 | access: 1, 14 | name: "field1", 15 | descriptor: "I" 16 | } 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /chasm/src/testData/transformers/add_field_2.chasm: -------------------------------------------------------------------------------- 1 | { 2 | id: "add_field", 3 | target_class: classes[0], 4 | transformations: [ 5 | { 6 | target: { 7 | node: target_class.fields, 8 | start: 0, 9 | end: 0 10 | }, 11 | apply: args -> [ 12 | { 13 | access: 1, 14 | name: "field2", 15 | descriptor: "I" 16 | } 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /chasm/src/testData/transformers/add_method.chasm: -------------------------------------------------------------------------------- 1 | { 2 | id: "add_method", 3 | target_class: classes[0], 4 | transformations: [ 5 | { 6 | target: { 7 | node: target_class.methods, 8 | start: 0, 9 | end: 0 10 | }, 11 | apply: args -> [ 12 | { 13 | access: 1, 14 | name: "returnThis", 15 | parameters: [], 16 | returnType: "LTestClass;", 17 | locals: { 18 | this: { 19 | type: "LTestClass;" 20 | } 21 | }, 22 | code: { 23 | instructions: [ 24 | { 25 | opcode: 25, 26 | var: "this" 27 | }, 28 | { 29 | opcode: 176 30 | } 31 | ] 32 | } 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /chasm/src/testData/transformers/test_local_variables.chasm: -------------------------------------------------------------------------------- 1 | { 2 | id: "exampleTransformer", 3 | tail: method -> { 4 | node: target_class.methods[m -> m.name = method][0].code.instructions, 5 | start: len(node) * 2 - 3, 6 | end: start 7 | }, 8 | target_class: classes[0], 9 | transformations: [ 10 | { 11 | target: tail("staticMethod"), 12 | sources: { 13 | var_name: { 14 | node: target.node[i -> i.opcode = 54][0].var 15 | } 16 | }, 17 | apply: args -> [ 18 | { 19 | opcode: 132, 20 | var: args.sources.var_name, 21 | increment: 1 22 | } 23 | ] 24 | }, 25 | { 26 | target: tail("instanceMethod"), 27 | sources: { 28 | var_name: { 29 | node: target.node[i -> i.opcode = 54][0].var 30 | } 31 | }, 32 | apply: args -> [ 33 | { 34 | opcode: 132, 35 | var: args.sources.var_name, 36 | increment: 1 37 | } 38 | ] 39 | }, 40 | { 41 | target: tail("mergeVariable"), 42 | sources: { 43 | var1: { 44 | node: target.node[i -> i.opcode = 54][0].var 45 | }, 46 | var2: { 47 | node: target.node 48 | [i -> i.opcode = 54 ? i.var = var1.node 49 | ? false : true : false] 50 | [0].var 51 | } 52 | }, 53 | apply: args -> [ 54 | { 55 | opcode: 132, 56 | var: args.sources.var1, 57 | increment: 1 58 | }, 59 | { 60 | opcode: 132, 61 | var: args.sources.var2, 62 | increment: 1 63 | } 64 | ] 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /chasm/src/testData/transformers/test_merge_insns.chasm: -------------------------------------------------------------------------------- 1 | { 2 | target_class: classes[0], 3 | tail: method -> { 4 | node: target_class.methods[m -> m.name = method][0].code.instructions, 5 | start: len(node) * 2 - 1, 6 | end: start 7 | }, 8 | transformations: [ 9 | { 10 | target: tail("target"), 11 | apply: args -> [ 12 | { 13 | opcode: 178, 14 | owner: "java/lang/System", 15 | name: "out", 16 | descriptor: "Ljava/io/PrintStream;" 17 | }, 18 | { 19 | opcode: 18, 20 | value: { 21 | type: "string", 22 | value: "Hello Merged World" 23 | } 24 | }, 25 | { 26 | opcode: 184, 27 | owner: "java/io/PrintStream", 28 | name: "println", 29 | descriptor: "(Z)V", 30 | isInterface: false 31 | } 32 | ] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /chasm/src/testData/transformers/touch.chasm: -------------------------------------------------------------------------------- 1 | { 2 | transformations: [ 3 | { 4 | target: { 5 | node: classes[0].methods, 6 | start: 0, 7 | end: 0 8 | }, 9 | apply: args -> [] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /chassembly/build.gradle: -------------------------------------------------------------------------------- 1 | compileJava { 2 | javaCompiler = javaToolchains.compilerFor { 3 | languageVersion = JavaLanguageVersion.of(8) 4 | } 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | compileOnly 'org.jetbrains:annotations:23.0.0' 13 | 14 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' 15 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' 16 | } 17 | 18 | test { 19 | useJUnitPlatform() 20 | } 21 | 22 | tasks.register("generateParser", GenerateParserTask) { 23 | grammarFile = file("src/main/java/org/quiltmc/chasm/lang/internal/parse/Parser.jj") 24 | outputDir = file("$buildDir/generated/sources/javacc") 25 | } 26 | 27 | sourceSets { 28 | main { 29 | java { 30 | srcDir generateParser.outputDir 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/BooleanNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | /** 4 | * A boolean literal expression. 5 | */ 6 | public final class BooleanNode extends ValueNode { 7 | /** 8 | * The boolean literal expression representing {@code true}. 9 | */ 10 | public static final BooleanNode TRUE = new BooleanNode(true); 11 | /** 12 | * The boolean literal expression representing {@code false}. 13 | */ 14 | public static final BooleanNode FALSE = new BooleanNode(false); 15 | 16 | private BooleanNode(Boolean value) { 17 | super(value); 18 | } 19 | 20 | /** 21 | * Returns the boolean literal expression representing the given value. 22 | * 23 | * @see Ast#literal(boolean) 24 | */ 25 | public static BooleanNode from(boolean value) { 26 | return value ? TRUE : FALSE; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/CallNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 5 | import org.quiltmc.chasm.lang.api.eval.FunctionNode; 6 | import org.quiltmc.chasm.lang.api.eval.Resolver; 7 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 8 | import org.quiltmc.chasm.lang.internal.render.Renderer; 9 | 10 | /** 11 | * A call expression, representing function application. 12 | */ 13 | public class CallNode extends Node { 14 | private Node function; 15 | private Node arg; 16 | 17 | /** 18 | * Creates a call expression. 19 | * 20 | * @see Ast#call(Node, Node) 21 | */ 22 | public CallNode(Node function, Node arg) { 23 | this.function = function; 24 | this.arg = arg; 25 | } 26 | 27 | /** 28 | * Gets the function that is being applied. 29 | */ 30 | public Node getFunction() { 31 | return function; 32 | } 33 | 34 | /** 35 | * Sets the function that is being applied. 36 | */ 37 | public void setFunction(Node function) { 38 | this.function = function; 39 | } 40 | 41 | /** 42 | * Gets the argument to the application. 43 | */ 44 | public Node getArg() { 45 | return arg; 46 | } 47 | 48 | /** 49 | * Sets the argument to the application. 50 | */ 51 | public void setArg(Node arg) { 52 | this.arg = arg; 53 | } 54 | 55 | @Override 56 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 57 | function.render(renderer, builder, currentIndentationMultiplier); 58 | builder.append('('); 59 | arg.render(renderer, builder, currentIndentationMultiplier); 60 | builder.append(')'); 61 | } 62 | 63 | @Override 64 | @ApiStatus.OverrideOnly 65 | public void resolve(Resolver resolver) { 66 | function.resolve(resolver); 67 | arg.resolve(resolver); 68 | } 69 | 70 | @Override 71 | public Node evaluate(Evaluator evaluator) { 72 | Node function = this.function.evaluate(evaluator); 73 | 74 | if (function instanceof FunctionNode) { 75 | return ((FunctionNode) function).apply(evaluator, arg.evaluate(evaluator)); 76 | } 77 | 78 | throw new EvaluationException("Can only call functions but found " + function); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/FloatNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | /** 4 | * A float literal expression. 5 | */ 6 | public final class FloatNode extends ValueNode { 7 | /** 8 | * Creates a float literal expression. 9 | * 10 | * @see Ast#literal(double) 11 | */ 12 | public FloatNode(double value) { 13 | super(value); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/IndexNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import java.util.Map; 4 | 5 | import org.jetbrains.annotations.ApiStatus; 6 | import org.quiltmc.chasm.lang.api.eval.ClosureNode; 7 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 8 | import org.quiltmc.chasm.lang.api.eval.Resolver; 9 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 10 | import org.quiltmc.chasm.lang.internal.render.Renderer; 11 | 12 | /** 13 | * An index expression, e.g. {@code foo[bar]}. 14 | */ 15 | public class IndexNode extends Node { 16 | private Node left; 17 | private Node index; 18 | 19 | /** 20 | * Creates an index expression. 21 | * 22 | * @see Ast#index(Node, Node) 23 | */ 24 | public IndexNode(Node left, Node index) { 25 | this.left = left; 26 | this.index = index; 27 | } 28 | 29 | /** 30 | * Gets the subject of this index expression, i.e. the object being indexed into. 31 | */ 32 | public Node getLeft() { 33 | return left; 34 | } 35 | 36 | /** 37 | * Sets the subject of this index expression, i.e. the object being indexed into. 38 | */ 39 | public void setLeft(Node node) { 40 | this.left = node; 41 | } 42 | 43 | /** 44 | * Gets the index. 45 | */ 46 | public Node getIndex() { 47 | return index; 48 | } 49 | 50 | /** 51 | * Gets the index. 52 | */ 53 | public void setIndex(Node index) { 54 | this.index = index; 55 | } 56 | 57 | @Override 58 | @ApiStatus.OverrideOnly 59 | public void resolve(Resolver resolver) { 60 | left.resolve(resolver); 61 | index.resolve(resolver); 62 | } 63 | 64 | @Override 65 | @ApiStatus.OverrideOnly 66 | public Node evaluate(Evaluator evaluator) { 67 | Node leftNode = this.left.evaluate(evaluator); 68 | Node indexNode = this.index.evaluate(evaluator); 69 | 70 | // Index list 71 | if (leftNode instanceof ListNode && indexNode instanceof IntegerNode) { 72 | long index = ((IntegerNode) indexNode).getValue(); 73 | ListNode leftList = (ListNode) leftNode; 74 | 75 | if (index < 0 || index >= leftList.size()) { 76 | return Ast.nullNode(); 77 | } 78 | 79 | return leftList.get((int) index).evaluate(evaluator); 80 | } 81 | 82 | // Filter list 83 | if (leftNode instanceof ListNode && indexNode instanceof ClosureNode) { 84 | ClosureNode closure = (ClosureNode) indexNode; 85 | ListNode newEntries = Ast.emptyList(); 86 | 87 | for (Node entry : ((ListNode) leftNode).getEntries()) { 88 | CallNode callExpression = Ast.call(closure, entry); 89 | Node reduced = callExpression.evaluate(evaluator); 90 | if (!(reduced instanceof BooleanNode)) { 91 | throw new EvaluationException("Filter function must return a boolean but found " + reduced); 92 | } 93 | 94 | if (((BooleanNode) reduced).getValue()) { 95 | newEntries.add(entry.evaluate(evaluator)); 96 | } 97 | } 98 | 99 | return newEntries; 100 | } 101 | 102 | // Index map 103 | if (leftNode instanceof MapNode && indexNode instanceof StringNode) { 104 | String key = ((StringNode) indexNode).getValue(); 105 | Map entries = ((MapNode) leftNode).getEntries(); 106 | 107 | if (!entries.containsKey(key)) { 108 | return Ast.nullNode(); 109 | } 110 | 111 | return entries.get(key).evaluate(evaluator); 112 | } 113 | 114 | throw new EvaluationException("Can't index " + leftNode + " with " + indexNode); 115 | } 116 | 117 | @Override 118 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 119 | left.render(renderer, builder, currentIndentationMultiplier + 1); 120 | builder.append('['); 121 | index.render(renderer, builder, currentIndentationMultiplier + 1); 122 | builder.append(']'); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/IntegerNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | /** 4 | * An integer literal expression. 5 | */ 6 | public class IntegerNode extends ValueNode { 7 | /** 8 | * Creates an integer literal expression. 9 | * 10 | * @see Ast#literal(long) 11 | */ 12 | public IntegerNode(long value) { 13 | super(value); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/LambdaNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 5 | import org.quiltmc.chasm.lang.api.eval.Resolver; 6 | import org.quiltmc.chasm.lang.internal.render.Renderer; 7 | 8 | /** 9 | * A lambda expression. 10 | */ 11 | public class LambdaNode extends Node { 12 | private String identifier; 13 | private Node inner; 14 | 15 | /** 16 | * Creates a lambda expression. 17 | * 18 | * @see Ast#lambda(String, Node) 19 | */ 20 | public LambdaNode(String identifier, Node inner) { 21 | this.identifier = identifier; 22 | this.inner = inner; 23 | } 24 | 25 | /** 26 | * Gets the argument name of this lambda expression. 27 | */ 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | /** 33 | * Sets the argument name of this lambda. 34 | */ 35 | public void setIdentifier(String identifier) { 36 | this.identifier = identifier; 37 | } 38 | 39 | /** 40 | * Gets the body of this lambda. 41 | */ 42 | public Node getInner() { 43 | return inner; 44 | } 45 | 46 | /** 47 | * Sets the body of this lambda. 48 | */ 49 | public void setInner(Node inner) { 50 | this.inner = inner; 51 | } 52 | 53 | @Override 54 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 55 | builder.append(identifier); 56 | builder.append(" -> "); 57 | inner.render(renderer, builder, currentIndentationMultiplier); 58 | } 59 | 60 | @Override 61 | @ApiStatus.OverrideOnly 62 | public void resolve(Resolver resolver) { 63 | resolver.enterLambda(this); 64 | inner.resolve(resolver); 65 | resolver.exitLambda(); 66 | } 67 | 68 | @Override 69 | @ApiStatus.OverrideOnly 70 | public Node evaluate(Evaluator evaluator) { 71 | return evaluator.createClosure(this); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/ListNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.jetbrains.annotations.ApiStatus; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 10 | import org.quiltmc.chasm.lang.api.eval.Resolver; 11 | import org.quiltmc.chasm.lang.internal.render.Renderer; 12 | 13 | /** 14 | * A list expression, for list creation syntax, e.g. {@code [foo, bar, baz]}. 15 | */ 16 | public class ListNode extends Node { 17 | private final List entries; 18 | 19 | /** 20 | * Creates a list expression. 21 | * 22 | * @see Ast#list() 23 | */ 24 | public ListNode(List entries) { 25 | this.entries = entries; 26 | } 27 | 28 | /** 29 | * Gets the entries of this list expression. 30 | */ 31 | public List getEntries() { 32 | return entries; 33 | } 34 | 35 | /** 36 | * Appends a node to the end of this list. 37 | */ 38 | public void add(Node node) { 39 | this.entries.add(node); 40 | } 41 | 42 | /** 43 | * Gets the node at the given index in this list. 44 | */ 45 | public Node get(int index) { 46 | return this.entries.get(index); 47 | } 48 | 49 | /** 50 | * Gets the size of this list. 51 | */ 52 | public int size() { 53 | return this.entries.size(); 54 | } 55 | 56 | @Override 57 | @ApiStatus.OverrideOnly 58 | public void resolve(Resolver resolver) { 59 | entries.forEach(node -> node.resolve(resolver)); 60 | } 61 | 62 | @Override 63 | @ApiStatus.OverrideOnly 64 | public Node evaluate(Evaluator evaluator) { 65 | List newEntries = new ArrayList<>(); 66 | 67 | for (Node entry : entries) { 68 | newEntries.add(entry.evaluate(evaluator)); 69 | } 70 | 71 | if (newEntries.equals(entries)) { 72 | return this; 73 | } 74 | 75 | return new ListNode(newEntries); 76 | } 77 | 78 | @Override 79 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 80 | builder.append("["); 81 | for (int i = 0; i < entries.size(); i++) { 82 | entries.get(i).render(renderer, builder, currentIndentationMultiplier + 1); 83 | if (i < entries.size() - 1 || renderer.hasTrailingCommas()) { 84 | builder.append(','); 85 | } 86 | } 87 | builder.append("]"); 88 | } 89 | 90 | /** 91 | * A builder for list nodes. 92 | * 93 | * @see Ast#list() 94 | */ 95 | public static final class Builder { 96 | private final List entries = new ArrayList<>(); 97 | 98 | Builder() { 99 | } 100 | 101 | /** 102 | * Adds an object to this list node. 103 | * Supported values are null, boxed primitives, strings, maps for map nodes, iterables for list nodes, 104 | * builders for map and list nodes, and nodes. 105 | */ 106 | public Builder add(@Nullable Object value) { 107 | entries.add(Ast.objectToNode(value)); 108 | return this; 109 | } 110 | 111 | /** 112 | * Adds a sequence of objects to this list node. 113 | * Supported values are the same as in {@linkplain #add(Object)}. 114 | */ 115 | public Builder addAll(@Nullable Object @NotNull ... values) { 116 | for (Object value : values) { 117 | entries.add(Ast.objectToNode(value)); 118 | } 119 | return this; 120 | } 121 | 122 | /** 123 | * Adds a sequence of objects to this list node. 124 | * Supported values are the same as in {@linkplain #add(Object)}. 125 | */ 126 | public Builder addAll(@NotNull Iterable values) { 127 | for (Object value : values) { 128 | entries.add(Ast.objectToNode(value)); 129 | } 130 | return this; 131 | } 132 | 133 | /** 134 | * Creates the list node. 135 | */ 136 | public ListNode build() { 137 | return new ListNode(entries); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/MemberNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import java.util.Map; 4 | 5 | import org.jetbrains.annotations.ApiStatus; 6 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 7 | import org.quiltmc.chasm.lang.api.eval.Resolver; 8 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 9 | import org.quiltmc.chasm.lang.internal.render.RenderUtil; 10 | import org.quiltmc.chasm.lang.internal.render.Renderer; 11 | 12 | /** 13 | * A member access expression, for member syntax for accessing values in a map, e.g. {@code foo.bar}. 14 | */ 15 | public class MemberNode extends Node { 16 | private Node left; 17 | private String identifier; 18 | 19 | /** 20 | * Creates a member access expression. 21 | * 22 | * @see Ast#member(Node, String) 23 | */ 24 | public MemberNode(Node node, String identifier) { 25 | this.left = node; 26 | this.identifier = identifier; 27 | } 28 | 29 | /** 30 | * Gets the map from which to get the member. 31 | */ 32 | public Node getLeft() { 33 | return left; 34 | } 35 | 36 | /** 37 | * Sets the map from which to get the member. 38 | */ 39 | public void setLeft(Node left) { 40 | this.left = left; 41 | } 42 | 43 | /** 44 | * Gets the member to extract from the map. 45 | */ 46 | public String getIdentifier() { 47 | return identifier; 48 | } 49 | 50 | /** 51 | * Sets the member to extract from the map. 52 | */ 53 | public void setIdentifier(String identifier) { 54 | this.identifier = identifier; 55 | } 56 | 57 | @Override 58 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 59 | left.render(renderer, builder, currentIndentationMultiplier); 60 | builder.append(".").append(RenderUtil.quotifyIdentifierIfNeeded(identifier, '`')); 61 | } 62 | 63 | @Override 64 | @ApiStatus.OverrideOnly 65 | public void resolve(Resolver resolver) { 66 | left.resolve(resolver); 67 | } 68 | 69 | @Override 70 | @ApiStatus.OverrideOnly 71 | public Node evaluate(Evaluator evaluator) { 72 | Node left = this.left.evaluate(evaluator); 73 | 74 | if (!(left instanceof MapNode)) { 75 | throw new EvaluationException("Member access expected a map, but got a " + left); 76 | } 77 | 78 | Map entries = ((MapNode) left).getEntries(); 79 | 80 | if (!entries.containsKey(identifier)) { 81 | return Ast.nullNode(); 82 | } 83 | 84 | return entries.get(identifier).evaluate(evaluator); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/Node.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | 7 | import org.jetbrains.annotations.ApiStatus; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.Resolver; 10 | import org.quiltmc.chasm.lang.api.exception.ParseException; 11 | import org.quiltmc.chasm.lang.api.metadata.Metadata; 12 | import org.quiltmc.chasm.lang.internal.parse.Parser; 13 | import org.quiltmc.chasm.lang.internal.render.Renderer; 14 | 15 | /** 16 | * The base class used to represent Nodes in the abstract syntax tree. 17 | * This class can be extended by an execution environment if it requires special behaviour for custom nodes. 18 | */ 19 | public abstract class Node { 20 | private final Metadata metadata = new Metadata(); 21 | 22 | @ApiStatus.OverrideOnly 23 | public abstract void resolve(Resolver resolver); 24 | 25 | public abstract Node evaluate(Evaluator evaluator); 26 | 27 | public abstract void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier); 28 | 29 | public Metadata getMetadata() { 30 | return metadata; 31 | } 32 | 33 | /** 34 | * Parse a given file into a {@link Node}. 35 | * 36 | * @param path Path to a valid source file containing a single node. 37 | * @return The node parsed from the given source file. 38 | * @throws IOException If there is an error reading the file. 39 | * @throws ParseException If the source contains syntax errors. 40 | */ 41 | public static Node parse(Path path) throws IOException { 42 | Parser parser = new Parser(path); 43 | return parser.file(); 44 | } 45 | 46 | /** 47 | * Parse a given {@link String} into a {@link Node}. 48 | * 49 | * @param string A string containing a single node. 50 | * @return The node parsed from the given source file. 51 | * @throws ParseException If the source contains syntax errors. 52 | */ 53 | public static Node parse(String string) { 54 | Parser parser = new Parser(string); 55 | return parser.file(); 56 | } 57 | 58 | /** 59 | * Writes this node to a file. 60 | */ 61 | public void write(Path path) throws IOException { 62 | Renderer renderer = Renderer.builder().build(); 63 | StringBuilder sb = new StringBuilder(); 64 | render(renderer, sb, 1); 65 | Files.write(path, sb.toString().getBytes()); // what about utf16 support? 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/NullNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | /** 4 | * A null literal expression. 5 | */ 6 | public final class NullNode extends ValueNode { 7 | /** 8 | * The expression representing {@code null}. 9 | * 10 | * @see Ast#nullNode() 11 | */ 12 | public static final NullNode INSTANCE = new NullNode(); 13 | 14 | private NullNode() { 15 | super(null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/ReferenceNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 5 | import org.quiltmc.chasm.lang.api.eval.Resolver; 6 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 7 | import org.quiltmc.chasm.lang.internal.render.RenderUtil; 8 | import org.quiltmc.chasm.lang.internal.render.Renderer; 9 | 10 | /** 11 | * A reference expression, which loads the value of a symbol ("variable"), e.g. a lambda parameter or a key from an 12 | * outer map. 13 | */ 14 | public class ReferenceNode extends Node { 15 | private String identifier; 16 | private boolean global; 17 | 18 | /** 19 | * Creates a reference expression. 20 | * 21 | * @see Ast#ref(String) 22 | * @see Ast#globalRef(String) 23 | */ 24 | public ReferenceNode(String identifier, boolean global) { 25 | this.identifier = identifier; 26 | this.global = global; 27 | } 28 | 29 | /** 30 | * Gets the reference name. 31 | */ 32 | public String getIdentifier() { 33 | return identifier; 34 | } 35 | 36 | /** 37 | * Sets the reference name. 38 | */ 39 | public void setIdentifier(String identifier) { 40 | this.identifier = identifier; 41 | } 42 | 43 | /** 44 | * Gets whether the reference is global. 45 | * 46 | *

If a reference is global, it is resolved from the outermost scope to the innermost; if a reference is not 47 | * global, it is resolved from the innermost scope to the outermost. 48 | */ 49 | public boolean isGlobal() { 50 | return global; 51 | } 52 | 53 | /** 54 | * Sets whether the reference is global. See {@linkplain #isGlobal()} for details. 55 | */ 56 | public void setGlobal(boolean global) { 57 | this.global = global; 58 | } 59 | 60 | @Override 61 | @ApiStatus.OverrideOnly 62 | public void resolve(Resolver resolver) { 63 | resolver.resolveReference(this); 64 | } 65 | 66 | @Override 67 | @ApiStatus.OverrideOnly 68 | public Node evaluate(Evaluator evaluator) { 69 | Node resolved = evaluator.resolveReference(this); 70 | 71 | if (resolved == null) { 72 | throw new EvaluationException("Failed to resolve reference: " + this); 73 | } 74 | 75 | return resolved.evaluate(evaluator); 76 | } 77 | 78 | @Override 79 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 80 | if (global) { 81 | builder.append("$"); 82 | } 83 | 84 | builder.append(RenderUtil.quotifyIdentifierIfNeeded(identifier, '`')); 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "Ref<" + identifier + ">"; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/StringNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import org.quiltmc.chasm.lang.internal.Assert; 4 | 5 | /** 6 | * A string literal expression. 7 | */ 8 | public final class StringNode extends ValueNode { 9 | /** 10 | * Creates a string literal expression. 11 | * 12 | * @see Ast#literal(String) 13 | */ 14 | public StringNode(String value) { 15 | super(value); 16 | Assert.check(value != null, "Null given to StringNode"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/TernaryNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 5 | import org.quiltmc.chasm.lang.api.eval.Resolver; 6 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 7 | import org.quiltmc.chasm.lang.internal.render.Renderer; 8 | 9 | /** 10 | * A ternary expression, e.g. {@code foo ? bar : baz}. 11 | */ 12 | public class TernaryNode extends Node { 13 | private Node condition; 14 | private Node trueExp; 15 | private Node falseExp; 16 | 17 | /** 18 | * Creates a ternary expression. 19 | * 20 | * @see Ast#ternary(Node, Node, Node) 21 | */ 22 | public TernaryNode(Node condition, Node trueExp, Node falseExp) { 23 | this.condition = condition; 24 | this.trueExp = trueExp; 25 | this.falseExp = falseExp; 26 | } 27 | 28 | /** 29 | * Gets the condition. 30 | */ 31 | public Node getCondition() { 32 | return condition; 33 | } 34 | 35 | /** 36 | * Sets the condition. 37 | */ 38 | public void setCondition(Node condition) { 39 | this.condition = condition; 40 | } 41 | 42 | /** 43 | * Gets the expression returned if the condition is true. 44 | */ 45 | public Node getTrue() { 46 | return trueExp; 47 | } 48 | 49 | /** 50 | * Sets the expression returned if the condition is true. 51 | */ 52 | public void setTrue(Node trueExp) { 53 | this.trueExp = trueExp; 54 | } 55 | 56 | /** 57 | * Gets the expression returned if the condition is false. 58 | */ 59 | public Node getFalse() { 60 | return falseExp; 61 | } 62 | 63 | /** 64 | * Sets the expression returned if the condition is false. 65 | */ 66 | public void setFalse(Node falseExp) { 67 | this.falseExp = falseExp; 68 | } 69 | 70 | @Override 71 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 72 | boolean wrapWithBraces = condition instanceof TernaryNode; 73 | if (wrapWithBraces) { 74 | builder.append('('); 75 | } 76 | condition.render(renderer, builder, currentIndentationMultiplier); 77 | if (wrapWithBraces) { 78 | builder.append(')'); 79 | } 80 | builder.append(" ? "); 81 | trueExp.render(renderer, builder, currentIndentationMultiplier); 82 | builder.append(" : "); 83 | falseExp.render(renderer, builder, currentIndentationMultiplier); 84 | } 85 | 86 | @Override 87 | @ApiStatus.OverrideOnly 88 | public void resolve(Resolver resolver) { 89 | condition.resolve(resolver); 90 | trueExp.resolve(resolver); 91 | falseExp.resolve(resolver); 92 | } 93 | 94 | @Override 95 | @ApiStatus.OverrideOnly 96 | public Node evaluate(Evaluator evaluator) { 97 | Node condition = this.condition.evaluate(evaluator); 98 | 99 | if (!(condition instanceof BooleanNode)) { 100 | throw new EvaluationException("Condition in ternary must evaluate to a boolean but found " + condition); 101 | } 102 | 103 | if (((BooleanNode) condition).getValue()) { 104 | return trueExp.evaluate(evaluator); 105 | } else { 106 | return falseExp.evaluate(evaluator); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/ast/ValueNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.ast; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 5 | import org.quiltmc.chasm.lang.api.eval.Resolver; 6 | import org.quiltmc.chasm.lang.internal.render.RenderUtil; 7 | import org.quiltmc.chasm.lang.internal.render.Renderer; 8 | 9 | /** 10 | * The base class for literal expressions. 11 | */ 12 | public abstract class ValueNode extends Node { 13 | private T value; 14 | 15 | /** 16 | * Creates a literal expression. 17 | */ 18 | public ValueNode(T value) { 19 | this.value = value; 20 | } 21 | 22 | /** 23 | * Gets the value of the literal expression. 24 | */ 25 | public T getValue() { 26 | return value; 27 | } 28 | 29 | /** 30 | * Sets the value of the literal expression. 31 | */ 32 | public void setValue(T value) { 33 | this.value = value; 34 | } 35 | 36 | @Override 37 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 38 | if (value instanceof String) { 39 | builder.append(RenderUtil.quotify((String) value, '"')); 40 | } else { 41 | builder.append(value); 42 | } 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return String.valueOf(value); 48 | } 49 | 50 | @Override 51 | @ApiStatus.OverrideOnly 52 | public void resolve(Resolver resolver) { 53 | } 54 | 55 | @Override 56 | @ApiStatus.OverrideOnly 57 | public Node evaluate(Evaluator evaluator) { 58 | return this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/eval/ClosureNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.eval; 2 | 3 | import java.util.Map; 4 | 5 | import org.jetbrains.annotations.ApiStatus; 6 | import org.quiltmc.chasm.lang.api.ast.LambdaNode; 7 | import org.quiltmc.chasm.lang.api.ast.Node; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.Resolver; 10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 11 | import org.quiltmc.chasm.lang.internal.render.Renderer; 12 | 13 | /** 14 | * Represents a lambda with all captures resolved. Do not create directly; if you really need to create a closure, 15 | * use {@linkplain Evaluator#createClosure(LambdaNode)}. 16 | */ 17 | public class ClosureNode extends FunctionNode { 18 | private final LambdaNode lambda; 19 | private final Map captures; 20 | 21 | @ApiStatus.Internal 22 | public ClosureNode(LambdaNode lambda, Map captures) { 23 | this.lambda = lambda; 24 | this.captures = captures; 25 | } 26 | 27 | /** 28 | * Gets the lambda for this closure. 29 | */ 30 | public LambdaNode getLambda() { 31 | return lambda; 32 | } 33 | 34 | /** 35 | * Gets the captures for this closure. 36 | */ 37 | public Map getCaptures() { 38 | return captures; 39 | } 40 | 41 | @Override 42 | public Node apply(Evaluator evaluator, Node arg) { 43 | return evaluator.callClosure(this, arg); 44 | } 45 | 46 | @Override 47 | public void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 48 | // Represent a closure as a capture-free lambda 49 | // Note: This currently fails because of infinite recursion 50 | 51 | /* 52 | String bodyName = "__lambda_body"; 53 | MapNode mapNode = new MapNode(new HashMap<>(captures)); 54 | mapNode.getEntries().put(bodyName, lambda.getInner()); 55 | IndexNode indexNode = new IndexNode(mapNode, new LiteralNode(bodyName)); 56 | LambdaNode lambdaNode = new LambdaNode(lambda.getIdentifier(), indexNode); 57 | lambdaNode.render(config, builder, currentIndentationMultiplier); 58 | */ 59 | 60 | builder.append(""); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/eval/Evaluator.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.eval; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.ast.LambdaNode; 5 | import org.quiltmc.chasm.lang.api.ast.Node; 6 | import org.quiltmc.chasm.lang.api.ast.ReferenceNode; 7 | import org.quiltmc.chasm.lang.internal.eval.EvaluatorImpl; 8 | import org.quiltmc.chasm.lang.internal.intrinsics.BuiltInIntrinsics; 9 | 10 | /** 11 | * Helper for evaluating (reducing) nodes. 12 | */ 13 | @ApiStatus.NonExtendable 14 | public interface Evaluator { 15 | /** 16 | * Creates an evaluator with the default settings for the given root node. 17 | */ 18 | static Evaluator create(Node node) { 19 | return new EvaluatorImpl(node, BuiltInIntrinsics.ALL); 20 | } 21 | 22 | /** 23 | * Creates an evaluator builder for the given root node, with which you can customize the evaluator's settings. 24 | */ 25 | static Builder builder(Node node) { 26 | return new EvaluatorImpl.Builder(node); 27 | } 28 | 29 | /** 30 | * Resolves a reference node to the node it is referring to. 31 | */ 32 | Node resolveReference(ReferenceNode reference); 33 | 34 | /** 35 | * Creates a closure from a lambda node. 36 | * 37 | * @see ClosureNode 38 | */ 39 | ClosureNode createClosure(LambdaNode lambdaNode); 40 | 41 | /** 42 | * Applies a closure using the given argument. 43 | */ 44 | Node callClosure(ClosureNode closure, Node arg); 45 | 46 | /** 47 | * Gets the {@linkplain Resolver} of this evaluator. 48 | */ 49 | Resolver getResolver(); 50 | 51 | /** 52 | * An evaluator builder to customize the settings of an evaluator. 53 | */ 54 | @ApiStatus.NonExtendable 55 | interface Builder { 56 | /** 57 | * Adds a new intrinsic function. 58 | */ 59 | Builder addIntrinsic(IntrinsicFunction intrinsic); 60 | 61 | /** 62 | * Builds the evaluator. 63 | */ 64 | Evaluator build(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/eval/FunctionNode.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.eval; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Node; 4 | 5 | /** 6 | * The base class for any node that can be applied using a call expression (a "function"). 7 | */ 8 | public abstract class FunctionNode extends Node { 9 | /** 10 | * Applies the function using the given argument. 11 | */ 12 | public abstract Node apply(Evaluator evaluator, Node arg); 13 | 14 | @Override 15 | public final void resolve(Resolver resolver) { 16 | } 17 | 18 | @Override 19 | public final Node evaluate(Evaluator evaluator) { 20 | return this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/eval/IntrinsicFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.eval; 2 | 3 | import org.quiltmc.chasm.lang.internal.render.Renderer; 4 | 5 | /** 6 | * A function that is implemented in Java rather than in chassembly. Some built-in intrinsic functions may also be 7 | * treated specially by the evaluator as well. 8 | */ 9 | public abstract class IntrinsicFunction extends FunctionNode { 10 | /** 11 | * Returns the name of the intrinsic function. 12 | */ 13 | public abstract String getName(); 14 | 15 | @Override 16 | public final void render(Renderer renderer, StringBuilder builder, int currentIndentationMultiplier) { 17 | builder.append(getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/eval/Resolver.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.eval; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.quiltmc.chasm.lang.api.ast.LambdaNode; 5 | import org.quiltmc.chasm.lang.api.ast.MapNode; 6 | import org.quiltmc.chasm.lang.api.ast.ReferenceNode; 7 | 8 | /** 9 | * A helper for resolving which node chassembly references refer to. 10 | */ 11 | @ApiStatus.NonExtendable 12 | public interface Resolver { 13 | /** 14 | * Resolves the reference and stores the result in this resolver, so that it can be queried later by the evaluator. 15 | */ 16 | void resolveReference(ReferenceNode reference); 17 | 18 | /** 19 | * Enters a map scope. 20 | */ 21 | void enterMap(MapNode map); 22 | 23 | /** 24 | * Exits a map scope. 25 | */ 26 | void exitMap(); 27 | 28 | /** 29 | * Enters a lambda scope. 30 | */ 31 | void enterLambda(LambdaNode lambda); 32 | 33 | /** 34 | * Exits a lambda scope. 35 | */ 36 | void exitLambda(); 37 | } 38 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/exception/EvaluationException.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.exception; 2 | 3 | /** 4 | * Thrown when chassembly evaluation fails. 5 | */ 6 | public class EvaluationException extends RuntimeException { 7 | /** 8 | * Creates an {@linkplain EvaluationException} with the given message. 9 | */ 10 | public EvaluationException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/exception/ParseException.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.exception; 2 | 3 | /** 4 | * Thrown when a piece of chassembly code failed to parse. 5 | */ 6 | public class ParseException extends RuntimeException { 7 | } 8 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/api/metadata/Metadata.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.api.metadata; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | 8 | /** 9 | * Provides metadata attached to a {@link Node}. 10 | */ 11 | public class Metadata { 12 | private final Map, Object> metadata; 13 | 14 | /** 15 | * Create a new, empty {@link Metadata}. 16 | */ 17 | public Metadata() { 18 | this.metadata = new HashMap<>(); 19 | } 20 | 21 | /** 22 | * Attach metadata of a given type. 23 | * 24 | * @param dataClass The class of the type to add. 25 | * @param data The instance of the specified type to attach. 26 | * @param The type of the metadata. 27 | * @return Previously attached metadata of the specified type, or {@code null} if it doesn't exist. 28 | */ 29 | @SuppressWarnings("unchecked") 30 | public T put(Class dataClass, T data) { 31 | return (T) metadata.put(dataClass, data); 32 | } 33 | 34 | /** 35 | * Retrieve metadata of a given type. 36 | * 37 | * @param dataClass The class of the type to retrieve. 38 | * @param The type of the metadata. 39 | * @return The attached metadata of the specified type, or {@code null} if it doesn't exist. 40 | */ 41 | @SuppressWarnings("unchecked") 42 | public T get(Class dataClass) { 43 | return (T) metadata.get(dataClass); 44 | } 45 | 46 | /** 47 | * Adds the given metadata to this metadata. 48 | * Existing metadata of a given type will be overwritten. 49 | * 50 | * @param metadata The metadata to add. 51 | */ 52 | public void putAll(Metadata metadata) { 53 | this.metadata.putAll(metadata.metadata); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/Assert.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | 5 | public final class Assert { 6 | private Assert() { 7 | throw new AssertionError("No Assert instances for you!"); 8 | } 9 | 10 | @Contract("false -> fail") 11 | public static void check(boolean value) { 12 | check(value, "Assertion failed"); 13 | } 14 | 15 | @Contract("false, _ -> fail") 16 | public static void check(boolean value, String message) { 17 | if (!value) { 18 | throw new AssertionError(message); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/eval/LambdaReference.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.eval; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.LambdaNode; 4 | 5 | public class LambdaReference extends Reference { 6 | private final String identifier; 7 | private final LambdaNode lambda; 8 | 9 | public LambdaReference(String identifier, LambdaNode lambda) { 10 | this.identifier = identifier; 11 | this.lambda = lambda; 12 | } 13 | 14 | public String getIdentifier() { 15 | return identifier; 16 | } 17 | 18 | public LambdaNode getLambda() { 19 | return lambda; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/eval/NodeReference.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.eval; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Node; 4 | 5 | public class NodeReference extends Reference { 6 | private final Node node; 7 | 8 | public NodeReference(Node node) { 9 | this.node = node; 10 | } 11 | 12 | public Node getNode() { 13 | return node; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/eval/Reference.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.eval; 2 | 3 | public abstract class Reference { 4 | } 5 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/eval/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.lang.internal.eval; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/BuiltInIntrinsics.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 8 | 9 | public class BuiltInIntrinsics { 10 | public static final Map ALL = new HashMap<>(); 11 | 12 | static { 13 | register(new CharsFunction()); 14 | register(new JoinFunction()); 15 | register(new LenFunction()); 16 | register(new EntriesFunction()); 17 | register(new FromEntriesFunction()); 18 | register(new MapFunction()); 19 | register(new ReduceFunction()); 20 | register(new FlattenFunction()); 21 | register(new ToIntegerFunction()); 22 | register(new ToFloatFunction()); 23 | register(new SplitFloatFunction()); 24 | } 25 | 26 | private static void register(IntrinsicFunction function) { 27 | ALL.put(function.getName(), function); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/CharsFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.ListNode; 5 | import org.quiltmc.chasm.lang.api.ast.Node; 6 | import org.quiltmc.chasm.lang.api.ast.StringNode; 7 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 8 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 9 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 10 | 11 | class CharsFunction extends IntrinsicFunction { 12 | @Override 13 | public String getName() { 14 | return "chars"; 15 | } 16 | 17 | @Override 18 | public Node apply(Evaluator evaluator, Node arg) { 19 | if (!(arg instanceof StringNode)) { 20 | throw new EvaluationException( 21 | "Built-in function \"chars\" can only be applied to strings but found " + arg); 22 | } 23 | 24 | String value = ((StringNode) arg).getValue(); 25 | 26 | ListNode entries = Ast.emptyList(); 27 | for (int i = 0; i < value.length(); i++) { 28 | entries.add(Ast.literal(value.charAt(i))); 29 | } 30 | 31 | return entries; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/EntriesFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.ListNode; 5 | import org.quiltmc.chasm.lang.api.ast.MapNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 8 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 9 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 10 | 11 | public class EntriesFunction extends IntrinsicFunction { 12 | @Override 13 | public Node apply(Evaluator evaluator, Node arg) { 14 | if (arg instanceof MapNode) { 15 | ListNode entryList = Ast.emptyList(); 16 | 17 | ((MapNode) arg).getEntries() 18 | .forEach((key, value) -> entryList.add(Ast.map().put("key", key).put("value", value).build())); 19 | 20 | return entryList; 21 | } 22 | throw new EvaluationException("Built-in function \"entries\" can only be applied to maps but found " + arg); 23 | } 24 | 25 | @Override 26 | public String getName() { 27 | return "entries"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/FlattenFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import java.util.stream.Collectors; 4 | 5 | import org.quiltmc.chasm.lang.api.ast.ListNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 8 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 9 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 10 | 11 | /** 12 | * The flatten intrinsic, which takes in a list of lists and returns a new list with the contents of the sublists. 13 | */ 14 | public class FlattenFunction extends IntrinsicFunction { 15 | @Override 16 | public Node apply(Evaluator evaluator, Node arg) { 17 | if (!(arg instanceof ListNode)) { 18 | throw createArgsException(arg); 19 | } 20 | 21 | return new ListNode(((ListNode) arg).getEntries() 22 | .stream() 23 | .map(entry -> { 24 | if (entry instanceof ListNode) { 25 | return (ListNode) entry; 26 | } else { 27 | throw createArgsException(arg); 28 | } 29 | }) 30 | .flatMap(entry -> entry.getEntries().stream()) 31 | .collect(Collectors.toList())); 32 | } 33 | 34 | @Override 35 | public String getName() { 36 | return "flatten"; 37 | } 38 | 39 | private static EvaluationException createArgsException(Node arg) { 40 | return new EvaluationException( 41 | "Built-in function \"flatten\" can only be applied to lists of lists but found " + arg 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/FromEntriesFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.ListNode; 5 | import org.quiltmc.chasm.lang.api.ast.MapNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.ast.StringNode; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 11 | 12 | public class FromEntriesFunction extends IntrinsicFunction { 13 | @Override 14 | public Node apply(Evaluator evaluator, Node arg) { 15 | if (arg instanceof ListNode) { 16 | MapNode result = Ast.emptyMap(); 17 | 18 | for (Node node : ((ListNode) arg).getEntries()) { 19 | if (node instanceof MapNode) { 20 | MapNode mapNode = (MapNode) node; 21 | Node key = mapNode.get("key"); 22 | Node value = mapNode.get("value"); 23 | 24 | if (!(key instanceof StringNode) || value == null) { 25 | throw createException(arg); 26 | } 27 | 28 | result.put(((StringNode) key).getValue(), value); 29 | } else { 30 | throw createException(arg); 31 | } 32 | } 33 | 34 | return result; 35 | } 36 | throw createException(arg); 37 | } 38 | 39 | private static EvaluationException createException(Node arg) { 40 | return new EvaluationException( 41 | "Built-in function \"from_entries\" can only be applied to lists of maps, " 42 | + "each with {key: string, value: any}, but found " + arg 43 | ); 44 | } 45 | 46 | @Override 47 | public String getName() { 48 | return "from_entries"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/JoinFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import org.quiltmc.chasm.lang.api.ast.Ast; 7 | import org.quiltmc.chasm.lang.api.ast.IntegerNode; 8 | import org.quiltmc.chasm.lang.api.ast.ListNode; 9 | import org.quiltmc.chasm.lang.api.ast.Node; 10 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 11 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 12 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 13 | 14 | class JoinFunction extends IntrinsicFunction { 15 | @Override 16 | public String getName() { 17 | return "join"; 18 | } 19 | 20 | @Override 21 | public Node apply(Evaluator evaluator, Node arg) { 22 | if (!(arg instanceof ListNode)) { 23 | throw new EvaluationException( 24 | "Built-in function \"join\" can only be applied to list of integers but found " + arg); 25 | } 26 | 27 | List entries = ((ListNode) arg).getEntries(); 28 | 29 | if (!entries.stream().allMatch(e -> e instanceof IntegerNode)) { 30 | throw new EvaluationException( 31 | "Built-in function \"join\" can only be applied to list of integers but found " + arg); 32 | } 33 | 34 | String joined = entries.stream() 35 | .map(e -> Character.toString((char) (((IntegerNode) e).getValue()).shortValue())) 36 | .collect(Collectors.joining()); 37 | 38 | return Ast.literal(joined); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/LenFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.ListNode; 5 | import org.quiltmc.chasm.lang.api.ast.Node; 6 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 7 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 8 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 9 | 10 | public class LenFunction extends IntrinsicFunction { 11 | @Override 12 | public Node apply(Evaluator evaluator, Node arg) { 13 | if (!(arg instanceof ListNode)) { 14 | throw new EvaluationException("Built-in function \"len\" can only be applied to lists but found " + arg); 15 | } 16 | return Ast.literal(((ListNode) arg).size()); 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return "len"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/MapFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import java.util.stream.Collectors; 4 | 5 | import org.quiltmc.chasm.lang.api.ast.ListNode; 6 | import org.quiltmc.chasm.lang.api.ast.MapNode; 7 | import org.quiltmc.chasm.lang.api.ast.Node; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.FunctionNode; 10 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 11 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 12 | 13 | /** 14 | * The map intrinsic, which takes in a list and a function, and returns a new list of the result of applying the 15 | * function to each element in the list. 16 | */ 17 | public class MapFunction extends IntrinsicFunction { 18 | @Override 19 | public Node apply(Evaluator evaluator, Node arg) { 20 | if (!(arg instanceof MapNode)) { 21 | throw createArgsException(arg); 22 | } 23 | 24 | MapNode mapArg = (MapNode) arg; 25 | Node list = mapArg.get("list"); 26 | Node function = mapArg.get("func"); 27 | if (!(list instanceof ListNode) || !(function instanceof FunctionNode)) { 28 | throw createArgsException(arg); 29 | } 30 | 31 | return new ListNode(((ListNode) list).getEntries().stream() 32 | .map(entry -> ((FunctionNode) function).apply(evaluator, entry)) 33 | .collect(Collectors.toList())); 34 | } 35 | 36 | @Override 37 | public String getName() { 38 | return "map"; 39 | } 40 | 41 | private static EvaluationException createArgsException(Node arg) { 42 | return new EvaluationException( 43 | "Built-in function \"map\" can only be applied to args {list, func} but found " + arg 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/ReduceFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.ListNode; 5 | import org.quiltmc.chasm.lang.api.ast.MapNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 8 | import org.quiltmc.chasm.lang.api.eval.FunctionNode; 9 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 11 | 12 | public class ReduceFunction extends IntrinsicFunction { 13 | @Override 14 | public Node apply(Evaluator evaluator, Node arg) { 15 | if (arg instanceof MapNode) { 16 | MapNode args = (MapNode) arg; 17 | Node list = args.get("list"); 18 | Node function = args.get("func"); 19 | 20 | if (list instanceof ListNode && function instanceof FunctionNode) { 21 | ListNode listNode = (ListNode) list; 22 | if (listNode.size() == 0) { 23 | throw new EvaluationException("Can't reduce empty list: " + list); 24 | } 25 | FunctionNode funcNode = (FunctionNode) function; 26 | 27 | Node result = listNode.get(0); 28 | for (int i = 1; i < listNode.size(); i++) { 29 | MapNode funcArgs = Ast.map().put("first", result).put("second", listNode.get(i)).build(); 30 | result = funcNode.apply(evaluator, funcArgs); 31 | } 32 | 33 | return result; 34 | } 35 | } 36 | 37 | throw new EvaluationException( 38 | "Built-in function \"reduce\" can only be applied to args {list, func} but found " + arg 39 | ); 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return "reduce"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/SplitFloatFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.FloatNode; 5 | import org.quiltmc.chasm.lang.api.ast.Node; 6 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 7 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 8 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 9 | 10 | /** 11 | * Converts a float into its sign (integer), coefficient (float) and exponent (integer). Produces a map with these 12 | * keys and values. It is always the case that {@code sign * coefficient * (2.0 ^ exponent)} will produce the original 13 | * float. 14 | */ 15 | public class SplitFloatFunction extends IntrinsicFunction { 16 | @Override 17 | public Node apply(Evaluator evaluator, Node arg) { 18 | if (!(arg instanceof FloatNode)) { 19 | throw new EvaluationException(getName() + " expected float, got " + arg); 20 | } 21 | double d = ((FloatNode) arg).getValue(); 22 | long bits = Double.doubleToRawLongBits(d); 23 | 24 | int sign = (bits & Long.MIN_VALUE) == 0 ? 1 : -1; 25 | int exponent; 26 | double coefficient; 27 | if (d == 0.0) { // covers both positive and negative zero 28 | exponent = 0; 29 | coefficient = 0.0; 30 | } else if (Double.isNaN(d)) { 31 | sign = 1; 32 | exponent = 0; 33 | coefficient = Double.NaN; 34 | } else if (Double.isInfinite(d)) { 35 | exponent = 0; 36 | coefficient = Double.POSITIVE_INFINITY; 37 | } else { 38 | exponent = (int) (((bits >> 52) & ((1L << 11) - 1)) - 1023); 39 | long mantissa = bits & ((1L << 52) - 1); 40 | if (exponent == -1023) { 41 | // subnormal 42 | int factorBelowNormal = Long.numberOfLeadingZeros(mantissa) - 11; 43 | exponent -= factorBelowNormal - 1; 44 | mantissa <<= factorBelowNormal; 45 | } 46 | coefficient = Double.longBitsToDouble((1023L << 52) | mantissa); 47 | } 48 | 49 | return Ast.map() 50 | .put("sign", sign) 51 | .put("exponent", exponent) 52 | .put("coefficient", coefficient) 53 | .build(); 54 | } 55 | 56 | @Override 57 | public String getName() { 58 | return "split_float"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/ToFloatFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.FloatNode; 5 | import org.quiltmc.chasm.lang.api.ast.IntegerNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.ast.StringNode; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 11 | 12 | public class ToFloatFunction extends IntrinsicFunction { 13 | @Override 14 | public Node apply(Evaluator evaluator, Node arg) { 15 | if (arg instanceof FloatNode) { 16 | return arg; 17 | } else if (arg instanceof IntegerNode) { 18 | return Ast.literal(((IntegerNode) arg).getValue().doubleValue()); 19 | } else if (arg instanceof StringNode) { 20 | String str = ((StringNode) arg).getValue(); 21 | try { 22 | return Ast.literal(Double.parseDouble(str)); 23 | } catch (NumberFormatException e) { 24 | throw new EvaluationException("Cannot convert string \"" + str + "\" to float"); 25 | } 26 | } else { 27 | throw new EvaluationException("Cannot convert " + arg + " to float"); 28 | } 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return "to_float"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/ToIntegerFunction.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.intrinsics; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Ast; 4 | import org.quiltmc.chasm.lang.api.ast.FloatNode; 5 | import org.quiltmc.chasm.lang.api.ast.IntegerNode; 6 | import org.quiltmc.chasm.lang.api.ast.Node; 7 | import org.quiltmc.chasm.lang.api.ast.StringNode; 8 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 9 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction; 10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException; 11 | 12 | public class ToIntegerFunction extends IntrinsicFunction { 13 | @Override 14 | public Node apply(Evaluator evaluator, Node arg) { 15 | if (arg instanceof IntegerNode) { 16 | return arg; 17 | } else if (arg instanceof FloatNode) { 18 | return Ast.literal(((FloatNode) arg).getValue().longValue()); 19 | } else if (arg instanceof StringNode) { 20 | String str = ((StringNode) arg).getValue(); 21 | try { 22 | return Ast.literal(Long.parseLong(str)); 23 | } catch (NumberFormatException e) { 24 | throw new EvaluationException("Cannot convert string \"" + str + "\" to integer"); 25 | } 26 | } else { 27 | throw new EvaluationException("Cannot convert " + arg + " to integer"); 28 | } 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return "to_integer"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/intrinsics/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.lang.internal.intrinsics; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.lang.internal; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/parse/SourceSpan.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.parse; 2 | 3 | public class SourceSpan { 4 | private final int lineStart; 5 | private final int columnStart; 6 | private final int lineEnd; 7 | private final int columnEnd; 8 | 9 | public SourceSpan(int lineStart, int columnStart, int lineEnd, int columnEnd) { 10 | this.lineStart = lineStart; 11 | this.lineEnd = lineEnd; 12 | this.columnStart = columnStart; 13 | this.columnEnd = columnEnd; 14 | } 15 | 16 | public static SourceSpan fromToken(Token t) { 17 | return new SourceSpan(t.getBeginLine(), t.getBeginColumn(), t.getEndLine(), t.getEndColumn()); 18 | } 19 | 20 | public SourceSpan join(SourceSpan other) { 21 | int lineStart; 22 | int columnStart; 23 | if (this.lineStart < other.lineStart) { 24 | lineStart = this.lineStart; 25 | columnStart = this.columnStart; 26 | } else if (other.lineStart < this.lineStart) { 27 | lineStart = other.lineStart; 28 | columnStart = other.columnStart; 29 | } else { 30 | lineStart = this.lineStart; 31 | columnStart = Math.min(this.columnStart, other.columnStart); 32 | } 33 | 34 | int lineEnd; 35 | int columnEnd; 36 | if (this.lineEnd > other.lineEnd) { 37 | lineEnd = this.lineEnd; 38 | columnEnd = this.columnEnd; 39 | } else if (other.lineEnd > this.lineEnd) { 40 | lineEnd = other.lineEnd; 41 | columnEnd = other.columnEnd; 42 | } else { 43 | lineEnd = this.lineEnd; 44 | columnEnd = Math.max(this.columnEnd, other.columnEnd); 45 | } 46 | 47 | return new SourceSpan(lineStart, columnStart, lineEnd, columnEnd); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/parse/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.lang.internal.parse; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/render/RenderUtil.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.render; 2 | 3 | public class RenderUtil { 4 | private RenderUtil() { 5 | } 6 | 7 | public static String quotify(String text, char quoteChar) { 8 | return quoteChar + text.replace("\\", "\\\\").replace("" + quoteChar, "\\" + quoteChar) + quoteChar; 9 | } 10 | 11 | public static String quotifyIdentifierIfNeeded(String identifier, char quoteChar) { 12 | return needsQuotes(identifier) ? quotify(identifier, quoteChar) : identifier; 13 | } 14 | 15 | private static boolean needsQuotes(String identifier) { 16 | if (identifier.isEmpty()) { 17 | return true; 18 | } 19 | 20 | if (!isValidIdentifierStartChar(identifier.charAt(0))) { 21 | return true; 22 | } 23 | 24 | for (int i = 1; i < identifier.length(); i++) { 25 | if (!isValidIdentifierChar(identifier.charAt(i))) { 26 | return true; 27 | } 28 | } 29 | 30 | return false; 31 | } 32 | 33 | private static boolean isValidIdentifierStartChar(char c) { 34 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; 35 | } 36 | 37 | private static boolean isValidIdentifierChar(char c) { 38 | return isValidIdentifierStartChar(c) || (c >= '0' && c <= '9'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/render/Renderer.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang.internal.render; 2 | 3 | import org.quiltmc.chasm.lang.api.ast.Node; 4 | 5 | public class Renderer { 6 | 7 | private final int indentation; 8 | private final char indentationChar; 9 | private final boolean prettyPrinting; 10 | private final boolean trailingNewline; 11 | private final boolean trailingCommas; 12 | 13 | private Renderer( 14 | int indentation, 15 | char indentationChar, 16 | boolean prettyPrinting, 17 | boolean trailingNewline, 18 | boolean trailingCommas 19 | ) { 20 | this.indentation = indentation; 21 | this.indentationChar = indentationChar; 22 | this.prettyPrinting = prettyPrinting; 23 | this.trailingNewline = trailingNewline; 24 | this.trailingCommas = trailingCommas; 25 | } 26 | 27 | public static Builder builder() { 28 | return new Builder(); 29 | } 30 | 31 | public String render(Node expression) { 32 | StringBuilder sb = new StringBuilder(); 33 | expression.render(this, sb, 1); 34 | if (trailingNewline) { 35 | sb.append('\n'); 36 | } 37 | return sb.toString(); 38 | } 39 | 40 | public int getIndentation() { 41 | return indentation; 42 | } 43 | 44 | public char getIndentationChar() { 45 | return indentationChar; 46 | } 47 | 48 | public boolean hasPrettyPrinting() { 49 | return prettyPrinting; 50 | } 51 | 52 | public boolean hasTrailingNewline() { 53 | return trailingNewline; 54 | } 55 | 56 | public boolean hasTrailingCommas() { 57 | return trailingCommas; 58 | } 59 | 60 | public void indent(StringBuilder builder, int currentIndentationMultiplier) { 61 | if (!prettyPrinting) { 62 | return; 63 | } 64 | builder.append('\n'); 65 | for (int i = 0; i < indentation * currentIndentationMultiplier; i++) { 66 | builder.append(indentationChar); 67 | } 68 | } 69 | 70 | public static class Builder { 71 | private int indentation; 72 | private char indentationChar; 73 | private boolean prettyPrinting; 74 | private boolean trailingNewline; 75 | private boolean trailingCommas; 76 | 77 | private Builder() { 78 | indentation = 4; 79 | indentationChar = ' '; 80 | prettyPrinting = false; 81 | trailingNewline = true; 82 | trailingCommas = true; 83 | } 84 | 85 | public Builder indentation(int value) { 86 | indentation = value; 87 | return this; 88 | } 89 | 90 | public Builder indentationChar(char value) { 91 | indentationChar = value; 92 | return this; 93 | } 94 | 95 | public Builder prettyPrinting(boolean value) { 96 | prettyPrinting = value; 97 | return this; 98 | } 99 | 100 | public Builder trailingNewline(boolean value) { 101 | trailingNewline = value; 102 | return this; 103 | } 104 | 105 | public Builder trailingCommas(boolean value) { 106 | trailingCommas = value; 107 | return this; 108 | } 109 | 110 | public Renderer build() { 111 | return new Renderer( 112 | indentation, 113 | indentationChar, 114 | prettyPrinting, 115 | trailingNewline, 116 | trailingCommas 117 | ); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /chassembly/src/main/java/org/quiltmc/chasm/lang/internal/render/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package org.quiltmc.chasm.lang.internal.render; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /chassembly/src/test/java/org/quiltmc/chasm/lang/TestBase.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Locale; 11 | import java.util.stream.Stream; 12 | 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.DynamicTest; 15 | import org.junit.jupiter.api.TestFactory; 16 | import org.quiltmc.chasm.lang.api.ast.Node; 17 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 18 | 19 | public abstract class TestBase { 20 | private static final Path TESTS_DIR = Paths.get("src/test/resources/tests"); 21 | private static final Path RESULTS_DIR = Paths.get("src/test/resources/results"); 22 | 23 | @TestFactory 24 | public Stream testAll() { 25 | // Create a test for each test definition 26 | List tests = new ArrayList<>(); 27 | 28 | try (Stream paths = Files.walk(TESTS_DIR)) { 29 | paths.forEach(path -> { 30 | if (Files.isRegularFile(path) && path.toString().endsWith(".chasm")) { 31 | Path relative = TESTS_DIR.relativize(path); 32 | String name = relative.toString(); 33 | 34 | Path resultPath = RESULTS_DIR.resolve(relative); 35 | DynamicTest test = DynamicTest.dynamicTest(name, () -> { 36 | long start = System.nanoTime(); 37 | doTest(path, resultPath); 38 | long end = System.nanoTime(); 39 | double time = (end - start) * 1e-6; 40 | System.out.printf(Locale.US, "%s: %.2fms\n", name, time); 41 | }); 42 | 43 | tests.add(test); 44 | } 45 | }); 46 | } catch (IOException e) { 47 | throw new UncheckedIOException(e); 48 | } 49 | 50 | return tests.stream(); 51 | } 52 | 53 | protected abstract void doTest(Path testPath, Path resultPath) throws IOException; 54 | } 55 | -------------------------------------------------------------------------------- /chassembly/src/test/java/org/quiltmc/chasm/lang/TestEvaluation.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | 7 | import org.junit.jupiter.api.Assertions; 8 | import org.quiltmc.chasm.lang.api.ast.Node; 9 | import org.quiltmc.chasm.lang.api.eval.Evaluator; 10 | import org.quiltmc.chasm.lang.internal.render.Renderer; 11 | 12 | public class TestEvaluation extends TestBase { 13 | @Override 14 | protected void doTest(Path testPath, Path resultPath) throws IOException { 15 | Node parsed = Node.parse(testPath); 16 | Node result = parsed.evaluate(Evaluator.create(parsed)); 17 | StringBuilder builder = new StringBuilder(); 18 | result.render(Renderer.builder().prettyPrinting(true).build(), builder, 1); 19 | String rendered = builder.toString(); 20 | 21 | // If result doesn't exist yet, create file but fail anyway 22 | if (!Files.exists(resultPath)) { 23 | Files.createDirectories(resultPath.getParent()); 24 | Files.write(resultPath, rendered.getBytes()); 25 | Assertions.fail("Automatically created result file. Please verify results before committing."); 26 | } 27 | 28 | String expected = Files.readString(resultPath).replace("\r\n", "\n"); 29 | Assertions.assertEquals(expected, rendered); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /chassembly/src/test/java/org/quiltmc/chasm/lang/TestRendering.java: -------------------------------------------------------------------------------- 1 | package org.quiltmc.chasm.lang; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Path; 5 | 6 | import org.junit.jupiter.api.Assertions; 7 | import org.quiltmc.chasm.lang.api.ast.Node; 8 | import org.quiltmc.chasm.lang.internal.render.Renderer; 9 | 10 | public class TestRendering extends TestBase { 11 | @Override 12 | protected void doTest(Path testPath, Path resultPath) throws IOException { 13 | Renderer renderer = Renderer.builder().prettyPrinting(true).trailingCommas(false).build(); 14 | 15 | Node parsed = Node.parse(testPath); 16 | StringBuilder expectedBuilder = new StringBuilder(); 17 | parsed.render(renderer, expectedBuilder, 1); 18 | String expected = expectedBuilder.toString(); 19 | 20 | System.out.println(expected); 21 | 22 | Node parsedAgain = Node.parse(expected); 23 | StringBuilder actualBuilder = new StringBuilder(); 24 | parsedAgain.render(renderer, actualBuilder, 1); 25 | String actual = actualBuilder.toString(); 26 | 27 | Assertions.assertEquals(expected, actual); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure line endings don't get changed for auto-generated results files 2 | *.chasm text eol=lf 3 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/complex/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | int: 8, 3 | bool: true, 4 | string: "abc", 5 | ref: 8, 6 | self: 8, 7 | lambda: , 8 | call: 6, 9 | ternary: "false", 10 | equals: false, 11 | null_equals: true, 12 | is_null: false, 13 | not_null: true, 14 | fibonacci: , 15 | call_fib: 55, 16 | curry: , 17 | call_curry: 2, 18 | list: [1,"two",false,{ 19 | name: "object", 20 | },null,], 21 | list_index: "two", 22 | map_member: "object", 23 | map_index: "object", 24 | concat: [1,2,3,4,], 25 | list_concat: [1,2,1,"two",false,{ 26 | name: "object", 27 | },null,], 28 | map_concat: { 29 | a: "b", 30 | b: "c", 31 | c: "d", 32 | }, 33 | filter_source: [{ 34 | name: "hi", 35 | },{ 36 | name: "how", 37 | },{ 38 | name: "are", 39 | },{ 40 | name: "you", 41 | },], 42 | filter: [{ 43 | name: "hi", 44 | },{ 45 | name: "how", 46 | },], 47 | compareWeird: , 48 | test: , 49 | test_call: true, 50 | binop: , 51 | binop_call: 121, 52 | shift: , 53 | shift_call: 250, 54 | shifting: , 55 | xmas: "Hello World!", 56 | quoted_ref: 0, 57 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/complex/brainfuck.chasm: -------------------------------------------------------------------------------- 1 | "Hello World!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/chars_join.chasm: -------------------------------------------------------------------------------- 1 | "hello there!hello there!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/conversion.chasm: -------------------------------------------------------------------------------- 1 | { 2 | i2i: 2, 3 | f2i: 1, 4 | s2i: 42, 5 | i2f: 2.0, 6 | f2f: 1.5, 7 | s2f: 4.2, 8 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/entries.chasm: -------------------------------------------------------------------------------- 1 | { 2 | entries_result: [{ 3 | key: "a", 4 | value: "b", 5 | },{ 6 | key: "b", 7 | value: "c", 8 | },{ 9 | key: "c", 10 | value: "d", 11 | },{ 12 | key: "d", 13 | value: "e", 14 | },], 15 | from_entries_result: { 16 | a: "b", 17 | c: "d", 18 | d: "e", 19 | }, 20 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/flatten.chasm: -------------------------------------------------------------------------------- 1 | [0,1,2,3,100,10,3,"hello!","world!",4,[5,],[6,7,8,],] -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/len.chasm: -------------------------------------------------------------------------------- 1 | 6 -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/map.chasm: -------------------------------------------------------------------------------- 1 | [1,2,3,4,5,] -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/reduce.chasm: -------------------------------------------------------------------------------- 1 | "I have 10 apples" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/intrinsics/split_float.chasm: -------------------------------------------------------------------------------- 1 | { 2 | zero: { 3 | sign: 1, 4 | exponent: 0, 5 | coefficient: 0.0, 6 | }, 7 | neg_zero: { 8 | sign: -1, 9 | exponent: 0, 10 | coefficient: 0.0, 11 | }, 12 | nan: { 13 | sign: 1, 14 | exponent: 0, 15 | coefficient: NaN, 16 | }, 17 | infinity: { 18 | sign: 1, 19 | exponent: 0, 20 | coefficient: Infinity, 21 | }, 22 | neg_infinity: { 23 | sign: -1, 24 | exponent: 0, 25 | coefficient: Infinity, 26 | }, 27 | one: { 28 | sign: 1, 29 | exponent: 0, 30 | coefficient: 1.0, 31 | }, 32 | forty_two: { 33 | sign: 1, 34 | exponent: 5, 35 | coefficient: 1.3125, 36 | }, 37 | half: { 38 | sign: 1, 39 | exponent: -1, 40 | coefficient: 1.0, 41 | }, 42 | minus_one: { 43 | sign: -1, 44 | exponent: 0, 45 | coefficient: 1.0, 46 | }, 47 | subnormal: { 48 | sign: 1, 49 | exponent: -1074, 50 | coefficient: 1.0, 51 | }, 52 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/lambdas/capturing/basic.chasm: -------------------------------------------------------------------------------- 1 | "Pass!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/lambdas/capturing/global.chasm: -------------------------------------------------------------------------------- 1 | "Pass!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/lambdas/currying/basic.chasm: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/lambdas/recursion/basic.chasm: -------------------------------------------------------------------------------- 1 | "Pass!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/lambdas/recursion/indirect.chasm: -------------------------------------------------------------------------------- 1 | "Pass!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/lambdas/recursion/map_arg.chasm: -------------------------------------------------------------------------------- 1 | "Pass!" -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/literals/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | null_: null, 3 | bool: true, 4 | int: 5, 5 | hex: 10, 6 | bin: 2, 7 | float: 1.3, 8 | exp: 4000.0, 9 | string: "simple string", 10 | char: 101, 11 | escaped_string: "\"escaped\\ string\"", 12 | backslash: 92, 13 | single_quote: 39, 14 | "quoted key": 0, 15 | quoted_key_ref: 0, 16 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/literals/float.chasm: -------------------------------------------------------------------------------- 1 | { 2 | nan: NaN, 3 | plus_inf: Infinity, 4 | minus_inf: -Infinity, 5 | normal: 0.5, 6 | leading_point: 0.5, 7 | trailing_point: 5.0, 8 | exp: 200000.0, 9 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/references/global.chasm: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/syntax/comments.chasm: -------------------------------------------------------------------------------- 1 | { 2 | foo: 0, 3 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/syntax/trailing_commas.chasm: -------------------------------------------------------------------------------- 1 | { 2 | list: [1,2,3,], 3 | list_multiline: [1,2,3,], 4 | object: { 5 | a: 1, 6 | b: 2, 7 | c: 3, 8 | }, 9 | object_multiline: { 10 | a: 1, 11 | b: 2, 12 | c: 3, 13 | }, 14 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/results/ternary/nested.chasm: -------------------------------------------------------------------------------- 1 | 4 -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/complex/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | int: 5 + 3, 3 | bool: true, 4 | string: "abc", 5 | ref: int, 6 | self: int, 7 | lambda: arg -> arg + 2, 8 | call: lambda(4), 9 | ternary: false ? "true" : "false", 10 | equals: 5 = 3, 11 | null_equals: null = null, 12 | is_null: {foo: "bar"} = null, 13 | not_null: {foo: "bar"} != null, 14 | fibonacci: val -> val = 1 ? 1 : val = 2 ? 1 : fibonacci(val - 1) + fibonacci(val - 2), 15 | call_fib: fibonacci(10), 16 | curry: first -> second -> first - second, 17 | call_curry: curry(5)(3), 18 | list: [1, "two", false, { name: "object" }, null], 19 | list_index: list[1], 20 | map_member: list[3].name, 21 | map_index: list[3]["name"], 22 | concat: [1, 2] + [3, 4], 23 | list_concat: [1, 2] + list, 24 | map_concat: {a: "b", b: "replace me"} + {b: "c", c: "d"}, 25 | filter_source: [{name: "hi"}, {name: "how"}, {name: "are"}, {name: "you"}], 26 | filter: filter_source[entry -> chars(entry.name)[0] = 'h'], 27 | compareWeird: arg -> arg > 3 = arg < 5 * -arg + 1000, 28 | test: arg -> arg.a && arg.b || arg.c && arg.d, 29 | test_call: test({ a: true, b: false, c: true, d: true }), 30 | binop: x -> (x & ~7) | 1, 31 | binop_call: binop(125), 32 | shift: x -> x << 1, 33 | shift_call: shift(125), 34 | shifting: x -> x << 1 ^ x >> 1, 35 | xmas: "Hello " + "World" + "!", 36 | quoted_ref: {"hello world": 0}.`hello world`, 37 | } 38 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/chars_join.chasm: -------------------------------------------------------------------------------- 1 | { 2 | string: "hello there!", 3 | split: chars("hello there!"), 4 | rejoined: join(split), 5 | together: string + rejoined 6 | }.together -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/conversion.chasm: -------------------------------------------------------------------------------- 1 | { 2 | i2i: to_integer(2), 3 | f2i: to_integer(1.5), 4 | s2i: to_integer("42"), 5 | i2f: to_float(2), 6 | f2f: to_float(1.5), 7 | s2f: to_float("4.2"), 8 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/entries.chasm: -------------------------------------------------------------------------------- 1 | { 2 | input_map: {a: "b", b: "c", c: "d", d: "e"}, 3 | result: { 4 | entries_result: entries(input_map), 5 | from_entries_result: from_entries(entries_result[entry -> entry.key != "b"]) 6 | } 7 | }.result -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/flatten.chasm: -------------------------------------------------------------------------------- 1 | { 2 | list: [[0], [1, 2, 3], [100, 10, 3], ["hello!", "world!"], [4], [[5], [6, 7, 8]]], 3 | flattened: flatten(list) 4 | }.flattened -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/len.chasm: -------------------------------------------------------------------------------- 1 | { 2 | list: [0, 1, 2, 3, 4, 5], 3 | length: len(list) 4 | }.length -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/map.chasm: -------------------------------------------------------------------------------- 1 | { 2 | mapped: map({list: [0, 1, 2, 3, 4], func: val -> val + 1}) 3 | }.mapped -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/reduce.chasm: -------------------------------------------------------------------------------- 1 | { 2 | to_reduce: ["I have ", (5 + 5), " apples"], 3 | reduced: reduce({list: to_reduce, func: args -> args.first + args.second}) 4 | }.reduced -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/intrinsics/split_float.chasm: -------------------------------------------------------------------------------- 1 | { 2 | zero: split_float(0.0), 3 | neg_zero: split_float(-0.0), 4 | nan: split_float(0.0 / 0.0), 5 | infinity: split_float(1.0 / 0.0), 6 | neg_infinity: split_float(-1.0 / 0.0), 7 | one: split_float(1.0), 8 | forty_two: split_float(42.0), 9 | half: split_float(0.5), 10 | minus_one: split_float(-1.0), 11 | subnormal: split_float(4.9e-324), 12 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/lambdas/capturing/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | val: "Pass!", 3 | get_val: ignored -> val 4 | }.get_val(null) 5 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/lambdas/capturing/global.chasm: -------------------------------------------------------------------------------- 1 | { 2 | val: "Pass!", 3 | get_val: ignored -> $val 4 | }.get_val(null) 5 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/lambdas/currying/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | curry: first -> second -> first - second 3 | }.curry(5)(3) 4 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/lambdas/recursion/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | run: state -> state = 0 ? "Pass!" : run(state - 1) 3 | }.run(10) 4 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/lambdas/recursion/indirect.chasm: -------------------------------------------------------------------------------- 1 | { 2 | run1: state -> run2(state - 1), 3 | run2: state -> state = 0 ? "Pass!" : run1(state) 4 | }.run1(10) 5 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/lambdas/recursion/map_arg.chasm: -------------------------------------------------------------------------------- 1 | { 2 | run: state -> state.count = 0 ? "Pass!" : run({ count: state.count - 1 }) 3 | }.run({count: 10}) 4 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/literals/basic.chasm: -------------------------------------------------------------------------------- 1 | { 2 | null_: null, 3 | bool: true, 4 | int: 5, 5 | hex: 0xA, 6 | bin: 0b010, 7 | float: 1.3, 8 | exp: 4.0e3, 9 | string: "simple string", 10 | char: 'e', 11 | escaped_string: "\"escaped\\ string\"", 12 | backslash: '\\', 13 | single_quote: '\'', 14 | "quoted key": 0, 15 | quoted_key_ref: `quoted key`, 16 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/literals/float.chasm: -------------------------------------------------------------------------------- 1 | { 2 | nan: NaN, 3 | plus_inf: Infinity, 4 | minus_inf: -Infinity, 5 | normal: 0.5, 6 | leading_point: .5, 7 | trailing_point: 5., 8 | exp: 2.0e5, 9 | } -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/references/global.chasm: -------------------------------------------------------------------------------- 1 | { 2 | val: 1, 3 | inner: { 4 | val: 0, 5 | result: $val - val 6 | } 7 | }.inner.result 8 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/syntax/comments.chasm: -------------------------------------------------------------------------------- 1 | // This is a single line comment 2 | { 3 | /* This is a multi- 4 | line comment */ 5 | foo: 0, 6 | /* These can contain * stars and / slashes too */ 7 | } 8 | // Line comments are allowed at the end of the file -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/syntax/trailing_commas.chasm: -------------------------------------------------------------------------------- 1 | { 2 | list: [1, 2, 3,], 3 | list_multiline: [ 4 | 1, 5 | 2, 6 | 3, 7 | ], 8 | object: { a: 1, b:2, c:3 }, 9 | object_multiline: { 10 | a: 1, 11 | b: 2, 12 | c: 3, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chassembly/src/test/resources/tests/ternary/nested.chasm: -------------------------------------------------------------------------------- 1 | { 2 | val: 5 > (3 < 1 ? 4 : 7) ? 1 : 5 - (3 - 2) 3 | }.val 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuiltMC/chasm/832525bcd2d74f90b1118d8857a5265eb588165f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStorePath=wrapper/dists 5 | zipStoreBase=GRADLE_USER_HOME 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'chasm' 2 | 3 | include 'chasm' 4 | include 'chassembly' 5 | --------------------------------------------------------------------------------