From ba05b27deb12160ec3fcbb3126251226aaa6da72 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 14 Apr 2024 01:36:54 +0700 Subject: [PATCH] Embed Luna as subproject --- luna/build.gradle.kts | 23 + .../java/org/classdump/luna/Arithmetic.java | 292 +++ .../org/classdump/luna/ArrayByteString.java | 163 ++ .../java/org/classdump/luna/ByteString.java | 481 ++++ .../org/classdump/luna/ByteStringBuilder.java | 279 ++ .../classdump/luna/ByteStringInputStream.java | 41 + .../classdump/luna/ConversionException.java | 33 + .../java/org/classdump/luna/Conversions.java | 590 +++++ .../java/org/classdump/luna/LuaFormat.java | 481 ++++ .../org/classdump/luna/LuaMathOperators.java | 600 +++++ .../java/org/classdump/luna/LuaObject.java | 44 + .../classdump/luna/LuaRuntimeException.java | 96 + .../main/java/org/classdump/luna/LuaType.java | 294 +++ .../org/classdump/luna/MetatableAccessor.java | 113 + .../org/classdump/luna/MetatableProvider.java | 94 + .../java/org/classdump/luna/Metatables.java | 226 ++ .../NoIntegerRepresentationException.java | 32 + .../java/org/classdump/luna/Ordering.java | 441 ++++ .../classdump/luna/PlainValueTypeNamer.java | 79 + .../java/org/classdump/luna/StateContext.java | 25 + .../org/classdump/luna/StringByteString.java | 249 ++ .../main/java/org/classdump/luna/Table.java | 330 +++ .../java/org/classdump/luna/TableFactory.java | 42 + .../java/org/classdump/luna/Userdata.java | 49 + .../org/classdump/luna/ValueTypeNamer.java | 32 + .../java/org/classdump/luna/Variable.java | 57 + .../luna/compiler/CompiledModule.java | 48 + .../luna/compiler/CompilerChunkLoader.java | 207 ++ .../luna/compiler/CompilerSettings.java | 291 +++ .../classdump/luna/compiler/FunctionId.java | 133 + .../org/classdump/luna/compiler/IRFunc.java | 89 + .../classdump/luna/compiler/IRTranslator.java | 30 + .../compiler/IRTranslatorTransformer.java | 1159 +++++++++ .../classdump/luna/compiler/LuaCompiler.java | 238 ++ .../org/classdump/luna/compiler/Module.java | 70 + .../luna/compiler/ModuleBuilder.java | 40 + .../luna/compiler/TranslationUtils.java | 154 ++ .../analysis/AbstractUseDefVisitor.java | 290 +++ .../compiler/analysis/DependencyAnalyser.java | 29 + .../compiler/analysis/DependencyInfo.java | 35 + .../compiler/analysis/LivenessAnalyser.java | 284 +++ .../luna/compiler/analysis/LivenessInfo.java | 103 + .../compiler/analysis/NestedRefVisitor.java | 43 + .../analysis/NumericOperationType.java | 43 + .../luna/compiler/analysis/SlotAllocInfo.java | 67 + .../luna/compiler/analysis/SlotAllocator.java | 274 ++ .../analysis/StaticMathImplementation.java | 127 + .../luna/compiler/analysis/TypeInfo.java | 135 + .../luna/compiler/analysis/Typer.java | 233 ++ .../luna/compiler/analysis/TyperVisitor.java | 627 +++++ .../luna/compiler/analysis/package-info.java | 20 + .../compiler/analysis/types/AbstractType.java | 77 + .../compiler/analysis/types/BottomType.java | 57 + .../analysis/types/ConcreteLitType.java | 29 + .../compiler/analysis/types/ConcreteType.java | 85 + .../compiler/analysis/types/DynamicType.java | 59 + .../compiler/analysis/types/FunctionType.java | 159 ++ .../analysis/types/GradualTypeLike.java | 25 + .../compiler/analysis/types/LiteralType.java | 102 + .../compiler/analysis/types/LuaTypes.java | 58 + .../types/PartialOrderComparisonResult.java | 53 + .../compiler/analysis/types/ReturnType.java | 99 + .../luna/compiler/analysis/types/TopType.java | 50 + .../luna/compiler/analysis/types/Type.java | 84 + .../luna/compiler/analysis/types/TypeSeq.java | 272 ++ .../compiler/analysis/types/package-info.java | 20 + .../luna/compiler/gen/BytecodeEmitter.java | 23 + .../compiler/gen/ClassNameTranslator.java | 25 + .../luna/compiler/gen/CodeSegmenter.java | 142 ++ .../luna/compiler/gen/CompiledClass.java | 61 + .../luna/compiler/gen/SegmentedCode.java | 101 + .../gen/SuffixingClassNameTranslator.java | 39 + .../compiler/gen/asm/ASMBytecodeEmitter.java | 337 +++ .../compiler/gen/asm/BytecodeEmitVisitor.java | 1196 +++++++++ .../compiler/gen/asm/ConstructorMethod.java | 118 + .../luna/compiler/gen/asm/InvokeMethod.java | 316 +++ .../luna/compiler/gen/asm/ResumeMethod.java | 187 ++ .../luna/compiler/gen/asm/RunMethod.java | 789 ++++++ .../gen/asm/StaticConstructorMethod.java | 93 + .../compiler/gen/asm/helpers/ASMUtils.java | 164 ++ .../asm/helpers/BoxedPrimitivesMethods.java | 177 ++ .../gen/asm/helpers/ConversionMethods.java | 100 + .../gen/asm/helpers/DispatchMethods.java | 131 + .../asm/helpers/ExecutionContextMethods.java | 77 + .../gen/asm/helpers/InvokableMethods.java | 38 + .../compiler/gen/asm/helpers/InvokeKind.java | 65 + .../gen/asm/helpers/OperatorMethods.java | 74 + .../gen/asm/helpers/ReflectionUtils.java | 105 + .../gen/asm/helpers/ReturnBufferMethods.java | 130 + .../gen/asm/helpers/TableMethods.java | 56 + .../compiler/gen/asm/helpers/UtilMethods.java | 63 + .../gen/asm/helpers/VariableMethods.java | 69 + .../gen/asm/helpers/package-info.java | 20 + .../luna/compiler/gen/asm/package-info.java | 20 + .../luna/compiler/gen/package-info.java | 20 + .../luna/compiler/ir/AbstractVal.java | 21 + .../luna/compiler/ir/AbstractVar.java | 21 + .../luna/compiler/ir/BasicBlock.java | 65 + .../org/classdump/luna/compiler/ir/BinOp.java | 75 + .../luna/compiler/ir/BlockTermNode.java | 23 + .../classdump/luna/compiler/ir/BodyNode.java | 21 + .../classdump/luna/compiler/ir/Branch.java | 147 ++ .../luna/compiler/ir/CPUWithdraw.java | 56 + .../org/classdump/luna/compiler/ir/Call.java | 50 + .../classdump/luna/compiler/ir/Closure.java | 52 + .../org/classdump/luna/compiler/ir/Code.java | 129 + .../luna/compiler/ir/CodeBuilder.java | 188 ++ .../luna/compiler/ir/CodeVisitor.java | 52 + .../classdump/luna/compiler/ir/IRNode.java | 23 + .../classdump/luna/compiler/ir/IRVisitor.java | 256 ++ .../org/classdump/luna/compiler/ir/Jmp.java | 45 + .../classdump/luna/compiler/ir/JmpNode.java | 23 + .../org/classdump/luna/compiler/ir/Label.java | 42 + .../org/classdump/luna/compiler/ir/Line.java | 38 + .../classdump/luna/compiler/ir/LoadConst.java | 127 + .../classdump/luna/compiler/ir/MultiGet.java | 50 + .../classdump/luna/compiler/ir/MultiVal.java | 32 + .../classdump/luna/compiler/ir/PhiLoad.java | 44 + .../classdump/luna/compiler/ir/PhiStore.java | 44 + .../classdump/luna/compiler/ir/PhiVal.java | 32 + .../luna/compiler/ir/RegProvider.java | 56 + .../org/classdump/luna/compiler/ir/Ret.java | 44 + .../org/classdump/luna/compiler/ir/TCall.java | 50 + .../classdump/luna/compiler/ir/TabGet.java | 50 + .../classdump/luna/compiler/ir/TabNew.java | 51 + .../luna/compiler/ir/TabRawAppendMulti.java | 51 + .../classdump/luna/compiler/ir/TabRawSet.java | 51 + .../luna/compiler/ir/TabRawSetInt.java | 51 + .../classdump/luna/compiler/ir/TabSet.java | 50 + .../classdump/luna/compiler/ir/ToNext.java | 44 + .../classdump/luna/compiler/ir/ToNumber.java | 51 + .../org/classdump/luna/compiler/ir/UnOp.java | 56 + .../classdump/luna/compiler/ir/UpLoad.java | 44 + .../classdump/luna/compiler/ir/UpStore.java | 44 + .../org/classdump/luna/compiler/ir/UpVar.java | 39 + .../org/classdump/luna/compiler/ir/VList.java | 44 + .../org/classdump/luna/compiler/ir/Val.java | 32 + .../org/classdump/luna/compiler/ir/Var.java | 32 + .../classdump/luna/compiler/ir/VarInit.java | 44 + .../classdump/luna/compiler/ir/VarLoad.java | 44 + .../classdump/luna/compiler/ir/VarStore.java | 44 + .../classdump/luna/compiler/ir/Vararg.java | 38 + .../luna/compiler/ir/package-info.java | 20 + .../classdump/luna/compiler/package-info.java | 20 + .../luna/compiler/tf/BranchInliner.java | 30 + .../compiler/tf/BranchInlinerVisitor.java | 85 + .../luna/compiler/tf/CPUAccounter.java | 35 + .../compiler/tf/CPUAccountingVisitor.java | 258 ++ .../luna/compiler/tf/CodeSimplifier.java | 148 ++ .../compiler/tf/CodeTransformerVisitor.java | 109 + .../luna/compiler/tf/ConstFolder.java | 30 + .../luna/compiler/tf/ConstFolderVisitor.java | 96 + .../luna/compiler/tf/DeadCodePruner.java | 31 + .../compiler/tf/DeadCodePrunerVisitor.java | 125 + .../luna/compiler/tf/ModuleFilter.java | 78 + .../luna/compiler/tf/package-info.java | 20 + .../luna/compiler/util/CodeUtils.java | 180 ++ .../util/DefaultNodeActionVisitor.java | 208 ++ .../luna/compiler/util/IRPrinter.java | 35 + .../luna/compiler/util/IRPrinterVisitor.java | 253 ++ .../luna/compiler/util/package-info.java | 20 + .../luna/env/RuntimeEnvironment.java | 77 + .../luna/env/RuntimeEnvironments.java | 56 + .../luna/env/SystemRuntimeEnvironment.java | 75 + .../org/classdump/luna/env/package-info.java | 20 + .../classdump/luna/exec/CallEventHandler.java | 64 + .../classdump/luna/exec/CallException.java | 74 + .../classdump/luna/exec/CallInitialiser.java | 34 + .../luna/exec/CallPausedException.java | 54 + .../org/classdump/luna/exec/Continuation.java | 45 + .../luna/exec/DirectCallExecutor.java | 361 +++ .../exec/InvalidContinuationException.java | 35 + .../luna/exec/OneShotContinuation.java | 34 + .../classdump/luna/exec/StackTraceback.java | 274 ++ .../org/classdump/luna/exec/package-info.java | 20 + .../impl/AbstractMappedCollectionView.java | 69 + .../luna/impl/AbstractStateContext.java | 150 ++ .../luna/impl/DefaultMetatableAccessor.java | 199 ++ .../luna/impl/DefaultSavedState.java | 79 + .../luna/impl/DefaultStateContext.java | 28 + .../org/classdump/luna/impl/DefaultTable.java | 110 + .../classdump/luna/impl/DefaultUserdata.java | 66 + .../luna/impl/DelegatingReturnBuffer.java | 162 ++ .../classdump/luna/impl/ImmutableTable.java | 332 +++ .../impl/MappedDelegatingReturnBuffer.java | 227 ++ .../impl/NonsuspendableFunctionException.java | 49 + .../luna/impl/PairCachingReturnBuffer.java | 334 +++ .../classdump/luna/impl/ReturnBuffers.java | 168 ++ .../luna/impl/SchedulingContexts.java | 172 ++ .../luna/impl/SimpleReturnBuffer.java | 190 ++ .../classdump/luna/impl/StateContexts.java | 57 + .../luna/impl/UnimplementedFunction.java | 62 + .../org/classdump/luna/impl/package-info.java | 20 + .../luna/lib/AbstractLibFunction.java | 67 + .../classdump/luna/lib/ArgumentIterator.java | 811 ++++++ .../luna/lib/AssertionFailedException.java | 36 + .../luna/lib/BadArgumentException.java | 96 + .../java/org/classdump/luna/lib/BasicLib.java | 1842 ++++++++++++++ .../org/classdump/luna/lib/CoroutineLib.java | 453 ++++ .../java/org/classdump/luna/lib/DebugLib.java | 899 +++++++ .../java/org/classdump/luna/lib/IoFile.java | 251 ++ .../java/org/classdump/luna/lib/IoLib.java | 824 ++++++ .../classdump/luna/lib/LoaderProvider.java | 44 + .../java/org/classdump/luna/lib/MathLib.java | 1233 +++++++++ .../org/classdump/luna/lib/ModuleLib.java | 712 ++++++ .../lib/NameMetamethodValueTypeNamer.java | 82 + .../java/org/classdump/luna/lib/OsLib.java | 576 +++++ .../luna/lib/SimpleLoaderFunction.java | 59 + .../classdump/luna/lib/StandardLibrary.java | 143 ++ .../org/classdump/luna/lib/StringLib.java | 1856 ++++++++++++++ .../org/classdump/luna/lib/StringPattern.java | 1233 +++++++++ .../java/org/classdump/luna/lib/TableLib.java | 1736 +++++++++++++ .../org/classdump/luna/lib/TableUtil.java | 74 + .../luna/lib/UnexpectedArgumentException.java | 29 + .../java/org/classdump/luna/lib/Utf8Lib.java | 278 ++ .../luna/lib/io/InputStreamIoFile.java | 71 + .../luna/lib/io/OutputStreamIoFile.java | 71 + .../luna/lib/io/SeekableInputStream.java | 69 + .../luna/lib/io/SeekableOutputStream.java | 60 + .../classdump/luna/lib/io/SeekableStream.java | 27 + .../classdump/luna/lib/io/package-info.java | 20 + .../org/classdump/luna/lib/package-info.java | 20 + .../classdump/luna/load/ChunkClassLoader.java | 118 + .../org/classdump/luna/load/ChunkFactory.java | 35 + .../org/classdump/luna/load/ChunkLoader.java | 69 + .../classdump/luna/load/CompiledChunk.java | 45 + .../classdump/luna/load/LoaderException.java | 121 + .../org/classdump/luna/load/package-info.java | 20 + .../java/org/classdump/luna/package-info.java | 20 + .../org/classdump/luna/parser/AssignRest.java | 53 + .../classdump/luna/parser/ExprBuilder.java | 95 + .../java/org/classdump/luna/parser/Exprs.java | 106 + .../luna/parser/FunctionNameBuilder.java | 56 + .../classdump/luna/parser/ParseException.java | 195 ++ .../org/classdump/luna/parser/Parser.java | 2257 +++++++++++++++++ .../luna/parser/ParserConstants.java | 298 +++ .../luna/parser/ParserTokenManager.java | 1892 ++++++++++++++ .../org/classdump/luna/parser/PostfixOp.java | 71 + .../luna/parser/SimpleCharStream.java | 472 ++++ .../classdump/luna/parser/SourceElement.java | 44 + .../org/classdump/luna/parser/Statements.java | 125 + .../java/org/classdump/luna/parser/Token.java | 132 + .../classdump/luna/parser/TokenMgrError.java | 148 ++ .../luna/parser/analysis/FunctionVarInfo.java | 53 + .../analysis/FunctionVarInfoBuilder.java | 178 ++ .../analysis/LabelAnnotatorTransformer.java | 48 + .../analysis/LabelResolutionTransformer.java | 311 +++ .../analysis/NameResolutionException.java | 25 + .../analysis/NameResolutionTransformer.java | 195 ++ .../luna/parser/analysis/NameResolver.java | 31 + .../luna/parser/analysis/ResolvedLabel.java | 21 + .../parser/analysis/ResolvedVariable.java | 47 + .../luna/parser/analysis/VarMapping.java | 51 + .../luna/parser/analysis/Variable.java | 57 + .../luna/parser/analysis/package-info.java | 20 + .../luna/parser/ast/AssignStatement.java | 54 + .../classdump/luna/parser/ast/Attributes.java | 84 + .../luna/parser/ast/BinaryOperationExpr.java | 59 + .../org/classdump/luna/parser/ast/Block.java | 48 + .../luna/parser/ast/BodyStatement.java | 27 + .../luna/parser/ast/BooleanLiteral.java | 43 + .../luna/parser/ast/BreakStatement.java | 30 + .../classdump/luna/parser/ast/CallExpr.java | 98 + .../luna/parser/ast/CallStatement.java | 47 + .../org/classdump/luna/parser/ast/Chunk.java | 60 + .../luna/parser/ast/ConditionalBlock.java | 47 + .../luna/parser/ast/DoStatement.java | 47 + .../org/classdump/luna/parser/ast/Expr.java | 27 + .../luna/parser/ast/FunctionDefExpr.java | 103 + .../luna/parser/ast/GenericForStatement.java | 72 + .../luna/parser/ast/GotoStatement.java | 59 + .../luna/parser/ast/IfStatement.java | 62 + .../classdump/luna/parser/ast/IndexExpr.java | 53 + .../classdump/luna/parser/ast/LValueExpr.java | 31 + .../luna/parser/ast/LabelStatement.java | 59 + .../classdump/luna/parser/ast/Literal.java | 23 + .../luna/parser/ast/LiteralExpr.java | 47 + .../luna/parser/ast/LocalDeclStatement.java | 69 + .../classdump/luna/parser/ast/MultiExpr.java | 25 + .../org/classdump/luna/parser/ast/Name.java | 63 + .../classdump/luna/parser/ast/NilLiteral.java | 31 + .../classdump/luna/parser/ast/Numeral.java | 125 + .../luna/parser/ast/NumericForStatement.java | 85 + .../classdump/luna/parser/ast/Operator.java | 105 + .../classdump/luna/parser/ast/ParenExpr.java | 47 + .../luna/parser/ast/RepeatUntilStatement.java | 53 + .../luna/parser/ast/ReturnStatement.java | 47 + .../classdump/luna/parser/ast/SourceInfo.java | 61 + .../classdump/luna/parser/ast/Statement.java | 25 + .../luna/parser/ast/StringLiteral.java | 297 +++ .../luna/parser/ast/SyntaxElement.java | 42 + .../luna/parser/ast/TableConstructorExpr.java | 76 + .../luna/parser/ast/Transformer.java | 228 ++ .../luna/parser/ast/UnaryOperationExpr.java | 53 + .../classdump/luna/parser/ast/VarExpr.java | 51 + .../luna/parser/ast/VarargsExpr.java | 30 + .../classdump/luna/parser/ast/Visitor.java | 261 ++ .../luna/parser/ast/WhileStatement.java | 53 + .../luna/parser/ast/package-info.java | 20 + .../luna/parser/ast/util/AttributeUtils.java | 38 + .../luna/parser/ast/util/package-info.java | 20 + .../classdump/luna/parser/package-info.java | 20 + .../parser/util/FormattingPrinterVisitor.java | 544 ++++ .../org/classdump/luna/parser/util/Util.java | 44 + .../luna/parser/util/package-info.java | 20 + .../luna/runtime/AbstractFunction0.java | 59 + .../luna/runtime/AbstractFunction1.java | 66 + .../luna/runtime/AbstractFunction2.java | 69 + .../luna/runtime/AbstractFunction3.java | 71 + .../luna/runtime/AbstractFunction4.java | 74 + .../luna/runtime/AbstractFunction5.java | 77 + .../luna/runtime/AbstractFunctionAnyArg.java | 59 + .../runtime/AbstractUntypedFunction0.java | 26 + .../runtime/AbstractUntypedFunction1.java | 27 + .../runtime/AbstractUntypedFunction2.java | 27 + .../runtime/AbstractUntypedFunction3.java | 27 + .../runtime/AbstractUntypedFunction4.java | 28 + .../runtime/AbstractUntypedFunction5.java | 28 + .../org/classdump/luna/runtime/AsyncTask.java | 59 + .../java/org/classdump/luna/runtime/Call.java | 539 ++++ .../luna/runtime/ControlThrowablePayload.java | 35 + .../org/classdump/luna/runtime/Coroutine.java | 167 ++ .../org/classdump/luna/runtime/Dispatch.java | 1571 ++++++++++++ .../org/classdump/luna/runtime/Errors.java | 123 + .../luna/runtime/ExecutionContext.java | 187 ++ .../IllegalCoroutineStateException.java | 37 + .../IllegalOperationAttemptException.java | 46 + .../classdump/luna/runtime/LuaFunction.java | 203 ++ .../luna/runtime/ProtectedResumable.java | 56 + .../runtime/ResolvedControlThrowable.java | 66 + .../org/classdump/luna/runtime/Resumable.java | 49 + .../classdump/luna/runtime/ResumeInfo.java | 53 + .../classdump/luna/runtime/ReturnBuffer.java | 652 +++++ .../luna/runtime/ReturnBufferFactory.java | 31 + .../luna/runtime/RuntimeCallInitialiser.java | 75 + .../luna/runtime/SchedulingContext.java | 59 + .../runtime/SchedulingContextFactory.java | 31 + .../runtime/UnresolvedControlThrowable.java | 84 + .../classdump/luna/runtime/package-info.java | 20 + .../luna/util/ArrayByteIterator.java | 64 + .../org/classdump/luna/util/ByteIterator.java | 42 + .../org/classdump/luna/util/ByteVector.java | 114 + .../luna/util/CharsetEncoderByteIterator.java | 214 ++ .../java/org/classdump/luna/util/Check.java | 230 ++ .../java/org/classdump/luna/util/Cons.java | 368 +++ .../luna/util/TraversableHashMap.java | 451 ++++ .../org/classdump/luna/util/package-info.java | 20 + luna/src/main/javacc/Parser.jj | 754 ++++++ 348 files changed, 55896 insertions(+) create mode 100644 luna/build.gradle.kts create mode 100644 luna/src/main/java/org/classdump/luna/Arithmetic.java create mode 100644 luna/src/main/java/org/classdump/luna/ArrayByteString.java create mode 100644 luna/src/main/java/org/classdump/luna/ByteString.java create mode 100644 luna/src/main/java/org/classdump/luna/ByteStringBuilder.java create mode 100644 luna/src/main/java/org/classdump/luna/ByteStringInputStream.java create mode 100644 luna/src/main/java/org/classdump/luna/ConversionException.java create mode 100644 luna/src/main/java/org/classdump/luna/Conversions.java create mode 100644 luna/src/main/java/org/classdump/luna/LuaFormat.java create mode 100644 luna/src/main/java/org/classdump/luna/LuaMathOperators.java create mode 100644 luna/src/main/java/org/classdump/luna/LuaObject.java create mode 100644 luna/src/main/java/org/classdump/luna/LuaRuntimeException.java create mode 100644 luna/src/main/java/org/classdump/luna/LuaType.java create mode 100644 luna/src/main/java/org/classdump/luna/MetatableAccessor.java create mode 100644 luna/src/main/java/org/classdump/luna/MetatableProvider.java create mode 100644 luna/src/main/java/org/classdump/luna/Metatables.java create mode 100644 luna/src/main/java/org/classdump/luna/NoIntegerRepresentationException.java create mode 100644 luna/src/main/java/org/classdump/luna/Ordering.java create mode 100644 luna/src/main/java/org/classdump/luna/PlainValueTypeNamer.java create mode 100644 luna/src/main/java/org/classdump/luna/StateContext.java create mode 100644 luna/src/main/java/org/classdump/luna/StringByteString.java create mode 100644 luna/src/main/java/org/classdump/luna/Table.java create mode 100644 luna/src/main/java/org/classdump/luna/TableFactory.java create mode 100644 luna/src/main/java/org/classdump/luna/Userdata.java create mode 100644 luna/src/main/java/org/classdump/luna/ValueTypeNamer.java create mode 100644 luna/src/main/java/org/classdump/luna/Variable.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/CompiledModule.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/CompilerChunkLoader.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/CompilerSettings.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/FunctionId.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/IRFunc.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/IRTranslator.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/IRTranslatorTransformer.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/LuaCompiler.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/Module.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ModuleBuilder.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/TranslationUtils.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/AbstractUseDefVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/DependencyAnalyser.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/DependencyInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/LivenessAnalyser.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/LivenessInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/NestedRefVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/NumericOperationType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/SlotAllocInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/SlotAllocator.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/StaticMathImplementation.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/TypeInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/Typer.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/TyperVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/AbstractType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/BottomType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/ConcreteLitType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/ConcreteType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/DynamicType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/FunctionType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/GradualTypeLike.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/LiteralType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/LuaTypes.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/PartialOrderComparisonResult.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/ReturnType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/TopType.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/Type.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/TypeSeq.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/analysis/types/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/BytecodeEmitter.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/ClassNameTranslator.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/CodeSegmenter.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/CompiledClass.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/SegmentedCode.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/SuffixingClassNameTranslator.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/ASMBytecodeEmitter.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/BytecodeEmitVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/ConstructorMethod.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/InvokeMethod.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/ResumeMethod.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/RunMethod.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/StaticConstructorMethod.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/ASMUtils.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/BoxedPrimitivesMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/ConversionMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/DispatchMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/ExecutionContextMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/InvokableMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/InvokeKind.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/OperatorMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/ReflectionUtils.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/ReturnBufferMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/TableMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/UtilMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/VariableMethods.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/helpers/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/asm/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/gen/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/AbstractVal.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/AbstractVar.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/BasicBlock.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/BinOp.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/BlockTermNode.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/BodyNode.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Branch.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/CPUWithdraw.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Call.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Closure.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Code.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/CodeBuilder.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/CodeVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/IRNode.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/IRVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Jmp.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/JmpNode.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Label.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Line.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/LoadConst.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/MultiGet.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/MultiVal.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/PhiLoad.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/PhiStore.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/PhiVal.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/RegProvider.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Ret.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TCall.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TabGet.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TabNew.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TabRawAppendMulti.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TabRawSet.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TabRawSetInt.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/TabSet.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/ToNext.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/ToNumber.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/UnOp.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/UpLoad.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/UpStore.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/UpVar.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/VList.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Val.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Var.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/VarInit.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/VarLoad.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/VarStore.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/Vararg.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/ir/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/BranchInliner.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/BranchInlinerVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/CPUAccounter.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/CPUAccountingVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/CodeSimplifier.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/CodeTransformerVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/ConstFolder.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/ConstFolderVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/DeadCodePruner.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/DeadCodePrunerVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/ModuleFilter.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/tf/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/util/CodeUtils.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/util/DefaultNodeActionVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/util/IRPrinter.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/util/IRPrinterVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/compiler/util/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/env/RuntimeEnvironment.java create mode 100644 luna/src/main/java/org/classdump/luna/env/RuntimeEnvironments.java create mode 100644 luna/src/main/java/org/classdump/luna/env/SystemRuntimeEnvironment.java create mode 100644 luna/src/main/java/org/classdump/luna/env/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/CallEventHandler.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/CallException.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/CallInitialiser.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/CallPausedException.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/Continuation.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/DirectCallExecutor.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/InvalidContinuationException.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/OneShotContinuation.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/StackTraceback.java create mode 100644 luna/src/main/java/org/classdump/luna/exec/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/AbstractMappedCollectionView.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/AbstractStateContext.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/DefaultMetatableAccessor.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/DefaultSavedState.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/DefaultStateContext.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/DefaultTable.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/DefaultUserdata.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/DelegatingReturnBuffer.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/ImmutableTable.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/MappedDelegatingReturnBuffer.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/NonsuspendableFunctionException.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/PairCachingReturnBuffer.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/ReturnBuffers.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/SchedulingContexts.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/SimpleReturnBuffer.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/StateContexts.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/UnimplementedFunction.java create mode 100644 luna/src/main/java/org/classdump/luna/impl/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/AbstractLibFunction.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/ArgumentIterator.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/AssertionFailedException.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/BadArgumentException.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/BasicLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/CoroutineLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/DebugLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/IoFile.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/IoLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/LoaderProvider.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/MathLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/ModuleLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/NameMetamethodValueTypeNamer.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/OsLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/SimpleLoaderFunction.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/StandardLibrary.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/StringLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/StringPattern.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/TableLib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/TableUtil.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/UnexpectedArgumentException.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/Utf8Lib.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/io/InputStreamIoFile.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/io/OutputStreamIoFile.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/io/SeekableInputStream.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/io/SeekableOutputStream.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/io/SeekableStream.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/io/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/lib/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/load/ChunkClassLoader.java create mode 100644 luna/src/main/java/org/classdump/luna/load/ChunkFactory.java create mode 100644 luna/src/main/java/org/classdump/luna/load/ChunkLoader.java create mode 100644 luna/src/main/java/org/classdump/luna/load/CompiledChunk.java create mode 100644 luna/src/main/java/org/classdump/luna/load/LoaderException.java create mode 100644 luna/src/main/java/org/classdump/luna/load/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/AssignRest.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ExprBuilder.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/Exprs.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/FunctionNameBuilder.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ParseException.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/Parser.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ParserConstants.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ParserTokenManager.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/PostfixOp.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/SimpleCharStream.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/SourceElement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/Statements.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/Token.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/TokenMgrError.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/FunctionVarInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/FunctionVarInfoBuilder.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/LabelAnnotatorTransformer.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/LabelResolutionTransformer.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/NameResolutionException.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/NameResolutionTransformer.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/NameResolver.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/ResolvedLabel.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/ResolvedVariable.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/VarMapping.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/Variable.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/analysis/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/AssignStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Attributes.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/BinaryOperationExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Block.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/BodyStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/BooleanLiteral.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/BreakStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/CallExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/CallStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Chunk.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/ConditionalBlock.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/DoStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Expr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/FunctionDefExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/GenericForStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/GotoStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/IfStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/IndexExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/LValueExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/LabelStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Literal.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/LiteralExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/LocalDeclStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/MultiExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Name.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/NilLiteral.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Numeral.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/NumericForStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Operator.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/ParenExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/RepeatUntilStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/ReturnStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/SourceInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Statement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/StringLiteral.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/SyntaxElement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/TableConstructorExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Transformer.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/UnaryOperationExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/VarExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/VarargsExpr.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/Visitor.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/WhileStatement.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/util/AttributeUtils.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/ast/util/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/util/FormattingPrinterVisitor.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/util/Util.java create mode 100644 luna/src/main/java/org/classdump/luna/parser/util/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunction0.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunction1.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunction2.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunction3.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunction4.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunction5.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractFunctionAnyArg.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractUntypedFunction0.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractUntypedFunction1.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractUntypedFunction2.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractUntypedFunction3.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractUntypedFunction4.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AbstractUntypedFunction5.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/AsyncTask.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/Call.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ControlThrowablePayload.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/Coroutine.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/Dispatch.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/Errors.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ExecutionContext.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/IllegalCoroutineStateException.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/IllegalOperationAttemptException.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/LuaFunction.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ProtectedResumable.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ResolvedControlThrowable.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/Resumable.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ResumeInfo.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ReturnBuffer.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/ReturnBufferFactory.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/RuntimeCallInitialiser.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/SchedulingContext.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/SchedulingContextFactory.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/UnresolvedControlThrowable.java create mode 100644 luna/src/main/java/org/classdump/luna/runtime/package-info.java create mode 100644 luna/src/main/java/org/classdump/luna/util/ArrayByteIterator.java create mode 100644 luna/src/main/java/org/classdump/luna/util/ByteIterator.java create mode 100644 luna/src/main/java/org/classdump/luna/util/ByteVector.java create mode 100644 luna/src/main/java/org/classdump/luna/util/CharsetEncoderByteIterator.java create mode 100644 luna/src/main/java/org/classdump/luna/util/Check.java create mode 100644 luna/src/main/java/org/classdump/luna/util/Cons.java create mode 100644 luna/src/main/java/org/classdump/luna/util/TraversableHashMap.java create mode 100644 luna/src/main/java/org/classdump/luna/util/package-info.java create mode 100644 luna/src/main/javacc/Parser.jj diff --git a/luna/build.gradle.kts b/luna/build.gradle.kts new file mode 100644 index 00000000..c19dd01f --- /dev/null +++ b/luna/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("java") +} + +group = "org.classdump.luna" +version = "0.4.2" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.9.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + + implementation("org.ow2.asm:asm:9.2") + implementation("org.ow2.asm:asm-tree:9.2") + implementation("org.ow2.asm:asm-util:9.2") +} + +tasks.test { + useJUnitPlatform() +} diff --git a/luna/src/main/java/org/classdump/luna/Arithmetic.java b/luna/src/main/java/org/classdump/luna/Arithmetic.java new file mode 100644 index 00000000..e7bc8d7a --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Arithmetic.java @@ -0,0 +1,292 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A representation of one of the two arithmetic modes (integer or float). + * + *

This class serves as a bridge between the representation of Lua numbers as + * {@link java.lang.Number} and the appropriate dispatch of arithmetic operations + * via the methods of {@link org.classdump.luna.LuaMathOperators}.

+ * + *

There are two concrete instances of this class, {@link #FLOAT} (the float + * arithmetic) and {@link #INTEGER} (the integer arithmetic). In order to obtain + * the correct instance for the given numbers, use the static methods + * {@link #of(Number, Number)} (for lookup based on two arguments, i.e. for binary + * operations) or {@link #of(Number)} (for lookup based on a single argument, i.e. + * for unary minus).

+ * + *

The arithmetic methods of this class yield boxed results.

+ * + *

Example: Given two objects {@code a} and {@code b} of type {@code Number}, + * use the {@code Arithmetic} class to compute the value of the Lua expression + * {@code (a // b)}:

+ * + *
+ *   // Number a, b
+ *   final Number result;
+ *   Arithmetic m = Arithmetic.of(a, b);
+ *   if (m != null) {
+ *       result = m.idiv(a, b);
+ *   }
+ *   else {
+ *       throw new IllegalStateException("a or b is nil");
+ *   }
+ * 
+ */ +public abstract class Arithmetic { + + /** + * Integer arithmetic. + * + *

Invokes arithmetic operations from {@link LuaMathOperators} with all arguments + * converted to Lua integers (i.e. {@code long}s).

+ */ + public static final Arithmetic INTEGER = new IntegerArithmetic(); + /** + * Float arithmetic. + * + *

Invokes arithmetic operations from {@link LuaMathOperators} with all arguments + * converted to Lua floats (i.e. {@code double}s).

+ */ + public static final Arithmetic FLOAT = new FloatArithmetic(); + + private Arithmetic() { + // not to be instantiated by the outside world + } + + /** + * Return an arithmetic based on the concrete types of the arguments {@code a} + * and {@code b}. + * + *

If either of {@code a} or {@code b} is {@code null}, returns {@code null}. + * Otherwise, if either of {@code a} or {@code b} a Lua float, returns {@link #FLOAT}. + * If {@code a} and {@code b} are both Lua integers, returns {@link #INTEGER}.

+ * + * @param a first argument, may be {@code null} + * @param b second argument, may be {@code null} + * @return {@code null} if {@code a} or {@code b} is {@code null}; {@link #FLOAT} if {@code a} or + * {@code b} is a Lua float; {@link #INTEGER} if {@code a} and {@code b} are Lua integers + */ + public static Arithmetic of(Number a, Number b) { + if (a == null || b == null) { + return null; + } else if ((a instanceof Double || a instanceof Float) + || (b instanceof Double || b instanceof Float)) { + return FLOAT; + } else { + return INTEGER; + } + } + + /** + * Return an arithmetic based on the concrete type of the argument {@code n}. + * + * If {@code n} is {@code null}, returns {@code null}; otherwise, returns + * {@link #FLOAT} if {@code n} is a Lua float, or {@link #INTEGER} if {@code n} + * is a Lua integer. + * + * @param n the argument, may be {@code null} + * @return {@code null} if {@code n} is {@code null}; {@link #FLOAT} if {@code n} is a Lua float; + * {@link #INTEGER} if {@code n} is a Lua integer + */ + public static Arithmetic of(Number n) { + if (n == null) { + return null; + } else if (n instanceof Double || n instanceof Float) { + return FLOAT; + } else { + return INTEGER; + } + } + + /** + * Returns the boxed result of the Lua addition of the two numbers + * {@code a} and {@code b}. + * + * @param a first addend, must not be {@code null} + * @param b second addend, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a + b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Number add(Number a, Number b); + + /** + * Returns the boxed result of the Lua subtraction of the two numbers + * {@code a} and {@code b}. + * + * @param a the minuend, must not be {@code null} + * @param b the subtrahend, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a - b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Number sub(Number a, Number b); + + /** + * Returns the boxed result of the Lua multiplication of the two numbers + * {@code a} and {@code b}. + * + * @param a first factor, must not be {@code null} + * @param b second factor, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a * b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Number mul(Number a, Number b); + + /** + * Returns the boxed result of the Lua float division of the two numbers + * {@code a} and {@code b}. + * + * @param a the dividend, must not be {@code null} + * @param b the divisor, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a / b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Double div(Number a, Number b); + + /** + * Returns the boxed result of the Lua modulo of the two numbers + * {@code a} and {@code b}. + * + * @param a the dividend, must not be {@code null} + * @param b the divisor, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a % b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Number mod(Number a, Number b); + + /** + * Returns the boxed result of the Lua floor division of the two numbers + * {@code a} and {@code b}. + * + * @param a the dividend, must not be {@code null} + * @param b the divisor, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a // b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Number idiv(Number a, Number b); + + /** + * Returns the boxed result of the Lua exponentiation of the two numbers + * {@code a} and {@code b}. + * + * @param a the base, must not be {@code null} + * @param b the exponent, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (a ^ b)} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract Double pow(Number a, Number b); + + /** + * Returns the boxed result of the Lua arithmetic negation of the number + * {@code n}. + * + * @param n the operand, must not be {@code null} + * @return the (boxed) value of the Lua expression {@code (-n)} + * @throws NullPointerException if {@code n} is {@code null} + */ + public abstract Number unm(Number n); + + private static final class IntegerArithmetic extends Arithmetic { + + @Override + public Long add(Number a, Number b) { + return LuaMathOperators.add(a.longValue(), b.longValue()); + } + + @Override + public Long sub(Number a, Number b) { + return LuaMathOperators.sub(a.longValue(), b.longValue()); + } + + @Override + public Long mul(Number a, Number b) { + return LuaMathOperators.mul(a.longValue(), b.longValue()); + } + + @Override + public Double div(Number a, Number b) { + return LuaMathOperators.div(a.longValue(), b.longValue()); + } + + @Override + public Long mod(Number a, Number b) { + return LuaMathOperators.mod(a.longValue(), b.longValue()); + } + + @Override + public Long idiv(Number a, Number b) { + return LuaMathOperators.idiv(a.longValue(), b.longValue()); + } + + @Override + public Double pow(Number a, Number b) { + return LuaMathOperators.pow(a.longValue(), b.longValue()); + } + + @Override + public Long unm(Number n) { + return LuaMathOperators.unm(n.longValue()); + } + + } + + private static final class FloatArithmetic extends Arithmetic { + + @Override + public Double add(Number a, Number b) { + return LuaMathOperators.add(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double sub(Number a, Number b) { + return LuaMathOperators.sub(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double mul(Number a, Number b) { + return LuaMathOperators.mul(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double div(Number a, Number b) { + return LuaMathOperators.div(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double mod(Number a, Number b) { + return LuaMathOperators.mod(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double idiv(Number a, Number b) { + return LuaMathOperators.idiv(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double pow(Number a, Number b) { + return LuaMathOperators.pow(a.doubleValue(), b.doubleValue()); + } + + @Override + public Double unm(Number n) { + return LuaMathOperators.unm(n.doubleValue()); + } + + } + +} diff --git a/luna/src/main/java/org/classdump/luna/ArrayByteString.java b/luna/src/main/java/org/classdump/luna/ArrayByteString.java new file mode 100644 index 00000000..a5375720 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/ArrayByteString.java @@ -0,0 +1,163 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; +import org.classdump.luna.util.ArrayByteIterator; +import org.classdump.luna.util.ByteIterator; + +/** + * A byte string backed by a byte array. + */ +class ArrayByteString extends ByteString { + + static final ArrayByteString EMPTY_INSTANCE = new ArrayByteString(new byte[0]); + + private final byte[] bytes; + private int hashCode; + + ArrayByteString(byte[] bytes) { + this.bytes = Objects.requireNonNull(bytes); + } + + private static void checkSubstringBounds(int start, int end, int len) { + if (start > end) { + throw new IndexOutOfBoundsException("start > end (" + start + " > " + end + ")"); + } else if (start < 0) { + throw new IndexOutOfBoundsException("start < 0 (" + start + " < 0)"); + } else if (end < 0) { + throw new IndexOutOfBoundsException("end < 0 (" + end + " < 0)"); + } else if (end > len) { + throw new IndexOutOfBoundsException("end > length (" + start + " > " + len + ")"); + } + } + + @Override + protected boolean equalsByteString(ByteString that) { + if (this.length() != that.length()) { + return false; + } + + int len = this.length(); + for (int i = 0; i < len; i++) { + if (this.byteAt(i) != that.byteAt(i)) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int hc = hashCode; + if (hc == 0) { + if (bytes.length > 0) { + for (byte b : bytes) { + hc = (hc * 31) + (b & 0xff); + } + hashCode = hc; + } + } + + return hc; + } + + @Override + int maybeHashCode() { + return hashCode; + } + + @Override + public String toString() { + return decode(); + } + + @Override + public String toRawString() { + char[] chars = new char[bytes.length]; + for (int i = 0; i < chars.length; i++) { + chars[i] = (char) (bytes[i] & 0xff); + } + return String.valueOf(chars); + } + + @Override + public int length() { + return bytes.length; + } + + @Override + int maybeLength() { + return bytes.length; + } + + @Override + public boolean isEmpty() { + return bytes.length == 0; + } + + @Override + public byte byteAt(int index) { + return bytes[index]; + } + + @Override + public ByteIterator byteIterator() { + return new ArrayByteIterator(bytes); + } + + @Override + public InputStream asInputStream() { + // no need to go via the iterator + return new ByteArrayInputStream(bytes); + } + + @Override + public ByteString substring(int start, int end) { + checkSubstringBounds(start, end, bytes.length); + return new ArrayByteString(Arrays.copyOfRange(bytes, start, end)); + } + + @Override + public byte[] getBytes() { + return Arrays.copyOf(bytes, bytes.length); + } + + @Override + public void putTo(ByteBuffer buffer) { + buffer.put(bytes); + } + + @Override + public void writeTo(OutputStream stream) throws IOException { + // must make a defensive copy to avoid leaking the contents + stream.write(getBytes()); + } + + @Override + public boolean startsWith(byte b) { + return bytes.length > 0 && bytes[0] == b; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/ByteString.java b/luna/src/main/java/org/classdump/luna/ByteString.java new file mode 100644 index 00000000..5ed6f7a9 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/ByteString.java @@ -0,0 +1,481 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Objects; +import org.classdump.luna.util.ByteIterator; + +/** + * Byte string, an immutable sequence of bytes. + * + *

The purpose of this class is to serve as a bridge between Java strings (with their + * characters corresponding to 16-bit code units in the Basic Multilingual Plane (BMP)) + * and Lua strings (raw 8-bit sequences).

+ * + *

Byte strings come in two flavours:

+ * + * + *

The {@code ByteString} class provides the functionality for treating both cases + * as sequences of bytes when they take part in Lua operations, and as Java strings when + * used by an outer Java application. However, these perspectives are as lazy + * as possible, in order to avoid doing unnecessary work.

+ * + *

This class provides a natural lexicographical ordering that is consistent with equals.

+ */ +public abstract class ByteString implements Comparable { + + ByteString() { + // no-op: package-private to restrict access + } + + /** + * Returns a new byte string corresponding to the bytes in {@code s} as encoded + * by the specified {@code charset}. + * + * @param s the string to take the byte view of, must not be {@code null} + * @param charset the charset to use for decoding {@code s} into bytes, must not be {@code null} + * @return a byte string perspective of {@code s} using {@code charset} + * @throws NullPointerException if {@code s} or {@code charset} is {@code null} + * @throws IllegalArgumentException if {@code charset} does not provide encoding capability (see + * {@link Charset#canEncode()}) + */ + public static ByteString of(String s, Charset charset) { + return new StringByteString(s, charset); + } + + /** + * Returns a new byte string corresponding to the bytes in {@code s} as encoded + * by the default charset ({@link Charset#defaultCharset()}). + * + * @param s the string to take the perspective of, must not be {@code null} + * @return a byte string perspective of {@code s} + * @throws NullPointerException if {@code s} is {@code null} + */ + public static ByteString of(String s) { + return of(s, Charset.defaultCharset()); + } + + /** + * Returns a new byte string corresponding to bytes in {@code s} by taking the + * least significant byte of each character. + * + * @param s the string to get bytes from, must not be {@code null} + * @return a byte string based on {@code s} by taking the least significant byte of each char + * @throws NullPointerException if {@code s} is {@code null} + */ + public static ByteString fromRaw(String s) { + byte[] bytes = new byte[s.length()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) ((int) s.charAt(i) & 0xff); + } + return wrap(bytes); + } + + /** + * Returns a byte string corresponding to the bytes in {@code s} as encoded by the default + * charset in a form suitable for use as a string constant. + * + *

This method differs from {@link #of(String)} in that it may force the computation + * of lazily-evaluated properties of the resulting string at instantiation time and + * cache them for use at runtime.

+ * + * @param s the string to get bytes from, must not be {@code null} + * @return a byte string based on a byte perspective of {@code s} + */ + public static ByteString constOf(String s) { + return of(s); + } + + static ByteString wrap(byte[] bytes) { + return new ArrayByteString(bytes); + } + + /** + * Returns a byte string containing a copy of the byte array {@code bytes}. + * + * @param bytes the byte array to use as the byte string, must not be {@code null} + * @return a byte string containing a copy of {@code bytes} + * @throws NullPointerException if {@code bytes} is {@code null} + */ + public static ByteString copyOf(byte[] bytes) { + return copyOf(bytes, 0, bytes.length); + } + + /** + * Returns a byte string containing a copy of a slice of the byte array {@code bytes} + * starting at the offset {@code offset} and consisting of {@code length} bytes. + * + * @param bytes the byte array to use as the byte string, must not be {@code null} + * @param offset offset in {@code bytes} to start reading from + * @param length the number of bytes to copy from {@code bytes} + * @return a byte string containing a copy of {@code bytes} + * @throws NullPointerException if {@code bytes} is {@code null} + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if + * {@code (offset + length)} is greater than {@code bytes.length} + */ + public static ByteString copyOf(byte[] bytes, int offset, int length) { + if (offset < 0 || length < 0 || (offset + length) > bytes.length) { + throw new IndexOutOfBoundsException("offset=" + offset + ", length=" + length); + } + + return wrap(Arrays.copyOfRange(bytes, offset, length)); + } + + /** + * Returns an empty byte string. + * + * @return an empty byte string + */ + public static ByteString empty() { + return ArrayByteString.EMPTY_INSTANCE; + } + + /** + * Returns {@code true} if {@code o} is a byte string containing the same bytes as + * this byte string. + * + *

Note: this method uses the strict interpretation of byte strings as byte + * sequences. It is therefore not necessarily true that for two byte strings {@code a} + * and {@code b}, the result of their comparison is the same as the result of comparing + * their images provided by {@link #toString()}:

+ *
+   *     boolean byteEq = a.equals(b);
+   *     boolean stringEq = a.toString().equals(b.toString());
+   *
+   *     // may fail!
+   *     assert (byteEq == stringEq);
+   * 
+ * + * @param o object to evaluate for equality with, may be {@code null} + * @return {@code true} iff {@code o} is a byte string with equal contents to {@code this} + */ + @Override + public final boolean equals(Object o) { + return (this == o) || ((o instanceof ByteString) && this.equalsByteString((ByteString) o)); + } + + /** + * Returns the hash code of this byte string. The hash code is computed using the same + * function as used by {@link String#hashCode()}, interpreting the byte string's bytes + * as unsigned integers. + * + * @return the hash code of this byte string + */ + @Override + public abstract int hashCode(); + + /** + * Returns an integer i that corresponds to the hash code of this byte string + * if i is non-zero. When i is zero, it may or may not be the hash code + * of this string. + * + * @return the hash code of this byte string if non-zero + */ + abstract int maybeHashCode(); + + abstract boolean equalsByteString(ByteString that); + + /** + * Returns a new byte array containing the bytes of this byte string. + * + * @return a new byte array + */ + public abstract byte[] getBytes(); + + /** + * Returns the byte at position {@code index}. + * + * @param index the position in the string + * @return the byte at position {@code index} + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()} + */ + public abstract byte byteAt(int index); + + /** + * Returns an iterator over the bytes in this byte string. + * + * @return an iterator over the bytes in this byte string + */ + public abstract ByteIterator byteIterator(); + + /** + * Returns an input stream that reads the contents of this string. + * + * @return an input stream that reads the contents of this string + */ + public InputStream asInputStream() { + return new ByteStringInputStream(byteIterator()); + } + + /** + * Returns the length of this byte string, i.e., the number of bytes it contains. + * + * @return the length of this byte string + */ + public abstract int length(); + + /** + * Returns an integer i that is equal to the length of this byte string if + * i is non-negative. When i is negative, the length of this byte string + * is not yet known. + * + * @return the length of this byte string if non-negative + */ + abstract int maybeLength(); + + /** + * Returns {@code true} iff this byte string is empty, i.e., if the number of bytes it + * contains is 0. + * + * @return {@code true} iff this byte string is empty + */ + public abstract boolean isEmpty(); + + /** + * Returns a substring of this byte string starting at position {@code start} (inclusive), + * ending at position {@code end} (exclusive). + * + *

The indices refer to the byte position in the byte string.

+ * + * @param start the first index to include in the new substring (inclusive) + * @param end the smallest index immediately following the new substring in this byte string + * @return a substring of this byte string ranging from {@code start} (inclusive) to {@code end} + * (exclusive) + * @throws IndexOutOfBoundsException if {@code start < 0}, {@code end > length()} or {@code start + * > end} + */ + public abstract ByteString substring(int start, int end); + + /** + * Puts the contents of this byte string to the specified {@code buffer}. + * + * @param buffer the buffer to use, must not be {@code null} + * @throws NullPointerException if {@code buffer} is {@code null} + */ + public abstract void putTo(ByteBuffer buffer); + + /** + * Writes the contents of this byte string to the specified {@code stream}. + * + * @param stream the stream to use, must not be {@code null} + * @throws IOException when I/O error happens during the write + * @throws NullPointerException if {@code stream} is {@code null} + */ + public abstract void writeTo(OutputStream stream) throws IOException; + + /** + * Returns the interpretation of this byte string as a Java string. + * + * @return the string represented by this byte string + */ + @Override + public abstract String toString(); + + /** + * Returns a string representation of this byte string that uses the specified + * charset {@code charset} to decode characters from bytes. + * + * @param charset the charset to use, must not be {@code null} + * @return this byte string decoded into a string using {@code charset} + * @throws NullPointerException if {@code charset} is {@code null} + */ + public String decode(Charset charset) { + if (isEmpty()) { + return ""; + } + + ByteBuffer byteBuffer = ByteBuffer.allocate(length()); + putTo(byteBuffer); + byteBuffer.flip(); + return charset.decode(byteBuffer).toString(); + } + + /** + * Returns a string represented by this byte string decoded using the default charset + * of the virtual machine. + * + *

This is effectively equivalent to {@link #decode(Charset)} + * called with {@link Charset#defaultCharset()}.

+ * + * @return a string decoded from this byte string using the platform's default charset + */ + public String decode() { + return decode(Charset.defaultCharset()); + } + + /** + * Returns a string in which all characters are directly mapped to the bytes in this + * byte string by treating them as unsigned integers. + * + *

This method is the complement of {@link #fromRaw(String)}.

+ * + * @return a raw string based on this byte string + */ + public abstract String toRawString(); + + /** + * Compares this byte string lexicographically with {@code that}. Returns a negative + * integer, zero, or a positive integer if {@code this} is lesser than, equal to or greater + * than {@code that} in this ordering. + * + *

For the purposes of this ordering, bytes are interpreted as unsigned + * integers.

+ * + *

Note: this method uses the strict interpretation of byte strings as byte + * sequences. It is therefore not necessarily true that for two byte strings {@code a} + * and {@code b}, the result of their comparison is the same as the result of comparing + * their images provided by {@link #toString()}:

+ *
+   *     int byteCmp = a.compareTo(b);
+   *     int stringCmp = a.toString().compareTo(b.toString());
+   *
+   *     // may fail!
+   *     assert(Integer.signum(byteCmp) == Integer.signum(stringCmp));
+   * 
+ * + *

This is done in order to ensure that the natural ordering provided by this + * {@code compareTo()} is consistent with equals.

+ * + * @param that byte string to compare to, must not be {@code null} + * @return a negative, zero, or positive integer if {@code this} is lexicographically lesser than, + * equal to or greater than {@code that} + * @throws NullPointerException if {@code that} is {@code null} + */ + @Override + public int compareTo(ByteString that) { + Objects.requireNonNull(that); + + ByteIterator thisIterator = this.byteIterator(); + ByteIterator thatIterator = that.byteIterator(); + + while (thisIterator.hasNext() && thatIterator.hasNext()) { + int thisByte = thisIterator.nextByte() & 0xff; + int thatByte = thatIterator.nextByte() & 0xff; + int diff = thisByte - thatByte; + if (diff != 0) { + return diff; + } + } + + return thisIterator.hasNext() + ? 1 // !thatIterator.hasNext() => that is shorter + : thatIterator.hasNext() + ? -1 // this is shorter + : 0; // equal length + } + + /** + * Returns a byte string formed by a concatenating this byte string with the byte string + * {@code other}. + * + *

Note: this method uses the non-strict interpretation and therefore + * may (but might not necessarily) preserve unmappable and malformed characters + * occurring in the two strings.

+ * + * @param other the byte string to concatenate this byte string with, must not be {@code null} + * @return this byte string concatenated with {@code other} + * @throws NullPointerException if {@code other} is {@code null} + */ + public ByteString concat(ByteString other) { + if (other.isEmpty()) { + return this; + } else if (this.isEmpty()) { + return other; + } + + byte[] thisBytes = this.getBytes(); + byte[] otherBytes = other.getBytes(); + + byte[] result = new byte[thisBytes.length + otherBytes.length]; + System.arraycopy(thisBytes, 0, result, 0, thisBytes.length); + System.arraycopy(otherBytes, 0, result, thisBytes.length, otherBytes.length); + return ByteString.wrap(result); + } + + /** + * Returns a byte string formed by concatenating this byte string with the string + * {@code other}. + * + *

This is a convenience method equivalent to

+ *
+   *     concat(ByteString.of(other))
+   * 
+ * + * @param other the string to concatenate with, must not be {@code null} + * @return this byte string concatenated with {@code other} + * @throws NullPointerException if {@code other} is {@code null} + */ + public ByteString concat(String other) { + return this.concat(ByteString.of(other)); + } + + // TODO: add startsWith(ByteString) + + /** + * Returns {@code true} if the first byte of this byte string is {@code b}. + * + * @param b the byte to compare the first byte of this byte string to + * @return {@code true} if this byte string starts with {@code b} + */ + public abstract boolean startsWith(byte b); + + // TODO: add contains(ByteString) + + /** + * Returns {@code true} if the byte string contains the byte {@code b}. + * + * @param b the byte to search for in the byte string + * @return {@code true} if this byte string contains {@code b} + */ + public boolean contains(byte b) { + ByteIterator it = byteIterator(); + while (it.hasNext()) { + if (b == it.nextByte()) { + return true; + } + } + return false; + } + + /** + * Replaces all occurrences of the byte string {@code target} in this byte string + * with the replacement {@code replacement}. + * + * @param target the substring to replace, must not be {@code null} + * @param replacement the replacement, must not be {@code null} + * @return this byte string with all occurrences of {@code target} replaced by {@code replacement} + * @throws NullPointerException if {@code target} or {@code replacement} is {@code null} + */ + public ByteString replace(ByteString target, ByteString replacement) { + // FIXME: don't go via raw strings + return ByteString.fromRaw(this.toRawString().replace( + target.toRawString(), + replacement.toRawString())); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/ByteStringBuilder.java b/luna/src/main/java/org/classdump/luna/ByteStringBuilder.java new file mode 100644 index 00000000..48638d32 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/ByteStringBuilder.java @@ -0,0 +1,279 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.nio.charset.Charset; +import java.util.Arrays; +import org.classdump.luna.util.Check; + +/** + * A builder for byte strings, similar in interface to {@link StringBuilder}. + * + *

This class is not thread-safe.

+ */ +public class ByteStringBuilder { + + private static final int DEFAULT_CAPACITY = 32; + // don't go any smaller than this + private static final int MIN_CAPACITY = DEFAULT_CAPACITY; + private byte[] buffer; + private int length; + + private ByteStringBuilder(byte[] buffer, int length) { + this.buffer = buffer; + this.length = length; + } + + /** + * Constructs a new empty {@code ByteStringBuilder} that can hold at least {@code capacity} + * bytes. + * + * @param capacity the initial required capacity, must not be negative + * @throws IllegalArgumentException if {@code capacity} is negative + */ + public ByteStringBuilder(int capacity) { + this(new byte[idealCapacity(Check.nonNegative(capacity))], 0); + } + + /** + * Constructs a new empty {@code ByteStringBuilder}. + */ + public ByteStringBuilder() { + this(new byte[DEFAULT_CAPACITY], 0); + } + + // returns the smallest positive integer i >= x such that i is a power of 2 + private static int binaryCeil(int x) { + if (x < 0) { + return 0; + } + // from "Hacker's Delight" by Henry S. Warren, Jr., section 3-2 + x -= 1; + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return x + 1; + } + + private static int idealCapacity(int desired) { + int ceil = binaryCeil(desired); + return Math.max(ceil, MIN_CAPACITY); + } + + private static byte[] resize(byte[] buf, int newSize) { + assert (newSize >= buf.length); + byte[] newBuf = new byte[newSize]; + System.arraycopy(buf, 0, newBuf, 0, buf.length); + return newBuf; + } + + private void ensureCapacity(int cap) { + if (cap > buffer.length) { + buffer = resize(buffer, idealCapacity(cap)); + } + } + + /** + * Returns the current capacity of the builder. + * + * @return the current capacity of the builder + */ + public int capacity() { + return buffer.length; + } + + /** + * Returns the number of bytes in this builder. + * + * @return the number of bytes in this builder + */ + public int length() { + return length; + } + + /** + * Sets the number of bytes in this builder to {@code newLength}. + * + *

When {@code newLength} is lesser than the current length {@code len}, + * drops the last {@code (len - newLength)} bytes from the constructed sequence. + * If {@code newLength} is greater than {@code len}, appends {@code newLength - len} + * zero bytes.

+ * + *

No memory is freed when reducing the length of the sequence.

+ * + * @param newLength the new length, must not be negative + * @throws IndexOutOfBoundsException if {@code newLength} is negative + */ + public void setLength(int newLength) { + if (newLength < 0) { + throw new IndexOutOfBoundsException(Integer.toString(newLength)); + } + + if (newLength < length) { + length = newLength; + } else if (newLength > length) { + ensureCapacity(newLength); + Arrays.fill(buffer, length, newLength, (byte) 0); + } + } + + /** + * Attempts to reduce the memory consumption of this builder by reducing its capacity + * to a smaller, yet still sufficient value. + */ + public void trimToSize() { + int cap = idealCapacity(length); + if (cap < capacity()) { + buffer = resize(buffer, cap); + } + } + + /** + * Sets the byte at position {@code index} to {@code value}. + * + * @param index the index of the byte to set + * @param value the new value of the byte at position {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or greater than or equal to the + * current buffer length + */ + public void setByteAt(int index, byte value) { + if (index < 0 || index > length) { + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + buffer[index] = value; + } + + /** + * Appends the byte {@code b}. + * + * @param b the byte to append + * @return this builder + */ + public ByteStringBuilder append(byte b) { + ensureCapacity(length + 1); + buffer[length] = b; + length += 1; + return this; + } + + /** + * Appends the contents of the byte array {@code array}. {@code off} + * is the offset in {@code array} to start the write from, and {@code len} is the + * number of bytes that should be written. + * + *

Throws an {@code IndexOutOfBoundsException} if {@code off} or {@code len} + * is negative, or if {@code (off + len)} is greater than {@code array.length}.

+ * + * @param array the byte array, must not be {@code null} + * @param off offset in {@code array} to start from + * @param len number of bytes to write + * @return this builder + * @throws NullPointerException if {@code array} is {@code null} + * @throws IndexOutOfBoundsException if {@code off} or {@code len} is negative, or if {@code (off + * + len)} is greater than {@code array.length} + */ + public ByteStringBuilder append(byte[] array, int off, int len) { + if (off < 0 || len < 0 || (off + len) > array.length) { + throw new IndexOutOfBoundsException("off=" + off + ", len=" + len); + } + + if (len > 0) { + ensureCapacity(length + len); + System.arraycopy(array, off, buffer, length, len); + length += len; + } + + return this; + } + + /** + * Appends the contents of the byte array {@code array}. + * + * @param array the byte array to append, must not be {@code null} + * @return this builder + * @throws NullPointerException if {@code array} is {@code null} + */ + public ByteStringBuilder append(byte[] array) { + return append(array, 0, array.length); + } + + /** + * Appends the contents of the byte string {@code string}. + * + * @param string the byte string to append, must not be {@code null} + * @return this builder + * @throws NullPointerException if {@code string} is {@code null} + */ + public ByteStringBuilder append(ByteString string) { + return append(string.getBytes()); + } + + /** + * Appends a char sequence {@code charSequence} interpreted as a sequence + * of bytes using the specified {@code Charset}. + * + * @param charSequence the char sequence to append, must not be {@code null} + * @param charset the charset to use for encoding, must not be {@code null} + * @return this builder + * @throws NullPointerException if {@code string} is {@code null} + * @throws IllegalArgumentException if {@code charset} cannot does not provide encoding capability + * (see {@link Charset#canEncode()}) + */ + public ByteStringBuilder append(CharSequence charSequence, Charset charset) { + if (!charset.canEncode()) { + throw new IllegalArgumentException("Charset cannot encode: " + charset.name()); + } + + // FIXME: inefficient, could be done more directly + append(ByteString.of(charSequence.toString(), charset)); + return this; + } + + /** + * Appends the char sequence {@code charSequence} interpreted as a sequence + * of bytes using the virtual machine's default charset (see {@link Charset#defaultCharset()}). + * + * @param charSequence the char sequence to append, must not be {@code null} + * @return this builder + * @throws NullPointerException if {@code charSequence} is {@code null} + */ + public ByteStringBuilder append(CharSequence charSequence) { + return append(charSequence, Charset.defaultCharset()); + } + + /** + * Returns a byte string consisting of the bytes in this builder. + * + * @return a byte string with this builder's contents + */ + public ByteString toByteString() { + return ByteString.copyOf(buffer, 0, length); + } + + /** + * Returns the interpretation of this builder's bytes as a {@code java.lang.String}. + * + * @return a {@code java.lang.String} interpretation of the bytes in this builder + */ + @Override + public String toString() { + return toByteString().toString(); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/ByteStringInputStream.java b/luna/src/main/java/org/classdump/luna/ByteStringInputStream.java new file mode 100644 index 00000000..9a2d237e --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/ByteStringInputStream.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.io.IOException; +import java.io.InputStream; +import org.classdump.luna.util.ByteIterator; + +class ByteStringInputStream extends InputStream { + + private final ByteIterator iterator; + + public ByteStringInputStream(ByteIterator iterator) { + this.iterator = iterator; + } + + @Override + public int read() throws IOException { + return !iterator.hasNext() ? -1 : iterator.nextByte() & 0xff; + } + + // TODO implement more efficient version + @Override + public int read(byte[] b, int off, int len) throws IOException { + return super.read(b, off, len); + } +} diff --git a/luna/src/main/java/org/classdump/luna/ConversionException.java b/luna/src/main/java/org/classdump/luna/ConversionException.java new file mode 100644 index 00000000..5a042cfa --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/ConversionException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * An exception thrown to indicate an unsuccessful value conversion. + */ +public class ConversionException extends LuaRuntimeException { + + /** + * Constructs a new instance with the given error message. + * + * @param message error message + */ + public ConversionException(String message) { + super(message); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/Conversions.java b/luna/src/main/java/org/classdump/luna/Conversions.java new file mode 100644 index 00000000..71980d3a --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Conversions.java @@ -0,0 +1,590 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.util.Arrays; + +/** + * Static methods implementing Lua value conversions. + */ +public final class Conversions { + + private static final ByteString NULL_ERROR_MESSAGE = ByteString.constOf("(null)"); + + private Conversions() { + // not to be instantiated + } + + /** + * Returns the numerical value of the string {@code s}, or {@code null} if + * {@code s} does not have a numerical value. + * + *

If {@code s} is a valid Lua integer literal with optional sign, the numerical + * value is the corresponding integer; if {@code s} is a valid Lua float literal with + * optional sign, the numerical value is the corresponding float. Otherwise, the {@code s} + * does not have a numerical value.

+ * + *

Leading and trailing whitespace in {@code s} is ignored by this method.

+ * + *

Numbers returned by this method are in the canonical representation.

+ * + * @param s string to convert to numerical value, may be {@code null} + * @return a number representing the numerical value of {@code s} (in the canonical + * representation), or {@code null} if {@code s} does not have a numerical value + */ + public static Number numericalValueOf(ByteString s) { + String trimmed = s.toString().trim(); + try { + return Long.valueOf(LuaFormat.parseInteger(trimmed)); + } catch (NumberFormatException ei) { + try { + return Double.valueOf(LuaFormat.parseFloat(trimmed)); + } catch (NumberFormatException ef) { + return null; + } + } + } + + /** + * Returns the numerical value of the object {@code o}, or {@code null} if {@code o} + * does not have a numerical value. + * + *

If {@code o} is already a number, returns {@code o} cast to number. If {@code o} + * is a string, returns its numerical value (see {@link #numericalValueOf(ByteString)}). + * Otherwise, returns {@code null}.

+ * + *

This method differs from {@link #arithmeticValueOf(Object)} in that it + * preserves the numerical value representation of coerced strings. For use in arithmetic + * operations following Lua's argument conversion rules, use that method instead.

+ * + *

Numbers returned by this method are not necessarily in the canonical representation.

+ * + * @param o object to convert to numerical value, may be {@code null} + * @return number representing the numerical value of {@code o} (not necessarily in the canonical + * representation), of {@code null} if {@code o} does not have a numerical value + * @see #arithmeticValueOf(Object) + */ + public static Number numericalValueOf(Object o) { + if (o instanceof Number) { + return (Number) o; + } else if (o instanceof ByteString) { + return numericalValueOf((ByteString) o); + } else if (o instanceof String) { + return numericalValueOf(ByteString.of((String) o)); + } else { + return null; + } + } + + /** + * Returns the numerical value of {@code o}, throwing a {@link ConversionException} + * if {@code o} does not have a numerical value. + * + *

The conversion rules are those of {@link #numericalValueOf(Object)}; the only difference + * is that this method throws an exception rather than returning {@code null} to signal + * errors.

+ * + *

Numbers returned by this method are not necessarily in the canonical representation.

+ * + * @param o object to convert to numerical value, may be {@code null} + * @param name value name for error reporting, may be {@code null} + * @return number representing the numerical value of {@code o} (not necessarily in the canonical + * representation), guaranteed to be non-{@code null} + * @throws ConversionException if {@code o} is not a number or string convertible to number. + * @see #numericalValueOf(Object) + */ + public static Number toNumericalValue(Object o, String name) { + Number n = numericalValueOf(o); + if (n == null) { + throw new ConversionException((name != null ? name : "value") + " must be a number"); + } else { + return n; + } + } + + /** + * Returns the number {@code n} in its canonical representation, + * i.e. a {@link java.lang.Long} if {@code n} is a Lua integer, or a {@link java.lang.Double} + * if {@code n} is a Lua float. + * + * @param n number to convert to canonical representation, must not be {@code null} + * @return an instance of {@code Long} if {@code n} is an integer, or an instance of {@code + * Double} if {@code n} is a float + * @throws NullPointerException if {@code n} is {@code null} + */ + public static Number toCanonicalNumber(Number n) { + if (n instanceof Long || n instanceof Double) { + // already in canonical representation + return n; + } else if (n instanceof Float) { + // re-box + return Double.valueOf(n.doubleValue()); + } else { + // re-box + return Long.valueOf(n.longValue()); + } + } + + /** + * Returns the value {@code o} to its canonical representation. + * + *

For numbers, this method is equivalent to {@link #toCanonicalNumber(Number)}. + * If {@code o} is a {@link String java.lang.String}, it is wrapped into a byte + * string by {@link ByteString#of(String)}. Otherwise, {@code o} is in canonical + * representation.

+ * + *

This method is intended for use at the Java → Lua boundary, and whenever + * it is not certain that {@code o} is in a canonical representation when a canonical + * representation is required.

+ * + * @param o value to convert to canonical representation, may be {@code null} + * @return {@code o} converted to canonical representation + */ + public static Object canonicalRepresentationOf(Object o) { + if (o instanceof Number) { + return toCanonicalNumber((Number) o); + } else if (o instanceof String) { + return ByteString.of((String) o); + } else { + return o; + } + } + + /** + * Returns the value {@code o} in its Java representation. + * + *

If {@code o} is a {@link ByteString}, returns {@code o} as a {@code java.lang.String} + * (using {@link ByteString#toString()}. Otherwise, returns {@code o}.

+ * + *

This method is intended for use at the Lua → Java boundary for interoperating + * with Java code unaware of (or not concerned with) the interpretation of Lua + * strings as sequences of bytes.

+ * + * @param o value to convert to Java representation, may be {@code null} + * @return {@code o} converted to a {@code java.lang.String} if {@code o} is a byte string, {@code + * o} otherwise + */ + public static Object javaRepresentationOf(Object o) { + if (o instanceof ByteString) { + return o.toString(); + } else { + return o; + } + } + + /** + * Modifies the contents of the array {@code values} by converting all values to + * their canonical representations. + * + * @param values values to convert to their canonical representations, must not be {@code null} + * @throws NullPointerException if {@code values} is {@code null} + * @see #canonicalRepresentationOf(Object) + */ + public static void toCanonicalValues(Object[] values) { + for (int i = 0; i < values.length; i++) { + Object v = values[i]; + values[i] = canonicalRepresentationOf(v); + } + } + + /** + * Modifies the contents of the array {@code values} by converting all values to + * their Java representations. + * + *

This method is intended for use at the Lua → Java boundary for interoperating + * with Java code unaware of (or not concerned with) the interpretation of Lua + * strings as sequences of bytes.

+ * + * @param values values to convert to their Java representations, must not be {@code null} + * @throws NullPointerException if {@code values} is {@code null} + * @see #javaRepresentationOf(Object) + */ + public static void toJavaValues(Object[] values) { + for (int i = 0; i < values.length; i++) { + Object v = values[i]; + values[i] = javaRepresentationOf(v); + } + } + + /** + * Returns a copy of the array {@code values} with all values converted to their + * canonical representation. + * + * @param values values to convert to their canonical representation, must not be {@code null} + * @return a copy of {@code values} with all elements converted to canonical representation + * @see #canonicalRepresentationOf(Object) + */ + public static Object[] copyAsCanonicalValues(Object[] values) { + values = Arrays.copyOf(values, values.length); + toCanonicalValues(values); + return values; + } + + /** + * Returns a copy of the array {@code values} with all values converted to their + * Java representation. + * + *

This method is intended for use at the Lua → Java boundary for interoperating + * with Java code unaware of (or not concerned with) the interpretation of Lua + * strings as sequences of bytes.

+ * + * @param values values to convert to their Java representation, must not be {@code null} + * @return a copy of {@code values} with all elements converted to their Java representation + * @see #javaRepresentationOf(Object) + */ + public static Object[] copyAsJavaValues(Object[] values) { + values = Arrays.copyOf(values, values.length); + toJavaValues(values); + return values; + } + + /** + * Normalises the number {@code n} so that it may be used as a key in a Lua + * table. + * + *

If {@code n} has an integer value i, returns the canonical representation + * of i; otherwise, returns the canonical representation of {@code n} + * (see {@link #toCanonicalNumber(Number)}).

+ * + * @param n number to normalise, must not be {@code null} + * @return an canonical integer if {@code n} has an integer value, the canonical representation of + * {@code n} otherwise + * @throws NullPointerException if {@code n} is {@code null} + */ + public static Number normaliseKey(Number n) { + Long i = integerValueOf(n); + return i != null ? i : toCanonicalNumber(n); + } + + /** + * Normalises the argument {@code o} so that it may be used safely as a key + * in a Lua table. + * + *

If {@code o} is a number, returns the number normalised (see {@link #normaliseKey(Number)}. + * If {@code o} is a {@code java.lang.String}, returns {@code o} as a byte string using + * {@link ByteString#of(String)}. Otherwise, returns {@code o}.

+ * + * @param o object to normalise, may be {@code null} + * @return normalised number if {@code o} is a number, {@code o} as byte string if {@code o} is a + * {@code java.lang.String}, {@code o} otherwise + */ + public static Object normaliseKey(Object o) { + if (o instanceof Number) { + return normaliseKey((Number) o); + } else if (o instanceof String) { + return ByteString.of((String) o); + } else { + return o; + } + } + + /** + * Returns the arithmetic value of the object {@code o}, or {@code null} if {@code o} + * does not have an arithmetic value. + * + *

If {@code o} is a number, then that number is its arithmetic value. If {@code o} + * is a string that has a numerical value (see {@link #numericalValueOf(ByteString)}), + * its arithmetic value is the numerical value converted to a float. Otherwise, + * {@code o} does not have an arithmetic value.

+ * + *

Note that this method differs from {@link #numericalValueOf(Object)} in that it + * coerces strings convertible to numbers into into floats rather than preserving + * their numerical value representation, and also note that this conversion happens + * after the numerical value has been determined. Most significantly,

+ * + *
+   *     Conversions.arithmeticValueOf("-0")
+   * 
+ * + *

yields {@code 0.0} rather than {@code 0} (as would be the case with + * {@code numericalValueOf("-0")}), or {@code -0.0} (it would in the case if the string + * was parsed directly as a float).

+ * + *

Numbers returned by this method are not necessarily in the canonical representation.

+ * + * @param o object to convert to arithmetic value, may be {@code null} + * @return number representing the arithmetic value of {@code o} (not necessarily in the canonical + * representation), or {@code null} if {@code o} does not have an arithmetic value + * @see #numericalValueOf(Object) + */ + public static Number arithmeticValueOf(Object o) { + if (o instanceof Number) { + return (Number) o; + } else { + Number n = numericalValueOf(o); + return n != null ? floatValueOf(n) : null; + } + } + + /** + * Returns the integer value of the number {@code n}, or {@code null} if {@code n} + * does not have an integer value. + * + *

{@code n} has an integer value if and only if the number it denotes can be represented + * as a signed 64-bit integer. That integer is then the integer value of {@code n}. + * In other words, if {@code n} is a float, it has an integer value if and only if + * it can be converted to a {@code long} without loss of precision.

+ * + * @param n number to convert to integer, must not be {@code null} + * @return a {@code Long} representing the integer value of {@code n}, or {@code null} if {@code + * n} does not have an integer value + * @throws NullPointerException if {@code n} is {@code null} + * @see LuaMathOperators#hasExactIntegerRepresentation(double) + */ + public static Long integerValueOf(Number n) { + if (n instanceof Double || n instanceof Float) { + double d = n.doubleValue(); + return LuaMathOperators.hasExactIntegerRepresentation(d) ? Long.valueOf((long) d) : null; + } else if (n instanceof Long) { + return (Long) n; + } else { + return Long.valueOf(n.longValue()); + } + } + + /** + * Returns the integer value of the number {@code n}, throwing + * a {@link NoIntegerRepresentationException} if {@code n} does not have an integer value. + * + *

This is a variant of {@link #integerValueOf(Number)}; the difference is that + * this method throws an exception rather than returning {@code null} to signal that + * {@code n} does not have an integer value, and that this method returns the unboxed + * integer value of {@code n} (as a {@code long}).

+ * + * @param n object to be converted to integer, must not be {@code null} + * @return integer value of {@code n} + * @throws NoIntegerRepresentationException if {@code n} does not have an integer value + * @throws NullPointerException if {@code n} is {@code null} + */ + public static long toIntegerValue(Number n) { + Long l = integerValueOf(n); + if (l != null) { + return l.longValue(); + } else { + throw new NoIntegerRepresentationException(); + } + } + + /** + * Returns the integer value of the object {@code o}, or {@code null} if {@code o} + * does not have an integer value. + * + *

The integer value of {@code o} is the integer value of its numerical value + * (see {@link #numericalValueOf(Object)}), when it exists.

+ * + * @param o object to be converted to integer, may be {@code null} + * @return a {@code Long} representing the integer value of {@code o}, or {@code null} if {@code + * o} does not have a integer value + * @see #integerValueOf(Number) + */ + public static Long integerValueOf(Object o) { + Number n = numericalValueOf(o); + return n != null ? integerValueOf(n) : null; + } + + /** + * Returns the integer value of the object {@code o}, throwing + * a {@link NoIntegerRepresentationException} if {@code o} does not have an integer value. + * + *

This is a variant of {@link #integerValueOf(Object)}; the difference is that + * this method throws an exception rather than returning {@code null} to signal that + * {@code o} does not have an integer value, and that this method returns the unboxed + * integer value of {@code o} (as a {@code long}).

+ * + * @param o object to be converted to integer, may be {@code null} + * @return integer value of {@code n} + * @throws NoIntegerRepresentationException if {@code o} does not have an integer value + */ + public static long toIntegerValue(Object o) { + Long l = integerValueOf(o); + if (l != null) { + return l.longValue(); + } else { + throw new NoIntegerRepresentationException(); + } + } + + /** + * Returns the float value of the number {@code n}. + * + *

The float value of {@code n} is its numerical value converted to a Lua float.

+ * + * @param n the number to convert to float, must not be {@code null} + * @return the float value of {@code n}, guaranteed to be non-{@code null} + * @throws NullPointerException if {@code n} is {@code null} + */ + public static Double floatValueOf(Number n) { + return n instanceof Double + ? (Double) n + : Double.valueOf(n.doubleValue()); + } + + /** + * Returns the float value of the object {@code o}, or {@code null} if {@code o} + * does not have a float value. + * + *

The float value of {@code o} is the {@linkplain #floatValueOf(Number) float value} + * of its numerical value (see {@link #numericalValueOf(Object)}), when {@code o} + * has a numerical value.

+ * + * @param o the object to be converted to float, may be {@code null} + * @return a {@code Double} representing the float value of {@code o}, or {@code null} if {@code + * o} does not have a float value + */ + public static Double floatValueOf(Object o) { + Number n = numericalValueOf(o); + return n != null ? floatValueOf(n) : null; + } + + /** + * Returns the boolean value of the object {@code o}. + * + *

The boolean value of {@code o} is {@code false} if and only if {@code o} is nil + * (i.e., {@code null}) or false (i.e., a {@link Boolean} {@code b} such + * that {@code b.booleanValue() == false}). + * + * @param o object to convert to boolean, may be {@code null} + * @return {@code false} if {@code o} is nil or false, {@code true} otherwise + */ + public static boolean booleanValueOf(Object o) { + return !(o == null || (o instanceof Boolean && !((Boolean) o).booleanValue())); + } + + /** + * Returns the string value of the number {@code n}. + * + *

The string value of integers is the result of {@link LuaFormat#toByteString(long)} + * on their numerical value; similarly the string value of floats is the result + * of {@link LuaFormat#toByteString(double)} on their numerical value. + * + * @param n number to be converted to string, must not be {@code null} + * @return string value of {@code n}, guaranteed to be non-{@code null} + * @throws NullPointerException if {@code n} is {@code null} + */ + public static ByteString stringValueOf(Number n) { + if (n instanceof Double || n instanceof Float) { + return LuaFormat.toByteString(n.doubleValue()); + } else { + return LuaFormat.toByteString(n.longValue()); + } + } + + /** + * Returns the string value of the object {@code o}, or {@code null} if {@code o} does + * not have a string value. + * + *

If {@code o} is a string, that is the string value. If {@code o} is a number, + * returns the string value of that number (see {@link #stringValueOf(Number)}). + * Otherwise, {@code o} does not have a string value. + * + * @param o object to be converted to string, may be {@code null} + * @return string value of {@code o}, or {@code null} if {@code o} does not have a string value + */ + public static ByteString stringValueOf(Object o) { + if (o instanceof ByteString) { + return (ByteString) o; + } else if (o instanceof Number) { + return stringValueOf((Number) o); + } else if (o instanceof String) { + return ByteString.of((String) o); + } else { + return null; + } + } + + /** + * Converts the object {@code o} to a human-readable string format. + * + *

The conversion rules are the following: + * + *

+ * + *

Note that this method ignores the object's {@code toString()} method + * and its {@code __tostring} metamethod. + * + * @param o the object to be converted to string, may be {@code null} + * @return human-readable string representation of {@code o} + * @see #stringValueOf(Object) + */ + public static ByteString toHumanReadableString(Object o) { + if (o == null) { + return LuaFormat.NIL; + } else if (o instanceof ByteString) { + return (ByteString) o; + } else if (o instanceof Number) { + return stringValueOf((Number) o); + } else if (o instanceof Boolean) { + return LuaFormat.toByteString(((Boolean) o).booleanValue()); + } else if (o instanceof String) { + return ByteString.of((String) o); + } else { + return ByteString.of(String.format("%s: %#010x", + PlainValueTypeNamer.INSTANCE.typeNameOf(o), + o.hashCode())); + } + } + + /** + * Converts a {@code Throwable} {@code t} to an error object. + * + *

If {@code t} is a {@link LuaRuntimeException}, the result of this operation + * is the result of its {@link LuaRuntimeException#getErrorObject()}. Otherwise, + * the result is {@link Throwable#getMessage()}. + * + * @param t throwable to convert to error object, must not be {@code null} + * @return error object represented by {@code t} + * @throws NullPointerException if {@code t} is {@code null} + */ + public static Object toErrorObject(Throwable t) { + if (t instanceof LuaRuntimeException) { + return ((LuaRuntimeException) t).getErrorObject(); + } else { + return t.getMessage(); + } + } + + /** + * Converts a {@code Throwable} {@code t} to an error message (a byte string). + * + *

This is equivalent to converting the error object retrieved from {@code t} by + * {@link #toErrorObject(Throwable)} to a byte string using + * {@link #stringValueOf(Object)}.

+ * + *

If the error object does not have a string value, returns {@code "(null)"}.

+ * + * @param t throwable to convert to error message, must not be {@code null} + * @return error message of {@code t}, or {@code "(null)"} if {@code t} does not have a string + * error message + * @throws NullPointerException if {@code t} is {@code null} + */ + public static ByteString toErrorMessage(Throwable t) { + ByteString m = Conversions.stringValueOf(toErrorObject(t)); + return m != null ? m : NULL_ERROR_MESSAGE; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/LuaFormat.java b/luna/src/main/java/org/classdump/luna/LuaFormat.java new file mode 100644 index 00000000..a0baaefa --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/LuaFormat.java @@ -0,0 +1,481 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Static methods for parsing and generating lexical strings following the Lua lexical + * conventions. + */ +public final class LuaFormat { + + /** + * The byte string representation of nil. + */ + public static final ByteString NIL = ByteString.constOf("nil"); + /** + * The byte string representation of true. + */ + public static final ByteString TRUE = ByteString.constOf("true"); + /** + * The byte string representation of false. + */ + public static final ByteString FALSE = ByteString.constOf("false"); + /** + * The byte string representation of infinity. + */ + public static final ByteString INF = ByteString.constOf("inf"); + /** + * The byte string representation of NaN. + */ + public static final ByteString NAN = ByteString.constOf("nan"); + /** + * Byte string representation of the Lua {@code nil} type. + */ + public static final ByteString TYPENAME_NIL = NIL; + /** + * Byte string representation of the Lua {@code boolean} type. + */ + public static final ByteString TYPENAME_BOOLEAN = ByteString.constOf("boolean"); + /** + * Byte string representation of the Lua {@code number} type. + */ + public static final ByteString TYPENAME_NUMBER = ByteString.constOf("number"); + /** + * Byte string representation of the Lua {@code string} type. + */ + public static final ByteString TYPENAME_STRING = ByteString.constOf("string"); + /** + * Byte string representation of the Lua {@code table} type. + */ + public static final ByteString TYPENAME_TABLE = ByteString.constOf("table"); + /** + * Byte string representation of the Lua {@code function} type. + */ + public static final ByteString TYPENAME_FUNCTION = ByteString.constOf("function"); + /** + * Byte string representation of the Lua {@code userdata} type. + */ + public static final ByteString TYPENAME_USERDATA = ByteString.constOf("userdata"); + /** + * Byte string representation of the Lua {@code thread} type. + */ + public static final ByteString TYPENAME_THREAD = ByteString.constOf("thread"); + /** + * Byte string representation of the Lua {@code lua object} type. + */ + public static final ByteString TYPENAME_LUAOBJECT = ByteString.constOf("luaobject"); + /** + * The '\a' character. + */ + public static final char CHAR_BELL = 0x07; + /** + * The '\v' character. + */ + public static final char CHAR_VERTICAL_TAB = 0x0b; + private static final ByteString NEG_INF = ByteString.constOf("-" + INF); + private static final Set keywords; + + static { + Set ks = new HashSet<>(); + Collections.addAll(ks, + "and", "break", "do", "else", "elseif", "end", "false", "for", + "function", "goto", "if", "in", "local", "nil", "not", "or", + "repeat", "return", "then", "true", "until", "while"); + keywords = Collections.unmodifiableSet(ks); + } + + private LuaFormat() { + // not to be instantiated + } + + /** + * Returns the Lua format string representation of the boolean value {@code b}. + * + *

Note: this method returns a {@code java.lang.String}. In order to + * obtain a byte string, use {@link #toByteString(boolean)} rather than + * wrapping the result of this method using {@link ByteString#of(String)}.

+ * + * @param b the boolean value + * @return string representation of {@code b} + */ + public static String toString(boolean b) { + return toByteString(b).toString(); + } + + /** + * Returns the Lua format byte string representation of the boolean value {@code b} + * as a byte string. + * + * @param b the boolean value + * @return byte string representation of {@code b} + */ + public static ByteString toByteString(boolean b) { + return b ? TRUE : FALSE; + } + + /** + * Returns the Lua format string representation of the integer value {@code l}. + * + *

Note: this method returns a {@code java.lang.String}. In order to + * obtain a byte string, use {@link #toByteString(long)} rather than + * wrapping the result of this method using {@link ByteString#of(String)}.

+ * + * @param l the integer value + * @return string representation of {@code l} + */ + public static String toString(long l) { + return Long.toString(l); + } + + /** + * Returns the Lua format byte string representation of the integer value {@code l}. + * + * @param l the integer value + * @return byte string representation of {@code l} + */ + public static ByteString toByteString(long l) { + return ByteString.of(toString(l)); + } + + /** + * Returns the Lua format string representation of the float value {@code f}. + * + *

Note: this method returns a {@code java.lang.String}. In order to + * obtain a byte string, use {@link #toByteString(long)} rather than + * wrapping the result of this method using {@link ByteString#of(String)}.

+ * + * @param f the float value + * @return string representation of {@code f} + */ + public static String toString(double f) { + return toByteString(f).toString(); + } + + private static ByteString finiteDoubleToByteString(double f) { + // f assumed not to be NaN or infinite + // TODO: check precision used in Lua + // TODO: don't go via java.lang.String + String s = Double.toString(f).toLowerCase(); + return ByteString.of(s); + } + + /** + * Returns the Lua format byte string representation of the float value {@code f}. + * + * @param f the float value + * @return byte string representation of {@code f} + */ + public static ByteString toByteString(double f) { + if (Double.isNaN(f)) { + return NAN; + } else if (Double.isInfinite(f)) { + return f > 0 ? INF : NEG_INF; + } else { + return finiteDoubleToByteString(f); + } + } + + private static int hexValue(int c) { + if (c >= '0' && c <= '9') { + return c - (int) '0'; + } else if (c >= 'a' && c <= 'f') { + return 10 + c - (int) 'a'; + } else if (c >= 'A' && c <= 'F') { + return 10 + c - (int) 'A'; + } else { + return -1; + } + } + + /** + * Parses the string {@code s} as an integer according to the Lua lexer rules. + * When {@code s} is not an integer numeral, throws a {@link NumberFormatException}. + * + *

This method ignores leading and trailing whitespace in {@code s}.

+ * + * @param s string to be parsed, must not be {@code null} + * @return the integer value represented by {@code s} + * @throws NullPointerException if {@code s} is {@code null} + * @throws NumberFormatException if {@code s} is not a valid Lua format string representing an + * integer value + */ + public static long parseInteger(String s) throws NumberFormatException { + s = s.trim(); + if (s.startsWith("0x") || s.startsWith("0X")) { + long l = 0; + int from = Math.max(2, s.length() - 16); + + for (int idx = 2; idx < from; idx++) { + if (hexValue(s.charAt(idx)) < 0) { + throw new NumberFormatException("Illegal character #" + idx + " in \"" + s + "\""); + } + } + + // only take the last 16 characters of the string for the value + for (int idx = Math.max(2, s.length() - 16); idx < s.length(); idx++) { + int hex = hexValue(s.charAt(idx)); + if (hex < 0) { + throw new NumberFormatException("Illegal character #" + idx + " in \"" + s + "\""); + } + l = l << 4 | hex; + } + + return l; + } else { + return Long.parseLong(s); + } + } + + /** + * Parses the string {@code s} as a float according to the Lua lexer rules. + * When {@code s} is not a float numeral, throws a {@link NumberFormatException}. + * + *

This method ignores leading and trailing whitespace in {@code s}.

+ * + * @param s the string to be parsed, must not be {@code null} + * @return the float value represented by {@code s} + * @throws NullPointerException if {@code s} is {@code null} + * @throws NumberFormatException if {@code s} is not a valid Lua format string representing a + * float value + */ + public static double parseFloat(String s) throws NumberFormatException { + try { + return Double.parseDouble(s); + } catch (NumberFormatException e0) { + // might be missing the trailing exponent for hex floating point constants + try { + return Double.parseDouble(s.trim() + "p0"); + } catch (NumberFormatException e1) { + throw new NumberFormatException("Not a number: " + s); + } + } + } + + /** + * Parses {@code s} as an integer following the Lua lexer rules. When {@code s} is + * an integer numeral, returns its value boxed as a {@link Long}. Otherwise, returns + * {@code null}. + * + *

This is a variant of {@link #parseInteger(String)} that signals invalid input + * by returning {@code null} rather than throwing a {@code NumberFormatException}.

+ * + * @param s the string to be parsed, must not be {@code null} + * @return the (boxed) integer value represented by {@code s} if {@code s} is an integer numeral; + * {@code null} otherwise + * @throws NullPointerException if {@code s} is {@code null} + */ + public static Long tryParseInteger(String s) { + try { + return parseInteger(s); + } catch (NumberFormatException ex) { + return null; + } + } + + /** + * Parses {@code s} as a float following the Lua lexer rules. When {@code s} is + * a float numeral, returns its value boxed as a {@link Double}. Otherwise, returns + * {@code null}. + * + *

This is a variant of {@link #parseFloat(String)} that signals invalid input + * by returning {@code null} rather than throwing a {@code NumberFormatException}.

+ * + * @param s the string to be parsed, must not be {@code null} + * @return the (boxed) float value represented by {@code s} if {@code s} is an float numeral; + * {@code null} otherwise + * @throws NullPointerException if {@code s} is {@code null} + */ + public static Double tryParseFloat(String s) { + try { + return parseFloat(s); + } catch (NumberFormatException ex) { + return null; + } + } + + /** + * Parses {@code s} as a number following the Lua lexer rules. When {@code s} is + * a numeral, returns its value boxed either as a {@link Long} (for integer numerals) + * or a {@link Double} (for float numerals). Otherwise, returns {@code null}. + * + *

Note an integer numeral is also a float numeral, but not all float numerals are + * integer numerals. This method returns the "most canonical" representation of the numeric + * value represented by {@code s}: it first tries to parse {@code s} as an integer, + * attempting to parse {@code s} as a float only when {@code s} is not an integer numeral.

+ * + * @param s the string to be parsed, must not be {@code null} + * @return the numeric value represented by {@code s}, or {@code null} if {@code s} is not a + * numeral + */ + public static Number tryParseNumeral(String s) { + Long l = tryParseInteger(s); + return l != null ? l : (Number) tryParseFloat(s); + } + + private static boolean isASCIIPrintable(char c) { + // ASCII printable character range + return c >= 32 && c < 127; + } + + private static int shortEscape(char c) { + switch (c) { + case CHAR_BELL: + return 'a'; + case '\b': + return 'b'; + case '\f': + return 'f'; + case '\n': + return 'n'; + case '\r': + return 'r'; + case '\t': + return 't'; + case CHAR_VERTICAL_TAB: + return 'v'; + case '"': + return '"'; + default: + return -1; + } + } + + private static char toHex(int i) { + // i must be between 0x0 and 0xf + return i < 0xa ? (char) ((int) '0' + i) : (char) ((int) 'a' + i - 0xa); + } + + /** + * Returns a string {@code esc} formed from the character sequence {@code s} such that + * when {@code esc} is read by a Lua lexer as a string literal, it evaluates to a string equal + * to {@code s}. The resulting string is enclosed in double quotes ({@code "}). + * + * @param s the character sequence to escape, must not be {@code null} + * @return a Lua string literal representing {@code s} + * @throws NullPointerException if {@code s} is {@code null} + */ + public static String escape(CharSequence s) { + Objects.requireNonNull(s); + + StringBuilder bld = new StringBuilder(); + bld.append('"'); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c != '\\' && c != '"' && isASCIIPrintable(c)) { + bld.append(c); + } else { + // escaping + bld.append('\\'); + + int esc = shortEscape(c); + + if (esc != -1) { + bld.append((char) esc); + } else { + if ((int) c <= 0xff) { + bld.append('x'); + bld.append(toHex(((int) c >>> 8) & 0xf)); + bld.append(toHex((int) c & 0xf)); + } else { + bld.append(Integer.toString((int) c)); + } + } + } + } + + bld.append('"'); + return bld.toString(); + } + + /** + * Returns a string {@code esc} formed from the byte string {@code s} such that + * when {@code esc} is read by a Lua lexer as a string literal, it evaluates to + * a byte string equal to {@code s}. The resulting string is enclosed in double quotes + * ({@code "}). + * + * @param byteString the byte sequence sequence to escape, must not be {@code null} + * @return a Lua string literal representing {@code s} + * @throws NullPointerException if {@code s} is {@code null} + */ + public static String escape(ByteString byteString) { + return escape(byteString.toRawString()); + } + + /** + * Returns {@code true} iff the string {@code s} is a keyword in Lua. + * + *

A keyword in Lua is one of the following strings: + * {@code "and"}, {@code "break"}, {@code "do"}, {@code "else"}, {@code "elseif"}, + * {@code "end"}, {@code "false"}, {@code "for"}, {@code "function"}, {@code "goto"}, + * {@code "if"}, {@code "in"}, {@code "local"}, {@code "nil"}, {@code "not"}, + * {@code "or"}, {@code "repeat"}, {@code "return"}, {@code "then"}, {@code "true"}, + * {@code "until"}, {@code "while"}.

+ * + * @param s the string to be examined, may be {@code null} + * @return {@code true} if {@code s} is a Lua keyword; {@code false} otherwise + */ + public static boolean isKeyword(String s) { + return s != null && keywords.contains(s); + } + + /** + * Returns {@code true} iff the string {@code s} is a valid Lua name. + * + *

According to §3.1 of the Lua Reference Manual,

+ * + *
+ * Names (also called identifiers) in Lua can be any string of letters, digits, + * and underscores, not beginning with a digit and not being a reserved word. + *
+ * + *

This implementation treats letters as characters in the ranges + * {@code 'a'}...{@code 'z'} and {@code 'A'}...{@code 'Z'}, and numbers as characters in + * the range {@code '0'}...{@code '9'}.

+ * + * @param s the string to be checked for being a valid name, may be {@code null} + * @return {@code true} if {@code s} is a valid name in Lua; {@code false} otherwise + */ + public static boolean isValidName(String s) { + if (s == null || s.isEmpty() || isKeyword(s)) { + return false; + } + + char c = s.charAt(0); + + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) { + return false; + } + + for (int i = 1; i < s.length(); i++) { + c = s.charAt(i); + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_') && (c < '0' || c > '9')) { + return false; + } + } + + return true; + } + + +} diff --git a/luna/src/main/java/org/classdump/luna/LuaMathOperators.java b/luna/src/main/java/org/classdump/luna/LuaMathOperators.java new file mode 100644 index 00000000..99d47ecb --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/LuaMathOperators.java @@ -0,0 +1,600 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A collection of static methods for performing the equivalents of Lua's arithmetic, + * bitwise and numerical comparison operations. + * + *

This class and its methods exploits the isomorphism between Lua integers and + * Java {@code long} on the one hand, and between Lua floats and Java {@code double} + * on the other.

+ * + *

For each operation, there are as many variants in the form of a static method + * as there are valid type combinations. While all arithmetic operations + * are defined in two variants (for two {@code long}s and two {@code double}s, e.g. + * {@link #idiv(long, long)} and {@link #idiv(double, double)}), bitwise operations + * have single variants (taking two {@code long}s, e.g. {@link #band(long, long)}), + * and numerical comparison operations have four variants (for argument type combination).

+ * + *

It is the task of a Lua implementation to select the appropriate method and + * supply it with the required values; however, note that the method selection is well-behaved + * under the type conversion rules of the Java programming language.

+ */ +public final class LuaMathOperators { + + private static final double MAX_LONG_AS_DOUBLE = (double) Long.MAX_VALUE; + + // Arithmetic operators + private static final double MIN_LONG_AS_DOUBLE = (double) Long.MIN_VALUE; + + private LuaMathOperators() { + // not to be instantiated or extended + } + + /** + * Returns the result of the addition of two {@code long}s, equivalent to the addition + * of two integers in Lua. + * + * @param a first addend + * @param b second addend + * @return the value of the Lua expression {@code (a + b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long add(long a, long b) { + return a + b; + } + + /** + * Returns the result of the addition of two {@code double}s, equivalent to the addition + * of two floats in Lua. + * + * @param a first addend + * @param b second addend + * @return the value of the Lua expression {@code (a + b)}, where {@code a} and {@code b} are Lua + * floats + */ + public static double add(double a, double b) { + return a + b; + } + + /** + * Returns the result of the subtraction of two {@code long}s, equivalent to the subtraction + * of two integers in Lua. + * + * @param a the minuend + * @param b the subtrahend + * @return the value of the Lua expression {@code (a - b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long sub(long a, long b) { + return a - b; + } + + /** + * Returns the result of the subtraction of two {@code double}s, equivalent to the subtraction + * of two floats in Lua. + * + * @param a the minuend + * @param b the subtrahend + * @return the value of the Lua expression {@code (a - b)}, where {@code a} and {@code b} are Lua + * floats + */ + public static double sub(double a, double b) { + return a - b; + } + + /** + * Returns the result of the multiplication of two {@code long}s, equivalent to + * the multiplication of two integers in Lua. + * + * @param a first factor + * @param b second factor + * @return the value of the Lua expression {@code (a * b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long mul(long a, long b) { + return a * b; + } + + /** + * Returns the result of the multiplication of two {@code double}s, equivalent to + * the multiplication of two floats in Lua. + * + * @param a first factor + * @param b second factor + * @return the value of the Lua expression {@code (a * b)}, where {@code a} and {@code b} are Lua + * floats + */ + public static double mul(double a, double b) { + return a * b; + } + + /** + * Returns the result of the division of two {@code long}s, equivalent to the float + * division of two integers in Lua. The result is always a {@code double}; when {@code b} + * is zero, the result is NaN. + * + *

Note that this behaviour differs from the standard Java integer division.

+ * + * @param a the dividend + * @param b the divisor + * @return the value of the Lua expression {@code (a / b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static double div(long a, long b) { + return ((double) a) / ((double) b); + } + + /** + * Returns the result of the division of two {@code double}s, equivalent to the float + * division of two floats in Lua. + * + * @param a the dividend + * @param b the divisor + * @return the value of the Lua expression {@code (a / b)}, where {@code a} and {@code b} are Lua + * floats + */ + public static double div(double a, double b) { + return a / b; + } + + /** + * Returns the floor modulus of two {@code long}s, equivalent to the modulus + * of two integers in Lua. + * + *

Note that in Lua,

+ * + *
+ * Modulo is defined as the remainder of a division that rounds the quotient + * towards minus infinity (floor division). + *
+ * + *

This definition is not equivalent to the standard Java definition of modulo + * (which is the remainder of a division rounding toward zero).

+ * + * @param a the dividend + * @param b the divisor + * @return the value of the Lua expression {@code (a % b)}, where {@code a} and {@code b} are Lua + * integers + * @throws ArithmeticException if {@code b} is zero + */ + public static long mod(long a, long b) { + // Note: in JDK 8+, Math.floorMod could be used + if (b == 0) { + throw new ArithmeticException("attempt to perform 'n%0'"); + } else { + return a - b * (long) Math.floor((double) a / (double) b); + } + } + + /** + * Returns the floor modulus of two {@code double}s, equivalent to the modulus + * of two floats in Lua. + * + *

Note that in Lua,

+ * + *
+ * Modulo is defined as the remainder of a division that rounds the quotient + * towards minus infinity (floor division). + *
+ * + *

This definition is not equivalent to the standard Java definition of modulo + * (which is the remainder of a division rounding toward zero).

+ * + * @param a the dividend + * @param b the divisor + * @return the value of the Lua expression {@code (a % b)}, where {@code a} and {@code b} are Lua + * floats + */ + public static double mod(double a, double b) { + return b != 0 ? a - b * Math.floor(a / b) : Double.NaN; + } + + /** + * Returns the result of floor division of two {@code long}s, equivalent to the Lua + * floor division of two integers. + * + *

In Lua,

+ * + *
+ * Floor division (//) is a division that rounds the quotient towards minus infinity, + * that is, the floor of the division of its operands. + *
+ * + * @param a the dividend + * @param b the divisor + * @return the value of the Lua expression {@code (a // b)} where {@code a} and {@code b} are Lua + * integers + * @throws ArithmeticException if {@code b} is zero + */ + public static long idiv(long a, long b) { + if (b == 0) { + throw new ArithmeticException("attempt to divide by zero"); + } else { + long q = a / b; + return q * b == a || (a ^ b) >= 0 ? q : q - 1; + } + } + + /** + * Returns the result of floor division of two {@code double}s, equivalent to the Lua + * floor division of two floats: + * + *

In Lua,

+ * + *
+ * Floor division (//) is a division that rounds the quotient towards minus infinity, + * that is, the floor of the division of its operands. + *
+ * + * @param a the dividend + * @param b the divisor + * @return the value of the Lua expression {@code (a // b)} where {@code a} and {@code b} are Lua + * floats + */ + public static double idiv(double a, double b) { + return Math.floor(a / b); + } + + /** + * Returns the result of the exponentiation of two {@code long}s, equivalent to the Lua + * exponentiation of two integers. Note that the resulting value is a {@code double}. + * + * @param a the base + * @param b the exponent + * @return the value of the Lua expression {@code (a ^ b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static double pow(long a, long b) { + return Math.pow((double) a, (double) b); + } + + /** + * Returns the result of the exponentiation of two {@code double}s, equivalent to Lua + * exponentiation of two floats. + * + * @param a the base + * @param b the exponent + * @return the value of the Lua expression {@code (a ^ b)}, where {@code a} and {@code b} are Lua + * floats + */ + public static double pow(double a, double b) { + return Math.pow(a, b); + } + + // Bitwise operators + + /** + * Returns the result of the (arithmetic) negation of a {@code long}, equivalent to + * the Lua unary minus on an integer. + * + * @param n the operand + * @return the value of the Lua expression {@code (-n)}, where {@code n} is a Lua integer + */ + public static long unm(long n) { + return -n; + } + + /** + * Returns the result of the (arithmetic) negation of a {@code long}, equivalent to + * the Lua unary minus on a float. + * + * @param n the operand + * @return the value of the Lua expression {@code (-n)}, where {@code n} is a Lua float + */ + public static double unm(double n) { + return -n; + } + + /** + * Returns the result of the bitwise AND of two {@code long}s, equivalent to the Lua + * bitwise AND of two integers. + * + * @param a the first operand + * @param b the second operand + * @return the value of the Lua expression {@code (a & b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long band(long a, long b) { + return a & b; + } + + /** + * Returns the result of the bitwise OR of two {@code long}s, equivalent to the Lua + * bitwise OR of two integers. + * + * @param a the first operand + * @param b the second operand + * @return the value of the Lua expression {@code (a | b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long bor(long a, long b) { + return a | b; + } + + /** + * Returns the result of the bitwise exclusive OR of two {@code long}s, + * equivalent to the Lua bitwise exclusive OR of two integers. + * + * @param a the first operand + * @param b the second operand + * @return the value of the Lua expression {@code (a ~ b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long bxor(long a, long b) { + return a ^ b; + } + + /** + * Returns the result of the (bitwise) logical left shift, equivalent to the Lua bitwise + * left shift on two integers. Vacant bits are filled with zeros. When {@code b} is negative, + * the result is equal to the result of a right shift by {@code -b}. + * + *

Note that Lua's behaviour differs from Java's {@code <<} operator in that if + * {@code b} is greater than 64, the result is zero, as all bits have been shifted out.

+ * + * @param a the left-hand side operand + * @param b the right-hand side operand (shift distance) + * @return the value of the Lua expression {@code (a << b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long shl(long a, long b) { + return b < 0 ? shr(a, -b) : (b < 64 ? a << b : 0); + } + + // Numerical comparison operators + + /** + * Returns the result of the (bitwise) logical right shift, equivalent to the Lua bitwise + * right shift on two integers. Vacant bits are filled with zeros. When {@code b} is negative, + * the result is equal to the result of a left shift by {@code -b}. + * + *

Note that Lua's behaviour differs from Java's {@code >>>} operator in that if + * {@code b} is greater than 64, the result is zero, as all bits have been shifted out.

+ * + * @param a the left-hand side operand + * @param b the right-hand side operand (shift distance) + * @return the value of the Lua expression {@code (a << b)}, where {@code a} and {@code b} are Lua + * integers + */ + public static long shr(long a, long b) { + return b < 0 ? shl(a, -b) : (b < 64 ? a >>> b : 0); + } + + /** + * Returns the result of the bitwise unary NOT of a {@code long}, equivalent to the Lua + * bitwise unary NOT on an integer. + * + * @param n the operand + * @return the value of the Lua expression {@code (~b)}, where {@code n} is a Lua integer + */ + public static long bnot(long n) { + return ~n; + } + + /** + * Returns {@code true} iff the {@code double} {@code d} can be represented by + * a {@code long} without the loss of precision, i.e. if {@code ((long) d)} + * and {@code d} denote the same mathematical value. + * + * @param d the {@code double} in question + * @return {@code true} iff {@code d} can be represented by a {@code long} without the loss of + * precision + */ + public static boolean hasExactIntegerRepresentation(double d) { + long l = (long) d; + return (double) l == d && l != Long.MAX_VALUE; + } + + /** + * Returns {@code true} iff the {@code long} {@code l} can be represented by + * a {@code double} without the loss of precision, i.e. if {@code ((double) l)} + * and {@code l} denote the same mathematical value. + * + * @param l the {@code long} in question + * @return {@code true} iff {@code l} can be represented by a {@code double} without the loss of + * precision + */ + public static boolean hasExactFloatRepresentation(long l) { + double d = (double) l; + return (long) d == l && l != Long.MAX_VALUE; + } + + /** + * Returns {@code true} iff the {@code long}s {@code a} and {@code b} denote + * the same mathematical value. This is equivalent to the Lua numerical equality + * comparison of two integers. + * + * @param a a {@code long} + * @param b a {@code long} to be compared with {@code a} for mathematical equality + * @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} and {@code b} are + * Lua integers, would evaluate to (Lua) true + */ + public static boolean eq(long a, long b) { + return a == b; + } + + /** + * Returns {@code true} iff the {@code long} {@code a} denotes the same mathematical + * value as the {@code double} {@code b}. This is equivalent to the Lua numerical + * equality comparison of an integer and a float. + * + * @param a a {@code long} + * @param b a {@code double} to be compared with {@code a} for mathematical equality + * @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} is a Lua integer + * and {@code b} is a Lua float, would evaluate to (Lua) true + */ + public static boolean eq(long a, double b) { + return hasExactFloatRepresentation(a) && (double) a == b; + } + + /** + * Returns {@code true} iff the {@code double} {@code a} denotes the same mathematical + * value as the {@code long} {@code b}. This is equivalent to the Lua numerical equality + * comparison of a float and an integer. + * + * @param a a {@code double} + * @param b a {@code long} to be compared with {@code a} for mathematical equality + * @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} is a Lua float + * and {@code b} is a Lua integer, would evaluate to (Lua) true + */ + public static boolean eq(double a, long b) { + return hasExactFloatRepresentation(b) && a == (double) b; + } + + /** + * Returns {@code true} iff the {@code double}s {@code a} and {@code b} denote + * the same mathematical value. This is equivalent to the Lua numerical equality + * comparison of two doubles. + * + * @param a a {@code double} + * @param b a {@code double} to be compared with {@code a} for mathematical equality + * @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} and {@code b} are + * Lua floats, would evaluate to (Lua) true + */ + public static boolean eq(double a, double b) { + return a == b; + } + + /** + * Returns {@code true} iff the mathematical value of the {@code long} {@code a} + * is lesser than the mathematical value of the {@code long} {@code b}. This is equivalent + * to the Lua numerical lesser-than comparison of two integers. + * + * @param a a {@code long} + * @param b a {@code long} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} and {@code b} are + * Lua integers, would evaluate to (Lua) true + */ + public static boolean lt(long a, long b) { + return a < b; + } + + /** + * Returns {@code true} iff the mathematical value of the {@code long} {@code a} + * is lesser than the mathematical value of the {@code double} {@code b}. This + * is equivalent to the Lua numerical lesser-than comparison of an integer and a float. + * + * @param a a {@code long} + * @param b a {@code double} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} is a Lua integer + * and {@code b} is a Lua float, would evaluate to (Lua) true + */ + public static boolean lt(long a, double b) { + if (hasExactFloatRepresentation(a)) { + return (double) a < b; + } else { + return !Double.isNaN(b) && b > MIN_LONG_AS_DOUBLE && (b >= MAX_LONG_AS_DOUBLE + || a < (long) b); + } + } + + /** + * Returns {@code true} iff the mathematical value of the {@code double} {@code a} + * is lesser than the mathematical value of the {@code long} {@code b}. This + * is equivalent to the Lua numerical lesser-than comparison of a float and an integer. + * + * @param a a {@code double} + * @param b a {@code long} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} is a Lua float and + * {@code b} is a Lua integer, would evaluate to (Lua) true + */ + public static boolean lt(double a, long b) { + return !Double.isNaN(a) && !le(b, a); + } + + /** + * Returns {@code true} iff the mathematical value of the {@code double} {@code a} + * is lesser than the mathematical value of the {@code double} {@code b}. This + * is equivalent to the Lua numerical lesser-than comparison of two floats. + * + * @param a a {@code double} + * @param b a {@code double} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} and {@code b} are + * Lua floats, would evaluate to (Lua) true + */ + public static boolean lt(double a, double b) { + return a < b; + } + + /** + * Returns {@code true} iff the mathematical value of the {@code long} {@code a} + * is lesser than or equal to the mathematical value of the {@code long} {@code b}. + * This is equivalent to the Lua numerical lesser-than-or-equal comparison of + * two integers. + * + * @param a a {@code long} + * @param b a {@code long} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} and {@code b} are + * Lua integers, would evaluate to (Lua) true + */ + public static boolean le(long a, long b) { + return a <= b; + } + + /** + * Returns {@code true} iff the mathematical value of the {@code long} {@code a} + * is lesser than or equal to the mathematical value of the {@code double} {@code b}. + * This is equivalent to the Lua numerical lesser-than-or-equal comparison of + * an integer and a float. + * + * @param a a {@code long} + * @param b a {@code double} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} is a Lua integer + * and {@code b} is a Lua float, would evaluate to (Lua) true + */ + public static boolean le(long a, double b) { + if (hasExactFloatRepresentation(a)) { + return (double) a <= b; + } else { + return !Double.isNaN(b) && b > MIN_LONG_AS_DOUBLE && (b >= MAX_LONG_AS_DOUBLE + || a <= (long) b); + } + } + + /** + * Returns {@code true} iff the mathematical value of the {@code double} {@code a} + * is lesser than or equal to the mathematical value of the {@code long} {@code b}. + * This is equivalent to the Lua numerical lesser-than-or-equal comparison of + * a float and an integer. + * + * @param a a {@code double} + * @param b a {@code long} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} is a Lua float + * and {@code b} is a Lua integer, would evaluate to (Lua) true + */ + public static boolean le(double a, long b) { + return !Double.isNaN(a) && !lt(b, a); + } + + /** + * Returns {@code true} iff the mathematical value of the {@code double} {@code a} + * is lesser than or equal to the mathematical value of the {@code double} {@code b}. + * This is equivalent to the Lua numerical lesser-than-or-equal comparison of + * two floats. + * + * @param a a {@code double} + * @param b a {@code double} to be compared with {@code a} + * @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} and {@code b} are + * Lua floats, would evaluate to (Lua) true + */ + public static boolean le(double a, double b) { + return a <= b; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/LuaObject.java b/luna/src/main/java/org/classdump/luna/LuaObject.java new file mode 100644 index 00000000..ef3ba596 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/LuaObject.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * Base class of objects that have a metatable attached to them on a per-instance basis. + */ +public abstract class LuaObject { + + /** + * Returns the metatable of this object, or {@code null} if this object does not have + * a metatable. + * + * @return this object's metatable, or {@code null} if this object does not have a metatable + */ + public abstract Table getMetatable(); + + /** + * Sets the metatable of this object to {@code mt}. {@code mt} may be {@code null}: + * in that case, removes the metatable from this object. + * + *

Returns the metatable previously associated with this object (i.e., the metatable + * before the call of this method; possibly {@code null}).

+ * + * @param mt new metatable to attach to this object, may be {@code null} + * @return previous metatable associated with this object + */ + public abstract Table setMetatable(Table mt); + +} diff --git a/luna/src/main/java/org/classdump/luna/LuaRuntimeException.java b/luna/src/main/java/org/classdump/luna/LuaRuntimeException.java new file mode 100644 index 00000000..51dc158b --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/LuaRuntimeException.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * Base class for runtime exceptions that carry arbitrary error objects + * attached to them. + * + *

To retrieve the error object, use {@link #getErrorObject()}.

+ */ +public class LuaRuntimeException extends RuntimeException { + + private final Object errorObject; + + private LuaRuntimeException(Throwable cause, Object errorObject) { + super(cause); + this.errorObject = errorObject; + } + + /** + * Constructs a new {@code LuaRuntimeException} with {@code errorObject} as its + * error object. {@code errorObject} may be {@code null}. + * + * @param errorObject the error object, may be {@code null} + */ + public LuaRuntimeException(Object errorObject) { + this(null, errorObject); + } + + /** + * Constructs a new {@code LuaRuntimeException} with {@code cause} as its cause. + * + *

When queried for the error object, invokes {@link Conversions#toErrorObject(Throwable)} + * on {@code cause}; when {@code cause} is {@code null}, then the error object + * is {@code null}.

+ * + * @param cause the cause of this error, may be {@code null} + */ + public LuaRuntimeException(Throwable cause) { + this(cause, null); + } + + /** + * Returns the error object attached to this exception converted to a string. + * + * @return error object converted to a string + */ + @Override + public String getMessage() { + return getErrorLocation() + Conversions.toHumanReadableString(getErrorObject()).toString(); + } + + /** + * Returns the error object attached to this exception. The error object may be {@code null}. + * + * @return the error object attached to this exception (possibly {@code null}) + */ + public Object getErrorObject() { + Throwable cause = getCause(); + if (cause != null) { + return Conversions.toErrorObject(cause); + } else { + return errorObject; + } + } + + /** + * Returns the closest location in the Lua code when this exception was triggered. + * + * @return the location of this error in the Lua code, @{code file:line} or @{code unknown:-1} if + * it could not be determined + */ + public String getErrorLocation() { + for (StackTraceElement stackTraceElement : getStackTrace()) { + if (stackTraceElement.getClassName().startsWith("luna_dynamic")) { + return stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber() + ": "; + } + } + return ""; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/LuaType.java b/luna/src/main/java/org/classdump/luna/LuaType.java new file mode 100644 index 00000000..2b0c2fcd --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/LuaType.java @@ -0,0 +1,294 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import org.classdump.luna.runtime.Coroutine; +import org.classdump.luna.runtime.LuaFunction; + +/** + * An enum representing a Lua type. + * + *

There are eight types in Lua ({@code nil}, {@code boolean}, {@code number}, {@code string}, + * {@code function}, {@code userdata}, {@code thread} and {@code table}). In Luna, + * all Java object references are mapped to a Lua type according to the following list:

+ * + *
    + *
  • {@link #NIL} ... no explicit {@code nil} type; any (Java) {@code null} value is + * considered nil
  • + *
  • {@link #BOOLEAN} ... instances of {@link Boolean java.lang.Boolean}
  • + *
  • {@link #NUMBER} ... instances of {@link Number java.lang.Number}: + *
      + *
    • float ... {@link Double java.lang.Double} (canonical) + * or {@link Float java.lang.Float}
    • + *
    • integer ... any other subclass of {@code java.lang.Number}, + * with {@link Long java.lang.Long} being the canonical + * representation
    • + *
    + *
  • + *
  • {@link #STRING} ... instances of {@link String java.lang.String}
  • + *
  • {@link #FUNCTION} ... {@link LuaFunction}
  • + *
  • {@link #USERDATA}: + *
      + *
    • full userdata ... {@link Userdata}
    • + *
    • light userdata ... instances of any class other than those mentioned + * in this list
    • + *
    + *
  • + *
  • {@link #THREAD} ... {@link Coroutine}
  • + *
  • {@link #TABLE} ... {@link Table}
  • + *
+ * + *

For numeric values, the canonical representation is the default, full-precision + * representation of the number as either a float or integer. To convert a number to its + * canonical value, use {@link Conversions#toCanonicalNumber(Number)}.

+ * + *

To retrieve the name of the type of a Lua value, use the {@link ValueTypeNamer} + * interface (for names based on the type only, without taking into account the {@code __name} + * metamethod, use {@link PlainValueTypeNamer}).

+ */ +public enum LuaType { + + /** + * The Lua {@code nil} type, corresponding to {@code null} references. + */ + NIL, + + /** + * The Lua {@code boolean} type, corresponding to instances + * of {@link Boolean java.lang.Boolean}. + */ + BOOLEAN, + + /** + * The Lua {@code number} type, corresponding to instances of {@link Number java.lang.Number}. + * + *

Instances of {@link Double java.lang.Double} and {@link Float java.lang.Float} + * are mapped to Lua floats ({@code Double}s being the canonical representation). All other + * subclasses of {@code Number} are mapped to Lua integers, with {@link Long java.lang.Long} + * being the canonical representation.

+ */ + NUMBER, + + /** + * The Lua {@code string} type, corresponding to instances of {@link ByteString} + * and {@link String java.lang.String}. + */ + STRING, + + /** + * The Lua {@code function} type, corresponding to instances of {@link LuaFunction}. + */ + FUNCTION, + + /** + * The Lua {@code userdata} type, corresponding to instances of {@link Userdata} (for full + * userdata), or any other subclasses of {@link java.lang.Object} not mapped to a Lua + * type (for light userdata). + */ + USERDATA, + + /** + * The Lua {@code thread} type, corresponding to instances of the {@link Coroutine} class. + */ + THREAD, + + /** + * The Lua {@code table} type, corresponding to instances of {@link Table}. + */ + TABLE; + + /** + * Returns the Lua type of the object {@code o}. + * + * @param o the object to determine the type of, may be {@code null} + * @return the Lua type of {@code o} + */ + public static LuaType typeOf(Object o) { + if (o == null) { + return LuaType.NIL; + } else if (o instanceof Boolean) { + return LuaType.BOOLEAN; + } else if (o instanceof Number) { + return LuaType.NUMBER; + } else if (o instanceof ByteString || o instanceof String) { + return LuaType.STRING; + } else if (o instanceof Table) { + return LuaType.TABLE; + } else if (o instanceof LuaFunction) { + return LuaType.FUNCTION; + } else if (o instanceof Coroutine) { + return LuaType.THREAD; + } else { + return LuaType.USERDATA; + } + } + + /** + * Returns {@code true} iff {@code o} is nil. + * + *

{@code o} is nil if and only if {@code o} is {@code null}.

+ * + * @param o the object to test for being nil, may be {@code null} + * @return {@code true} iff {@code o} is nil + */ + public static boolean isNil(Object o) { + return o == null; + } + + /** + * Returns {@code true} iff {@code o} is a Lua boolean. + * + *

{@code o} is a Lua boolean if and only if {@code o} is an instance of + * {@link Boolean java.lang.Boolean}.

+ * + * @param o the object to test for being a boolean, may be {@code null} + * @return {@code true} iff {@code o} is a Lua boolean + */ + public static boolean isBoolean(Object o) { + return o instanceof Boolean; + } + + /** + * Returns {@code true} iff {@code o} is a Lua number. + * + *

{@code o} is a Lua number if and only if {@code o} is an instance of + * {@link Number java.lang.Number}.

+ * + * @param o the object to test for being a number, may be {@code null} + * @return {@code true} iff {@code o} is a Lua number + */ + public static boolean isNumber(Object o) { + return o instanceof Number; + } + + /** + * Returns {@code true} iff {@code o} is a Lua float. + * + *

{@code o} is a Lua float if and only if {@code o} is an instance of + * {@link Double java.lang.Double} or {@link Float java.lang.Float}.

+ * + * @param o the object to test for being a float, may be {@code null} + * @return {@code true} iff {@code o} is a Lua float + */ + public static boolean isFloat(Object o) { + return o instanceof Double || o instanceof Float; + } + + /** + * Returns {@code true} iff {@code o} is a Lua integer. + * + *

{@code o} is a Lua number if and only if {@code o} is a Lua number and is not + * a Lua float.

+ * + * @param o the object to test for being an integer, may be {@code null} + * @return {@code true} iff {@code o} is a Lua integer + */ + public static boolean isInteger(Object o) { + return isNumber(o) && !isFloat(o); + } + + /** + * Returns {@code true} iff {@code o} is a Lua string. + * + *

{@code o} is a Lua string if and only if {@code o} is an instance of + * {@link ByteString} or {@link String java.lang.String}.

+ * + * @param o the object to test for being a string, may be {@code null} + * @return {@code true} iff {@code o} is a Lua string + */ + public static boolean isString(Object o) { + return o instanceof ByteString || o instanceof String; + } + + /** + * Returns {@code true} iff {@code o} is a Lua function. + * + *

{@code o} is a Lua function if and only if {@code o} is an instance of + * {@link LuaFunction}.

+ * + * @param o the object to test for being a function, may be {@code null} + * @return {@code true} iff {@code o} is a Lua function + */ + public static boolean isFunction(Object o) { + return o instanceof LuaFunction; + } + + /** + * Returns {@code true} iff {@code o} is a Lua userdata. + * + *

{@code o} is a Lua userdata if it is not {@code nil}, {@code boolean}, {@code number}, + * {@code string}, {@code function}, {@code thread} or {@code table}.

+ * + * @param o the object to test for being a userdata, may be {@code null} + * @return {@code true} iff {@code o} is a Lua userdata + */ + public static boolean isUserdata(Object o) { + return typeOf(o) == USERDATA; + } + + /** + * Returns {@code true} iff {@code o} is full userdata. + * + *

{@code o} is full userdata if and only if {@code o} is an instance of + * {@link Userdata}.

+ * + * @param o the object to test for being full userdata, may be {@code null} + * @return {@code true} iff {@code o} is full userdata + */ + public static boolean isFullUserdata(Object o) { + return o instanceof Userdata; + } + + /** + * Returns {@code true} iff the object {@code o} is light userdata. + * + *

An object is light userdata when its Lua type is {@link #USERDATA} and it + * is not an instance of the {@link Userdata} class. In other words, it is not an + * instance of any class mapped to a Lua type.

+ * + * @param o the object to test for being light userdata, may be {@code null} + * @return {@code true} iff {@code o} is light userdata + */ + public static boolean isLightUserdata(Object o) { + return !isFullUserdata(o) && isUserdata(o); + } + + /** + * Returns {@code true} iff the object {@code o} is a Lua thread. + * + *

{@code o} is a Lua thread if and only if {@code o} is an instance of {@link Coroutine}.

+ * + * @param o the object to test for being a Lua thread, may be {@code null} + * @return {@code true} iff {@code o} is a Lua thread + */ + public static boolean isThread(Object o) { + return o instanceof Coroutine; + } + + /** + * Returns {@code true} iff the object {@code o} is a Lua table. + * + *

{@code o} is a Lua table if and only if {@code o} is an instance of {@link Table}.

+ * + * @param o the object to test for being a Lua table, may be {@code null} + * @return {@code true} iff {@code o} is a Lua table + */ + public static boolean isTable(Object o) { + return o instanceof Table; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/MetatableAccessor.java b/luna/src/main/java/org/classdump/luna/MetatableAccessor.java new file mode 100644 index 00000000..63015d2b --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/MetatableAccessor.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A metatable accessor, an interface for getting and setting object metatables. + * + *

In Lua, only tables and (full) userdata carry their own metatables; for all other + * types of values T, all values of type T share a metatable. This interface + * provides a uniform setter for metatables of all types.

+ */ +public interface MetatableAccessor extends MetatableProvider { + + /** + * Sets the metatable for nil (i.e., the {@code nil} type) to {@code table}. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for the {@code nil} type, may be {@code null} + * @return the previous metatable for the {@code nil} type + */ + Table setNilMetatable(Table table); + + /** + * Sets the metatable for the {@code boolean} type. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for the {@code boolean} type, may be {@code null} + * @return the previous metatable for the {@code boolean} type + */ + Table setBooleanMetatable(Table table); + + /** + * Sets the metatable for the {@code number} type. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for the {@code number} type, may be {@code null} + * @return the previous metatable for the {@code number} type + */ + Table setNumberMetatable(Table table); + + /** + * Sets the metatable for the {@code string} type. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for the {@code string} type, may be {@code null} + * @return the previous metatable for the {@code string} type + */ + Table setStringMetatable(Table table); + + /** + * Sets the metatable for the {@code function} type. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for the {@code function} type, may be {@code null} + * @return the previous metatable for the {@code function} type + */ + Table setFunctionMetatable(Table table); + + /** + * Sets the metatable for the {@code thread} type. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for the {@code thread} type, may be {@code null} + * @return the previous metatable for the {@code thread} type + */ + Table setThreadMetatable(Table table); + + /** + * Sets the metatable for light userdata. + * {@code table} may be {@code null}: in that case, clears the metatable. Returns + * the previous metatable. + * + * @param table new metatable for light userdata, may be {@code null} + * @return the previous metatable for light userdata + */ + Table setLightUserdataMetatable(Table table); + + /** + * Sets the metatable of the object {@code instance} to {@code table}. + * {@code table} may be {@code null}: in that case, clears {@code instance}'s metatable. + * Returns the previous metatable. + * + *

Note that {@code instance} may share the metatable with other instances of the same + * (Lua) type. This method provides a uniform interface for setting the metatables + * of all types.

+ * + * @param instance object to set the metatable of, may be {@code null} + * @param table new metatable of {@code instance}, may be {@code null} + * @return the previous metatable of {@code instance} + */ + Table setMetatable(Object instance, Table table); + +} diff --git a/luna/src/main/java/org/classdump/luna/MetatableProvider.java b/luna/src/main/java/org/classdump/luna/MetatableProvider.java new file mode 100644 index 00000000..c44d2177 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/MetatableProvider.java @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * An interface for obtaining value metatables. + * + *

In Lua, only tables and (full) userdata carry their own metatables; for all other + * types of values T, all values of type T share a metatable. This interface + * provides uniform access to metatables of all types.

+ */ +public interface MetatableProvider { + + /** + * Returns the metatable for nil (the {@code nil} type), or {@code null} if this + * provider does not assign a metatable to the {@code nil} type. + * + * @return the metatable for the {@code nil} type + */ + Table getNilMetatable(); + + /** + * Returns the metatable for {@code boolean} values, or {@code null} if this provider does + * not assign a metatable to the {@code boolean} type. + * + * @return the metatable for the {@code boolean} type + */ + Table getBooleanMetatable(); + + /** + * Returns the metatable for {@code number} values, or {@code null} if this provider does + * not assign a metatable to the {@code number} type. + * + * @return the metatable for the {@code number} type + */ + Table getNumberMetatable(); + + /** + * Returns the metatable for {@code string} values, or {@code null} if this provider does + * not assign a metatable to the {@code string} type. + * + * @return the metatable for the {@code string} type + */ + Table getStringMetatable(); + + /** + * Returns the metatable for {@code function} values, or {@code null} if this provider does + * not assign a metatable to the {@code function} type. + * + * @return the metatable for the {@code function} type + */ + Table getFunctionMetatable(); + + /** + * Returns the metatable for {@code thread} values, or {@code null} if this provider does + * not assign a metatable to the {@code thread} type. + * + * @return the metatable for the {@code thread} type + */ + Table getThreadMetatable(); + + /** + * Returns the metatable for light userdata, or {@code null} if this provider does + * not assign a metatable to light userdata.. + * + * @return the metatable for light userdata + */ + Table getLightUserdataMetatable(); + + /** + * Returns the metatable for the object {@code instance}, or {@code null} if this + * metatable provider does not assign any metatable to {@code instance}. + * + * @param instance the object to obtain a metatable for, may be {@code null} + * @return the metatable of {@code instance}, or {@code null} if there is no metatable assigned to + * {@code instance} in this provider + */ + Table getMetatable(Object instance); + +} diff --git a/luna/src/main/java/org/classdump/luna/Metatables.java b/luna/src/main/java/org/classdump/luna/Metatables.java new file mode 100644 index 00000000..a267164b --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Metatables.java @@ -0,0 +1,226 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.util.Objects; + +/** + * Metatable keys and utilities. + */ +public final class Metatables { + + /** + * The metatable key {@code "__add"}. When defined, customises the behaviour of + * the Lua addition operator ({@code +}). + */ + public static final ByteString MT_ADD = ByteString.constOf("__add"); + /** + * The metatable key {@code "__sub"}. When defined, customises the behaviour of + * the Lua subtraction operator (binary {@code -}). + */ + public static final ByteString MT_SUB = ByteString.constOf("__sub"); + /** + * The metatable key {@code "__mul"}. When defined, customises the behaviour of + * the Lua multiplication operator ({@code *}). + */ + public static final ByteString MT_MUL = ByteString.constOf("__mul"); + /** + * The metatable key {@code "__div"}. When defined, customises the behaviour of + * the Lua division operator ({@code /}). + */ + public static final ByteString MT_DIV = ByteString.constOf("__div"); + /** + * The metatable key {@code "__mod"}. When defined, customises the behaviour of + * the Lua modulo operator ({@code %}). + */ + public static final ByteString MT_MOD = ByteString.constOf("__mod"); + /** + * The metatable key {@code "__pow"}. When defined, customises the behaviour of + * the Lua exponentiation operator ({@code ^}). + */ + public static final ByteString MT_POW = ByteString.constOf("__pow"); + /** + * The metatable key {@code "__unm"}. When defined, customises the behaviour of + * the Lua unary minus operator (unary {@code -}). + */ + public static final ByteString MT_UNM = ByteString.constOf("__unm"); + /** + * The metatable key {@code "__idiv"}. When defined, customises the behaviour of + * the Lua floor division ({@code //}). + */ + public static final ByteString MT_IDIV = ByteString.constOf("__idiv"); + /** + * The metatable key {@code "__band"}. When defined, customises the behaviour of + * the Lua bitwise AND operator ({@code &}). + */ + public static final ByteString MT_BAND = ByteString.constOf("__band"); + /** + * The metatable key {@code "__bor"}. When defined, customises the behaviour of + * the Lua bitwise OR operator ({@code |}). + */ + public static final ByteString MT_BOR = ByteString.constOf("__bor"); + /** + * The metatable key {@code "__bxor"}. When defined, customises the behaviour of + * the Lua bitwise XOR operator (binary {@code ~}). + */ + public static final ByteString MT_BXOR = ByteString.constOf("__bxor"); + /** + * The metatable key {@code "__bnot"}. When defined, customises the behaviour of + * the Lua bitwise NOT operator (unary {@code ~}). + */ + public static final ByteString MT_BNOT = ByteString.constOf("__bnot"); + /** + * The metatable key {@code "__shl"}. When defined, customises the behaviour of + * the Lua bitwise left shift operator ({@code <<}). + */ + public static final ByteString MT_SHL = ByteString.constOf("__shl"); + /** + * The metatable key {@code "__shr"}. When defined, customises the behaviour of + * the Lua bitwise right shift operator ({@code >>}). + */ + public static final ByteString MT_SHR = ByteString.constOf("__shr"); + /** + * The metatable key {@code "__concat"}. When defined, customises the behaviour of + * the Lua concatenation operator ({@code ..}). + */ + public static final ByteString MT_CONCAT = ByteString.constOf("__concat"); + /** + * The metatable key {@code "__len"}. When defined, customises the behaviour of + * the Lua length operator ({@code #}). + */ + public static final ByteString MT_LEN = ByteString.constOf("__len"); + /** + * The metatable key {@code "__eq"}. When defined, customises the behaviour of + * the Lua equality operator ({@code ==}). + */ + public static final ByteString MT_EQ = ByteString.constOf("__eq"); + /** + * The metatable key {@code "__lt"}. When defined, customises the behaviour of + * the Lua lesser-than operator ({@code <}). + */ + public static final ByteString MT_LT = ByteString.constOf("__lt"); + /** + * The metatable key {@code "__le"}. When defined, customises the behaviour of + * the Lua lesser-than-or-equal-to operator ({@code <=}). + */ + public static final ByteString MT_LE = ByteString.constOf("__le"); + /** + * The metatable key {@code "__index"}. When defined, customises the behaviour of + * the (non-assignment) Lua table access operator ({@code t[k]}). + */ + public static final ByteString MT_INDEX = ByteString.constOf("__index"); + /** + * The metatable key {@code "__newindex"}. When defined, customises the behaviour of + * Lua table assignment ({@code t[k] = v}). + */ + public static final ByteString MT_NEWINDEX = ByteString.constOf("__newindex"); + /** + * The metatable key {@code "__call"}. When defined, customises the behaviour of + * the Lua call operator ({@code f(args)}). + */ + public static final ByteString MT_CALL = ByteString.constOf("__call"); + /** + * The metatable key {@code "__mode"}. Used to control the weakness of table keys + * and values. + */ + public static final ByteString MT_MODE = ByteString.constOf("__mode"); + + private Metatables() { + // not to be instantiated + } + + /** + * Returns the entry with the key {@code event} of the metatable of the {@link LuaObject} + * {@code o}. If {@code o} does not have a metatable or {@code event} does not exist in it as + * a key, returns {@code null}. + * + *

The access of the metatable is raw (i.e. uses {@link Table#rawget(Object)}).

+ * + *

This method differs from {@link #getMetamethod(MetatableProvider, ByteString, Object)} + * in that it does not require a metatable provider as the object in question is known + * to have metatables attached on a per-instance basis.

+ * + * @param event the key to look up in the metatable, must not be {@code null} + * @param o the object in question, must not be {@code null} + * @return a non-{@code null} value if {@code event} is a key in {@code o}'s metatable; {@code + * null} otherwise + * @throws NullPointerException if {@code o} or {@code event} is {@code null} + */ + public static Object getMetamethod(ByteString event, LuaObject o) { + Objects.requireNonNull(event); + Objects.requireNonNull(o); + + Table mt = o.getMetatable(); + if (mt != null) { + return mt.rawget(event); + } else { + return null; + } + } + + /** + * Returns the entry with the key {@code event} of the metatable of the object {@code o}. + * If {@code o} does not have a metatable or {@code event} does not exist in it as + * a key, returns {@code null}. + * + *

The access of the metatable is raw (i.e. uses {@link Table#rawget(Object)}).

+ * + * @param metatableProvider the metatable provider, must not be {@code null} + * @param event the key to look up in the metatable, must not be {@code null} + * @param o the object in question, may be {@code null} + * @return a non-{@code null} value if {@code event} is a key in {@code o}'s metatable; {@code + * null} otherwise + * @throws NullPointerException if {@code metatableProvider} or {@code event} is {@code null} + */ + public static Object getMetamethod(MetatableProvider metatableProvider, ByteString event, + Object o) { + Objects.requireNonNull(event); + // o can be null + + Table mt = metatableProvider.getMetatable(o); + if (mt != null) { + return mt.rawget(event); + } else { + return null; + } + } + + /** + * Returns the metatable entry {@code event} for {@code a} or in {@code b}, or {@code null} + * if neither {@code a} nor {@code b} has such an entry in their metatable. + * + *

This method is similar to {@link #getMetamethod(MetatableProvider, ByteString, Object)}, + * but first looks up the entry {@code event} in {@code a}, and if this fails (by + * returning {@code null}), tries to look {@code event} up in {@code b}. + * + * @param metatableProvider the metatable provider, must not be {@code null} + * @param event the key to look up in the metatable, must not be {@code null} + * @param a the first object to try, may be {@code null} + * @param b the second object to try, may be {@code null} + * @return a non-{@code null} value if {@code event} is a key in {@code a}'s or {@code b}'s + * metatable (in this order); {@code null} otherwise + * @throws NullPointerException if {@code metatableProvider} or {@code event} is {@code null} + */ + public static Object binaryHandlerFor(MetatableProvider metatableProvider, ByteString event, + Object a, Object b) { + Objects.requireNonNull(metatableProvider); + Objects.requireNonNull(event); + Object ma = Metatables.getMetamethod(metatableProvider, event, a); + return ma != null ? ma : Metatables.getMetamethod(metatableProvider, event, b); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/NoIntegerRepresentationException.java b/luna/src/main/java/org/classdump/luna/NoIntegerRepresentationException.java new file mode 100644 index 00000000..fb1a661f --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/NoIntegerRepresentationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * An exception thrown to indicate an unsuccessful conversion of a number to an integer, + * i.e., when a number has no integer representation. + */ +public class NoIntegerRepresentationException extends ConversionException { + + /** + * Constructs a new instance of {@code NoIntegerRepresentationException}. + */ + public NoIntegerRepresentationException() { + super("number has no integer representation"); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/Ordering.java b/luna/src/main/java/org/classdump/luna/Ordering.java new file mode 100644 index 00000000..398fb990 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Ordering.java @@ -0,0 +1,441 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.util.Comparator; + +/** + * A representation of an ordering on values, allowing the comparison of values + * of the same type (in the type parameter {@code T}). + * + *

In Lua, only strings and numbers have such an ordering. This class serves the + * purpose of a bridge between the concrete representation of Lua numbers + * (as {@link java.lang.Number}) and the raw comparison operations provided by + * {@link LuaMathOperators}, and the concrete representation of Lua strings + * (as {@link java.lang.String}) and the comparison operations defined on them.

+ * + *

Consequently, there are two concrete implementations of this class: + * {@link #NUMERIC} for the numeric ordering and {@link #STRING} for the string + * ordering. These instances may be used directly for comparing objects of known, + * conforming types; for unknown objects, the method {@link #of(Object, Object)} + * returns an ordering that accepts {@link java.lang.Object}, and uses one of + * the two ordering instances, or {@code null} if the arguments + * are not directly comparable in Lua.

+ * + *

The comparison methods of this class return unboxed booleans.

+ * + *

This class implements the {@link Comparator} interface by imposing a total + * order on the accepted values. For numbers, this total ordering is different + * from the one imposed by this class. See the documentation of {@link NumericOrdering} + * for more details.

+ * + *

Example: Given two objects {@code a}, and {@code b}, attempt to + * evaluate the Lua expression {@code (a <= b)}:

+ * + *
+ *     // Object a, b
+ *     final boolean result;
+ *     Ordering<Object> cmp = Ordering.of(a, b);
+ *     if (cmp != null) {
+ *         // a and b are comparable in cmp
+ *         result = cmp.le(a, b);
+ *     }
+ *     else {
+ *         throw new RuntimeException("a and b not comparable");
+ *     }
+ * 
+ * + * @param the type of values comparable in the ordering + */ +public abstract class Ordering implements Comparator { + + /** + * A static instance of the numeric ordering. + */ + public static final NumericOrdering NUMERIC = new NumericOrdering(); + /** + * A static instance of the string ordering. + */ + public static final StringOrdering STRING = new StringOrdering(); + private static final NumericObjectOrdering NUMERIC_OBJECT = new NumericObjectOrdering(); + private static final StringObjectOrdering STRING_OBJECT = new StringObjectOrdering(); + + private Ordering() { + // not to be instantiated by the outside world + } + + /** + * Returns {@code true} iff the object {@code a} is raw-equal to {@code b} following + * the Lua equality rules. + * + *

Excerpt from the Lua Reference Manual (§3.4.4):

+ * + *
+ *

Equality (==) first compares the type of its operands. If the types are different, + * then the result is false. Otherwise, the values of the operands are compared. + * Strings are compared in the obvious way. Numbers are equal if they denote the + * same mathematical value.

+ * + *

Tables, userdata, and threads are compared by reference: two objects are considered + * equal only if they are the same object. Every time you create a new object (a table, + * userdata, or thread), this new object is different from any previously existing + * object. Closures with the same reference are always equal. Closures with any + * detectable difference (different behavior, different definition) are always + * different.

+ *
+ * + *

Note: Luna uses {@link Object#equals(Object)} to compare all non-nil, + * non-string, and non-numeric values for equality, effectively shifting the + * responsibility of adhering to the rules of Lua raw-equality for tables, userdata + * and threads to their implementations.

+ * + * @param a an object, may be {@code null} + * @param b another object, may be {@code null} + * @return {@code true} iff {@code a} is raw-equal to {@code b} + */ + public static boolean isRawEqual(Object a, Object b) { + if (a == null && b == null) { + // two nils + return true; + } else if (a == null) { + // b is definitely not nil; also ensures that neither a nor b is null in the tests below + return false; + } else if (a instanceof Number && b instanceof Number) { + return Ordering.NUMERIC.eq((Number) a, (Number) b); + } else if (LuaType.isString(a) && LuaType.isString(b)) { + return Ordering.STRING.eq(toByteString(a), toByteString(b)); + } else { + return a.equals(b); + } + } + + private static ByteString toByteString(Object o) throws ClassCastException { + if (o instanceof ByteString) { + return (ByteString) o; + } else { + return ByteString.of((String) o); // may throw a ClassCastException + } + } + + /** + * Based on the actual types of the arguments {@code a} and {@code b}, returns + * the ordering in which {@code a} and {@code b} can be compared, or {@code null} + * if they are not comparable. + * + *

More specifically, if {@code a} and {@code b} are both numbers, returns + * an ordering that uses (but is distinct from) {@link #NUMERIC}; if {@code a} and + * {@code b} are both strings, returns an ordering that uses (but is distinct from) + * {@link #STRING}; otherwise, returns {@code null}.

+ * + *

Note that when the result is non-{@code null}, it is guaranteed that + * 1) neither {@code a} nor {@code b} is {@code null}; and 2) + * both {@code a} and {@code b} are of types accepted by the underlying ordering. + * Caution must be observed when using the ordering with another object {@code c} + * (i.e., other than {@code a} or {@code b}): the returned ordering will throw + * a {@link ClassCastException} if {@code c} is of an incompatible type, or + * a {@link NullPointerException} if {@code c} is {@code null}.

+ * + * @param a an object, may be {@code null} + * @param b another object, may be {@code null} + * @return an ordering based on {@link #NUMERIC} if both {@code a} and {@code b} are numbers; an + * ordering based on {@link #STRING} if both {@code a} and {@code b} are strings; {@code null} + * otherwise + */ + public static Ordering of(Object a, Object b) { + if (a instanceof Number && b instanceof Number) { + return NUMERIC_OBJECT; + } else if (LuaType.isString(a) && LuaType.isString(b)) { + return STRING_OBJECT; + } else { + return null; + } + } + + /** + * Returns {@code true} if {@code a} is equal to {@code b} in this ordering. + * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return {@code true} iff {@code a} is equal to {@code b} in this ordering + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract boolean eq(T a, T b); + + /** + * Returns {@code true} if {@code a} is lesser than {@code b} in this ordering. + * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return {@code true} iff {@code a} is lesser than {@code b} in this ordering + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract boolean lt(T a, T b); + + /** + * Returns {@code true} if {@code a} is lesser than or equal to {@code b} in this ordering. + * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return {@code true} iff {@code a} is lesser than or equal to equal to {@code b} in this + * ordering + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + public abstract boolean le(T a, T b); + + /** + * Numeric ordering. + * + *

Numbers are compared using the comparison methods provided by {@link LuaMathOperators}, + * defining the ordering as one based on the ordering of the mathematical values + * of the numbers in question.

+ * + *

This class implements the {@link Comparator} interface by imposing a total order + * on numbers that differs from the ordering defined by the methods + * {@link #eq(Number, Number)}, {@link #lt(Number, Number)} + * and {@link #le(Number, Number)}:

+ * + *
    + *
  • NaN is treated as equal to itself and greater than any other + * number, while {@code eq(a, b) == false} and {@code lt(a, b) == false} + * when {@code a} or {@code b} is NaN; + *
  • {@code -0.0} is considered to be lesser than {@code 0.0}, + * while {@code eq(-0.0, 0.0) == true} and {@code lt(-0.0, 0.0) == false}.
  • + *
+ * + *

Note that the total ordering imposed by the {@link #compare(Number, Number)} + * is inconsistent with equals.

+ * + *

For proper treatment of NaNs and (float) zero values, use the + * {@code Ordering} methods directly.

+ */ + public static final class NumericOrdering extends Ordering { + + private NumericOrdering() { + // not to be instantiated by the outside world + } + + /** + * Returns {@code true} iff {@code a} denotes the same mathematical value + * as {@code b}. + * + *

Note that since NaN does not denote any mathematical value, + * this method returns {@code false} whenever any of its arguments is NaN.

+ * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return {@code true} iff {@code a} and {@code b} denote the same mathematical value + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + @Override + public boolean eq(Number a, Number b) { + boolean isflt_a = a instanceof Double || a instanceof Float; + boolean isflt_b = b instanceof Double || b instanceof Float; + + if (isflt_a) { + return isflt_b + ? LuaMathOperators.eq(a.doubleValue(), b.doubleValue()) + : LuaMathOperators.eq(a.doubleValue(), b.longValue()); + } else { + return isflt_b + ? LuaMathOperators.eq(a.longValue(), b.doubleValue()) + : LuaMathOperators.eq(a.longValue(), b.longValue()); + } + } + + /** + * Returns {@code true} iff the mathematical value denoted by {@code a} + * is lesser than the mathematical value denoted by {@code b}. + * + *

Note that since NaN does not denote any mathematical value, + * this method returns {@code false} whenever any of its arguments is NaN.

+ * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return {@code true} iff the mathematical value denoted by {@code a} is lesser than the + * mathematical value denoted by {@code b} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + @Override + public boolean lt(Number a, Number b) { + boolean isflt_a = a instanceof Double || a instanceof Float; + boolean isflt_b = b instanceof Double || b instanceof Float; + + if (isflt_a) { + return isflt_b + ? LuaMathOperators.lt(a.doubleValue(), b.doubleValue()) + : LuaMathOperators.lt(a.doubleValue(), b.longValue()); + } else { + return isflt_b + ? LuaMathOperators.lt(a.longValue(), b.doubleValue()) + : LuaMathOperators.lt(a.longValue(), b.longValue()); + } + } + + /** + * Returns {@code true} iff the mathematical value denoted by {@code a} + * is lesser than or equal to the mathematical value denoted by {@code b}. + * + *

Note that since NaN does not denote any mathematical value, + * this method returns {@code false} whenever any of its arguments is NaN.

+ * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return {@code true} iff the mathematical value denoted by {@code a} is lesser than or equal + * to the mathematical value denoted by {@code b} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + @Override + public boolean le(Number a, Number b) { + boolean isflt_a = a instanceof Double || a instanceof Float; + boolean isflt_b = b instanceof Double || b instanceof Float; + + if (isflt_a) { + return isflt_b + ? LuaMathOperators.le(a.doubleValue(), b.doubleValue()) + : LuaMathOperators.le(a.doubleValue(), b.longValue()); + } else { + return isflt_b + ? LuaMathOperators.le(a.longValue(), b.doubleValue()) + : LuaMathOperators.le(a.longValue(), b.longValue()); + } + } + + /** + * Compare the numbers {@code a} and {@code b}, yielding an integer that + * is negative, zero or positive if {@code a} is lesser than, equal to, or greater + * than {@code b}. + * + *

The ordering imposed by this method differs from the one defined + * by the methods {@link #eq(Number, Number)}, {@link #lt(Number, Number)} + * and {@link #le(Number, Number)} in the treatment of NaNs + * and float zeros:

+ * + *
    + *
  • NaN is treated as equal to itself and greater than any other + * number, while {@code eq(a, b) == false} and {@code lt(a, b) == false} + * when {@code a} or {@code b} is NaN; + *
  • {@code -0.0} is considered to be lesser than {@code 0.0}, + * while {@code eq(-0.0, 0.0) == true} and {@code lt(-0.0, 0.0) == false}.
  • + *
+ * + *

The total ordering of {@code Number} objects imposed by this method + * is inconsistent with equals.

+ * + * @param a first argument, must not be {@code null} + * @param b second argument, must not be {@code null} + * @return a negative, zero or positive integer if the number {@code a} is lesser than, equal + * to, or greater than the number {@code b} + * @throws NullPointerException if {@code a} or {@code b} is {@code null} + */ + @Override + public int compare(Number a, Number b) { + if (lt(a, b)) { + return -1; + } else if (lt(b, a)) { + return 1; + } else { + // treat NaN as equal to itself and greater than any other number, + // and -0.0 as lesser than 0.0 + return Double.compare(a.doubleValue(), b.doubleValue()); + } + } + + } + + /** + * String ordering. + * + *

This is the (total) lexicographical ordering imposed by the method + * {@link String#compareTo(String)}.

+ */ + public static final class StringOrdering extends Ordering { + + private StringOrdering() { + // not to be instantiated by the outside world + } + + @Override + public boolean eq(ByteString a, ByteString b) { + return a.compareTo(b) == 0; + } + + @Override + public boolean lt(ByteString a, ByteString b) { + return a.compareTo(b) < 0; + } + + @Override + public boolean le(ByteString a, ByteString b) { + return a.compareTo(b) <= 0; + } + + @Override + public int compare(ByteString a, ByteString b) { + return a.compareTo(b); + } + + } + + private static class NumericObjectOrdering extends Ordering { + + @Override + public boolean eq(Object a, Object b) { + return NUMERIC.eq((Number) a, (Number) b); + } + + @Override + public boolean lt(Object a, Object b) { + return NUMERIC.lt((Number) a, (Number) b); + } + + @Override + public boolean le(Object a, Object b) { + return NUMERIC.le((Number) a, (Number) b); + } + + @Override + public int compare(Object a, Object b) { + return NUMERIC.compare((Number) a, (Number) b); + } + + } + + private static class StringObjectOrdering extends Ordering { + + @Override + public boolean eq(Object a, Object b) { + return STRING.eq(toByteString(a), toByteString(b)); + } + + @Override + public boolean lt(Object a, Object b) { + return STRING.lt(toByteString(a), toByteString(b)); + } + + @Override + public boolean le(Object a, Object b) { + return STRING.le(toByteString(a), toByteString(b)); + } + + @Override + public int compare(Object a, Object b) { + return STRING.compare(toByteString(a), toByteString(b)); + } + + } + +} diff --git a/luna/src/main/java/org/classdump/luna/PlainValueTypeNamer.java b/luna/src/main/java/org/classdump/luna/PlainValueTypeNamer.java new file mode 100644 index 00000000..54fa61f5 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/PlainValueTypeNamer.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import static org.classdump.luna.LuaFormat.TYPENAME_BOOLEAN; +import static org.classdump.luna.LuaFormat.TYPENAME_FUNCTION; +import static org.classdump.luna.LuaFormat.TYPENAME_NIL; +import static org.classdump.luna.LuaFormat.TYPENAME_NUMBER; +import static org.classdump.luna.LuaFormat.TYPENAME_STRING; +import static org.classdump.luna.LuaFormat.TYPENAME_TABLE; +import static org.classdump.luna.LuaFormat.TYPENAME_THREAD; +import static org.classdump.luna.LuaFormat.TYPENAME_USERDATA; + +/** + * A value type namer that uses a fixed mapping from types to type names. + * + *

This is a wrapper of the static method {@link #luaTypeToName(LuaType)}.

+ */ +public class PlainValueTypeNamer implements ValueTypeNamer { + + /** + * A static instance of this value type namer. + */ + public static final PlainValueTypeNamer INSTANCE = new PlainValueTypeNamer(); + + /** + * Returns the name (a string) of a given Lua type. + * + *

The result of this method is one of {@code "nil"}, {@code "boolean"}, {@code "number"}, + * {@code "string"}, {@code "table"}, {@code "function"}, {@code "userdata"} + * and {@code "thread"}.

+ * + * @param type the type, must not be {@code null} + * @return the name of {@code type} + * @throws NullPointerException if {@code type} is {@code null} + */ + public static ByteString luaTypeToName(LuaType type) { + switch (type) { + case NIL: + return TYPENAME_NIL; + case BOOLEAN: + return TYPENAME_BOOLEAN; + case NUMBER: + return TYPENAME_NUMBER; + case STRING: + return TYPENAME_STRING; + case TABLE: + return TYPENAME_TABLE; + case FUNCTION: + return TYPENAME_FUNCTION; + case USERDATA: + return TYPENAME_USERDATA; + case THREAD: + return TYPENAME_THREAD; + default: + throw new NullPointerException("Illegal type: " + type); + } + } + + @Override + public ByteString typeNameOf(Object instance) { + return luaTypeToName(LuaType.typeOf(instance)); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/StateContext.java b/luna/src/main/java/org/classdump/luna/StateContext.java new file mode 100644 index 00000000..03713c3e --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/StateContext.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A global context holding shared metatables and providing methods for instantiating new + * tables. + */ +public interface StateContext extends MetatableAccessor, TableFactory { + +} diff --git a/luna/src/main/java/org/classdump/luna/StringByteString.java b/luna/src/main/java/org/classdump/luna/StringByteString.java new file mode 100644 index 00000000..8321e6bb --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/StringByteString.java @@ -0,0 +1,249 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Objects; +import org.classdump.luna.util.ByteIterator; +import org.classdump.luna.util.CharsetEncoderByteIterator; + +/** + * A byte string backed by a {@link java.lang.String}. + */ +class StringByteString extends ByteString { + + private final String string; + private final Charset charset; + + private int byteHashCode; + private int byteLength; + + StringByteString(String s, Charset charset) { + this.string = Objects.requireNonNull(s); + this.charset = Objects.requireNonNull(charset); + if (!charset.canEncode()) { + throw new IllegalArgumentException("Charset cannot encode: " + charset.name()); + } + this.byteHashCode = 0; + this.byteLength = string.isEmpty() ? 0 : -1; + } + + private static void checkSubstringBounds(int start, int end, int len) { + if (start > end) { + throw new IndexOutOfBoundsException("start > end (" + start + " > " + end + ")"); + } else if (start < 0) { + throw new IndexOutOfBoundsException("start < 0 (" + start + " < 0)"); + } else if (end < 0) { + throw new IndexOutOfBoundsException("end < 0 (" + end + " < 0)"); + } else if (end > len) { + throw new IndexOutOfBoundsException("end > length (" + start + " > " + len + ")"); + } + } + + @Override + protected boolean equalsByteString(ByteString that) { + if (this.isEmpty() && that.isEmpty()) { + return true; + } + + // don't force hashCode computation, but use if already known + int thisHash = this.maybeHashCode(); + int thatHash = that.maybeHashCode(); + if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) { + return false; + } + + // don't force length computation, but use if already known + int thisLength = this.maybeLength(); + int thatLength = that.maybeLength(); + if (thisLength >= 0 && thatLength >= 0 && thisLength != thatLength) { + return false; + } + + // compare byte-by-byte + ByteIterator thisIterator = this.byteIterator(); + ByteIterator thatIterator = that.byteIterator(); + while (thisIterator.hasNext() && thatIterator.hasNext()) { + byte thisByte = thisIterator.nextByte(); + byte thatByte = thatIterator.nextByte(); + if (thisByte != thatByte) { + return false; + } + } + + return thisIterator.hasNext() == thatIterator.hasNext(); + } + + private int computeHashCode() { + int hc = 0; + + ByteIterator it = new CharsetEncoderByteIterator(string, charset); + while (it.hasNext()) { + hc = (hc * 31) + (it.nextByte() & 0xff); + } + + return hc; + } + + @Override + public int hashCode() { + int hc = byteHashCode; + + if (hc == 0 && !string.isEmpty()) { + hc = computeHashCode(); + + // update cached hashCode + byteHashCode = hc; + } + + return hc; + } + + @Override + int maybeHashCode() { + return byteHashCode; + } + + private int computeLength() { + int len = 0; + ByteIterator it = new CharsetEncoderByteIterator(string, charset); + while (it.hasNext()) { + it.nextByte(); + len++; + } + return len; + } + + @Override + public int length() { + int len = byteLength; + if (len < 0) { + len = computeLength(); + byteLength = len; + } + return len; + } + + @Override + int maybeLength() { + return byteLength; + } + + @Override + public boolean isEmpty() { + return string.isEmpty(); + } + + // must not escape, may be an array from the cache! + private byte[] toBytes() { + // TODO: cache the result + return string.getBytes(charset); + } + + @Override + public byte[] getBytes() { + byte[] bytes = toBytes(); + + // must make a defensive copy + return Arrays.copyOf(bytes, bytes.length); + } + + @Override + public byte byteAt(int index) { + if (index < 0) { + // don't even have to convert to bytes + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + + return toBytes()[index]; + } + + @Override + public ByteIterator byteIterator() { + return new CharsetEncoderByteIterator(string, charset); + } + + @Override + public ByteString substring(int start, int end) { + byte[] bytes = toBytes(); + checkSubstringBounds(start, end, bytes.length); + return new ArrayByteString(Arrays.copyOfRange(bytes, start, end)); + } + + @Override + public String toString() { + return string; + } + + @Override + public String decode(Charset charset) { + if (this.charset.equals(charset)) { + return string; + } else { + return super.decode(charset); + } + } + + @Override + public String toRawString() { + byte[] bytes = toBytes(); + char[] chars = new char[bytes.length]; + for (int i = 0; i < chars.length; i++) { + chars[i] = (char) (bytes[i] & 0xff); + } + return String.valueOf(chars); + } + + @Override + public void putTo(ByteBuffer buffer) { + // ByteBuffer cannot be directly extended: it's safe to use a possibly cached array + buffer.put(toBytes()); + } + + @Override + public void writeTo(OutputStream stream) throws IOException { + // OutputStream can be extended: pass a defensive copy + stream.write(getBytes()); + } + + @Override + public ByteString concat(ByteString other) { + if (other instanceof StringByteString) { + StringByteString that = (StringByteString) other; + if (this.charset.equals(that.charset)) { + // Caveat: preserves malformed characters and characters unmappable by charset + return ByteString.of(this.string.concat(that.string)); + } + } + + return super.concat(other); + } + + @Override + public boolean startsWith(byte b) { + if (string.isEmpty()) { + return false; + } + ByteIterator it = new CharsetEncoderByteIterator(string, charset); + return it.hasNext() && it.nextByte() == b; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/Table.java b/luna/src/main/java/org/classdump/luna/Table.java new file mode 100644 index 00000000..14f57f8a --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Table.java @@ -0,0 +1,330 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; +import org.classdump.luna.runtime.Dispatch; +import org.classdump.luna.runtime.ExecutionContext; + +/** + * An abstract class representing a Lua table. + * + *

Note on equality: according to §3.4.4 of the Lua Reference Manual, + * tables {@code a} and {@code b} are expected to be equal if and only if they are + * the same object. However, {@link Ordering#isRawEqual(Object, Object)} compares + * tables using {@link Object#equals(Object)}. Exercise caution when overriding + * {@code equals()}.

+ */ +public abstract class Table extends LuaObject { + + /** + * A weak set containing the references to tables this table is a metatable of. + * + * Let M be the metatable of a table T. Then T is a *basetable* of M. M may have multiple + * basetables; this is the set of all basetables of this table. + * + * According to LRM §2.5.2, when a table T has a metatable M, the value associated + * with the key "__mode" in M determines whether T has weak keys, values or both. This means + * that an update of M["__mode"] may trigger a change in the weakness status of all basetables + * of M. Therefore, each table must keep track of its basetables. + */ + private final Set basetables = Collections + .newSetFromMap(new WeakHashMap()); + /** + * The metatable of this table, may be {@code null}. + */ + private Table metatable; + + /** + * Retrieves the value associated with the given {@code key}, returning {@code null} + * when {@code key} has no value associated with it. + * + *

Implementations of this method must ensure that the Lua rules for valid + * table keys are honoured, e.g. by normalising keys using + * {@link Conversions#normaliseKey(Object)}.

+ * + *

This method provides raw access to the table. For non-raw access + * (i.e., handling the {@code __index} metamethod), use + * {@link Dispatch#index(ExecutionContext, Table, Object)}.

+ * + * @param key the key, may be {@code null} + * @return the value associated with {@code key}, or {@code null} when there is no value + * associated with {@code key} in this table + */ + public abstract Object rawget(Object key); + + /** + * Retrieves the value associated with the given integer {@code idx}, returning + * {@code null} when {@code idx} has no value associated with it. + * + *

This method must be functionally equivalent to {@link #rawget(Object)} with the + * corresponding boxed key. However, implementations of this method may optimise the retrieval + * in this case, since the type of the key is known at compile-time.

+ * + *

This method provides raw access to the table. For non-raw access + * (i.e., handling the {@code __index} metamethod), use + * {@link Dispatch#index(ExecutionContext, Table, long)}.

+ * + * @param idx the integer key + * @return the value associated with {@code idx}, or {@code null} when there is no value + * associated with {@code idx} in this table + */ + public Object rawget(long idx) { + return rawget(Long.valueOf(idx)); + } + + /** + * Sets the value associated with the key {@code key} to {@code value}. When {@code value} + * is {@code null}, removes {@code key} from the table. + * + *

When {@code key} is {@code null} (i.e., a nil) or a NaN, + * an {@link IllegalArgumentException} is thrown.

+ * + *

This method provides raw access to the table. For non-raw access + * (i.e., handling the {@code __newindex} metamethod), use + * {@link Dispatch#setindex(ExecutionContext, Table, Object, Object)}.

+ * + *

Implementation notes: Implementations of this method must ensure that + * the behaviour of this method conforms to the Lua semantics as delineated in the Lua + * Reference Manual. In particular:

+ *
    + *
  • float keys that have an integer value must be treated as integer keys + * (e.g. by using {@link Conversions#normaliseKey(Object)};
  • + *
  • updates of the value associated with the key {@code "__mode"} + * must call {@link #updateBasetableModes(Object, Object)}.
  • + *
+ * + * @param key the key, must not be {@code null} or NaN + * @param value the value to associate with {@code key}, may be {@code null} + * @throws IllegalArgumentException when {@code key} is {@code null} or a NaN + */ + public abstract void rawset(Object key, Object value); + + /** + * Sets the value associated with the integer key {@code idx} to {@code value}. + * When {@code value} is {@code null}, removes {@code idx} from the table. + * + *

This method must be functionally equivalent to {@link #rawset(Object, Object)} with the + * corresponding boxed key. However, implementations of this method may be more optimised + * than in the generic case, since the type of the key is known at compile-time.

+ * + *

This method provides raw access to the table. For non-raw access + * (i.e., handling the {@code __newindex} metamethod), use + * {@link Dispatch#setindex(ExecutionContext, Table, long, Object)}.

+ * + * @param idx the integer key + * @param value the value to associate with {@code idx}, may be {@code null} + */ + public void rawset(long idx, Object value) { + rawset(Long.valueOf(idx), value); + } + + /** + * If this table is a sequence, returns the length of this sequence. + * + *

According to §2.1 of the Lua Reference Manual, a sequence is

+ *
+ * a table where the set of all positive numeric keys is equal to {1..n} for some + * non-negative integer n, which is called the length of the sequence + *
+ * + *

Note that when this table is not a sequence, the return value of this method + * is undefined.

+ * + * @return the length of the sequence if this table is a sequence + */ + public long rawlen() { + long idx = 1; + + while (idx >= 0 && rawget(idx) != null) { + idx <<= 1; + } + + // if idx overflows (idx < 0), don't check rawget(idx) + + if (idx == 1) { + return 0; + } else { + // binary search in [idx >>> 1, idx] + + long min = idx >>> 1; + long max = idx; + + // invariant: (min > 0 && rawget(min) != null) && (max < 0 || rawget(max) == null) + + while (min + 1 != max) { + // works even if max == (1 << 63) + long mid = (min + max) >>> 1; + if (rawget(mid) == null) { + max = mid; + } else { + min = mid; + } + } + + // min + 1 == max; given the invariant, min is the result + + return min; + } + } + + /** + * Returns the initial key for iterating through the set of keys in this table. + * + *

Conceptually speaking, all keys in this table are totally ordered; this method + * returns the minimal key.

+ * + *

The key returned by this method, together with the subsequent calls + * to {@link #successorKeyOf(Object)} will visit all keys in this table exactly once + * (in an unspecified order):

+ *
+   *     Object k = table.initialIndex();
+   *     while (k != null) {
+   *         // process the key k
+   *         k = table.nextIndex(k);
+   *     }
+   *     // at this point, we visited all keys in table exactly once
+   * 
+ * + * @return an initial key for iteration through all keys in this table + */ + public abstract Object initialKey(); + + /** + * Returns the next key for iterating through the set of keys in this table. + * + *

Conceptually speaking, all keys in this table are totally ordered; this method + * returns the immediate successor of {@code key}, or {@code null} if {@code key} is + * the maximal key.

+ * + *

When no value is associated with the key {@code key} in this table, + * an {@link IllegalArgumentException} is thrown.

+ * + *

To retrieve the initial key for iterating through this table, use + * {@link #initialKey()}.

+ * + * @param key the key to get the immediate successor of, must not be {@code null} + * @return the immediate successor of {@code key} in this table + * @throws IllegalArgumentException when no value is associated with {@code key} in this table, or + * {@code key} is {@code null} + */ + public abstract Object successorKeyOf(Object key); + + @Override + public Table getMetatable() { + // not thread-safe! + return metatable; + } + + /** + * Sets the metatable of this table to {@code mt}. {@code mt} may be {@code null}: + * in that case, removes the metatable from this object. + * + *

Returns the metatable previously associated with this object (i.e., the metatable + * before the call of this method; possibly {@code null}).

+ * + *

This method maintains the weakness of this table by invoking + * {@link #setMode(boolean, boolean)} every time it is called.

+ * + * @param mt new metatable to attach to this object, may be {@code null} + * @return previous metatable associated with this object + */ + @Override + public Table setMetatable(Table mt) { + + // not thread-safe! + + Table old = metatable; + + if (old != null) { + // update the basetable mapping + old.basetables.remove(this); + } + + boolean wk = false; + boolean wv = false; + + if (mt != null) { + mt.basetables.add(this); + Object m = mt.rawget(Metatables.MT_MODE); + if (m instanceof String) { + String s = (String) m; + wk = s.indexOf('k') > -1; + wv = s.indexOf('v') > -1; + } + } + + metatable = mt; + setMode(wk, wv); + + return old; + } + + /** + * If {@code key} is equal to {@link Metatables#MT_MODE}, updates the weakness of the tables + * that use this table as their metatable (i.e., the basetables of this table). + * Otherwise, this method has no effect. + * + *

Whenever applicable, this method must be called by the implementations + * of {@link #rawset(Object, Object)} in order to ensure that assignments to + * the {@link Metatables#MT_MODE} key update the weakness mode of the tables that use this + * table as a metatable, as required by §2.5.2 of the Lua Reference Manual.

+ * + *

It is safe not to call this method when {@code key} is known not to be equal to + * {@link Metatables#MT_MODE}.

+ * + * @param key the key, may be {@code null} + * @param value the value, may be {@code null} + */ + protected void updateBasetableModes(Object key, Object value) { + // not thread-safe! + if (Metatables.MT_MODE.equals(key)) { + boolean wk = false; + boolean wv = false; + + if (value instanceof String) { + String s = (String) value; + wk = s.indexOf('k') > -1; + wv = s.indexOf('v') > -1; + } + + // update all tables + for (Table t : basetables) { + t.setMode(wk, wv); + } + } + } + + /** + * Sets the weakness of this table. If {@code weakKeys} is {@code true}, the table will have + * weak keys (otherwise, the table will have non-weak keys). Similarly, if {@code weakValues} + * is {@code true}, the table will have weak values (and non-weak values if {@code false}). + * + *

This method is not meant to be called directly: according to §2.5.2 of the Lua + * Reference Manual, the weakness of a table is fully determined by the value of the + * {@code "__mode"} field of its metatable. It is, however, meant to be called as part + * of maintenance of this requirement by {@link #setMetatable(Table)} and + * {@link #updateBasetableModes(Object, Object)}.

+ * + * @param weakKeys key mode ({@code true} for weak, {@code false} for non-weak keys) + * @param weakValues value mode ({@code true} for weak, {@code false} for non-weak values) + */ + protected abstract void setMode(boolean weakKeys, boolean weakValues); + +} diff --git a/luna/src/main/java/org/classdump/luna/TableFactory.java b/luna/src/main/java/org/classdump/luna/TableFactory.java new file mode 100644 index 00000000..34698a1e --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/TableFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A factory for {@link Table} instances. + */ +public interface TableFactory { + + /** + * Creates a new empty table. This is functionally equivalent to {@code newTable(0, 0)}. + * + * @return new empty table + * @see #newTable(int, int) + */ + Table newTable(); + + /** + * Creates a new empty table with the given initial capacities for its array and hash + * parts. + * + * @param array initial capacity for the array part + * @param hash initial capacity for the hash part + * @return new empty table + */ + Table newTable(int array, int hash); + +} diff --git a/luna/src/main/java/org/classdump/luna/Userdata.java b/luna/src/main/java/org/classdump/luna/Userdata.java new file mode 100644 index 00000000..c2b83d74 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Userdata.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * Full userdata. + * + *

Instances of this class may have a user value attached to them, + * accessible using the methods {@link #getUserValue()} and {@link #setUserValue(Object)}.

+ * + *

Note on equality: according to §3.4.4 of the Lua Reference Manual, + * userdata {@code a} and {@code b} are expected to be equal if and only if they are + * the same object. However, {@link Ordering#isRawEqual(Object, Object)} compares + * userdata using {@link Object#equals(Object)}. Exercise caution when overriding + * {@code equals()}.

+ */ +public abstract class Userdata extends LuaObject { + + /** + * Returns the user value attached to this full userdata. + * + * @return the user value attached to this full userdata + */ + public abstract T getUserValue(); + + /** + * Sets the user value attached to this full userdata to {@code value}, returning + * the old user value. + * + * @param value new user value, may be {@code null} + * @return old user value + */ + public abstract T setUserValue(T value); + +} diff --git a/luna/src/main/java/org/classdump/luna/ValueTypeNamer.java b/luna/src/main/java/org/classdump/luna/ValueTypeNamer.java new file mode 100644 index 00000000..962343fc --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/ValueTypeNamer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A mapping from values to a string representation of their type. + */ +public interface ValueTypeNamer { + + /** + * Returns the type name (a byte string) of the value {@code instance}. + * + * @param instance the object in question, may be {@code null} + * @return the type name of {@code instance} + */ + ByteString typeNameOf(Object instance); + +} diff --git a/luna/src/main/java/org/classdump/luna/Variable.java b/luna/src/main/java/org/classdump/luna/Variable.java new file mode 100644 index 00000000..217d8845 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/Variable.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna; + +/** + * A reified variable. + * + *

A variable is an object storing a single value of arbitrary type. When used as fields + * in function class instances, they serve a function equivalent to that of upvalues + * in PUC-Lua.

+ */ +public class Variable { + + private Object value; + + /** + * Creates a new variable instance with the given initial value. + * + * @param initialValue the initial value of the variable, may be {@code null} + */ + public Variable(Object initialValue) { + this.value = initialValue; + } + + /** + * Gets the value stored in this variable. + * + * @return the value of this variable (possibly {@code null}) + */ + public Object get() { + return value; + } + + /** + * Sets the value stored in this variable to {@code value}. + * + * @param value the new value of this variable, may be {@code null} + */ + public void set(Object value) { + this.value = value; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/CompiledModule.java b/luna/src/main/java/org/classdump/luna/compiler/CompiledModule.java new file mode 100644 index 00000000..7f9eac99 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/CompiledModule.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import java.util.Map; +import java.util.Objects; +import org.classdump.luna.load.CompiledChunk; +import org.classdump.luna.util.ByteVector; + +public class CompiledModule implements CompiledChunk { + + private final Map classMap; + private final String mainClassName; + + public CompiledModule(Map classMap, String mainClassName) { + this.classMap = Objects.requireNonNull(classMap); + this.mainClassName = Objects.requireNonNull(mainClassName); + + if (!classMap.containsKey(mainClassName)) { + throw new IllegalStateException("No main class in class map"); + } + } + + @Override + public Map classMap() { + return classMap; + } + + @Override + public String mainClassName() { + return mainClassName; + } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/CompilerChunkLoader.java b/luna/src/main/java/org/classdump/luna/compiler/CompilerChunkLoader.java new file mode 100644 index 00000000..52654d96 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/CompilerChunkLoader.java @@ -0,0 +1,207 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import java.util.Objects; +import org.classdump.luna.Variable; +import org.classdump.luna.load.ChunkClassLoader; +import org.classdump.luna.load.ChunkFactory; +import org.classdump.luna.load.ChunkLoader; +import org.classdump.luna.load.LoaderException; +import org.classdump.luna.parser.ParseException; +import org.classdump.luna.parser.Parser; +import org.classdump.luna.parser.TokenMgrError; +import org.classdump.luna.runtime.LuaFunction; + +/** + * A chunk loader that uses the {@linkplain LuaCompiler compiler} to convert Lua source + * text to Java classfiles, and loads these classfiles into the VM using a {@link ClassLoader}. + */ +public class CompilerChunkLoader implements ChunkLoader { + + private final ChunkClassLoader chunkClassLoader; + private final String rootClassPrefix; + private final LuaCompiler compiler; + + private int idx; + + CompilerChunkLoader(ClassLoader classLoader, LuaCompiler compiler, String rootClassPrefix) { + this.chunkClassLoader = new ChunkClassLoader(Objects.requireNonNull(classLoader)); + this.compiler = Objects.requireNonNull(compiler); + this.rootClassPrefix = Objects.requireNonNull(rootClassPrefix); + this.idx = 0; + } + + /** + * Returns a new instance of {@code CompilerChunkLoader} that uses the specified + * class loader {@code classLoader} to load classes it compiles using {@code compiler}, + * with every main chunk class having the class name {@code rootClassPrefix} followed + * by a monotonically-increasing integer suffix. + * + * @param classLoader the class loader used by this chunk loader, must not be {@code null} + * @param compiler the compiler instance used by this chunk loader, must not be {@code null} + * @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null} + * @return a new instance of {@code CompilerChunkLoader} + * @throws NullPointerException if {@code classLoader}, {@code compiler} or {@code + * rootClassPrefix} is {@code null} + */ + public static CompilerChunkLoader of(ClassLoader classLoader, LuaCompiler compiler, + String rootClassPrefix) { + return new CompilerChunkLoader(classLoader, compiler, rootClassPrefix); + } + + /** + * Returns a new instance of {@code CompilerChunkLoader} that uses the specified + * class loader {@code classLoader} to load classes it compiles using a new instance + * of the Lua compiler with the settings {@code compilerSettings}, + * with every main chunk class having the class name {@code rootClassPrefix} followed + * by a monotonically-increasing integer suffix. + * + * @param classLoader the class loader used by this chunk loader, must not be {@code null} + * @param compilerSettings the compiler settings used to instantiate the compiler, must not be + * {@code null} + * @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null} + * @return a new instance of {@code CompilerChunkLoader} + * @throws NullPointerException if {@code classLoader}, {@code compilerSettings} or {@code + * rootClassPrefix} is {@code null} + */ + public static CompilerChunkLoader of(ClassLoader classLoader, CompilerSettings compilerSettings, + String rootClassPrefix) { + return new CompilerChunkLoader(classLoader, new LuaCompiler(compilerSettings), rootClassPrefix); + } + + /** + * Returns a new instance of {@code CompilerChunkLoader} that uses the specified + * class loader {@code classLoader} to load classes it compiles using a compiler + * instantiated with {@linkplain CompilerSettings#defaultSettings() default settings}, + * with every main chunk class having the class name {@code rootClassPrefix} followed + * by a monotonically-increasing integer suffix. + * + * @param classLoader the class loader used by this chunk loader, must not be {@code null} + * @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null} + * @return a new instance of {@code CompilerChunkLoader} + * @throws NullPointerException if {@code classLoader} or {@code rootClassPrefix} is {@code null} + */ + public static CompilerChunkLoader of(ClassLoader classLoader, String rootClassPrefix) { + return of(classLoader, CompilerSettings.defaultSettings(), rootClassPrefix); + } + + /** + * Returns a new instance of {@code CompilerChunkLoader} that uses the class loader + * that loaded the {@code CompilerChunkLoader} class to load classes it compiles using + * {@code compiler}, with every main chunk class having the class name {@code rootClassPrefix} + * followed by a monotonically-increasing integer suffix. + * + * @param compiler the compiler instance used by this chunk loader, must not be {@code null} + * @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null} + * @return a new instance of {@code CompilerChunkLoader} + * @throws NullPointerException {@code compiler} or {@code rootClassPrefix} is {@code null} + */ + public static CompilerChunkLoader of(LuaCompiler compiler, String rootClassPrefix) { + return of(CompilerChunkLoader.class.getClassLoader(), compiler, rootClassPrefix); + } + + /** + * Returns a new instance of {@code CompilerChunkLoader} that uses the class loader + * that loaded the {@code CompilerChunkLoader} class to load classes it compiles using + * a new instance of the Lua compiler with the settings {@code compilerSettings}, + * with every main chunk class having the class name {@code rootClassPrefix} followed + * by a monotonically-increasing integer suffix. + * + * @param compilerSettings the compiler settings used to instantiate the compiler, must not be + * {@code null} + * @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null} + * @return a new instance of {@code CompilerChunkLoader} + * @throws NullPointerException if {@code compilerSettings} or {@code rootClassPrefix} is {@code + * null} + */ + public static CompilerChunkLoader of(CompilerSettings compilerSettings, String rootClassPrefix) { + return of(new LuaCompiler(compilerSettings), rootClassPrefix); + } + + /** + * Returns a new instance of {@code CompilerChunkLoader} that uses the class loader + * that loaded the {@code CompilerChunkLoader} class to load classes it compiles using + * a compiler instantiated with + * {@linkplain CompilerSettings#defaultSettings() default settings}, + * with every main chunk class having the class name {@code rootClassPrefix} followed + * by a monotonically-increasing integer suffix. + * + * @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null} + * @return a new instance of {@code CompilerChunkLoader} + * @throws NullPointerException if {@code rootClassPrefix} is {@code null} + */ + public static CompilerChunkLoader of(String rootClassPrefix) { + return of(CompilerSettings.defaultSettings(), rootClassPrefix); + } + + public ChunkClassLoader getChunkClassLoader() { + return chunkClassLoader; + } + + @Override + public LuaFunction loadTextChunk(Variable env, String chunkName, + String sourceText) throws LoaderException { + Objects.requireNonNull(env); + Objects.requireNonNull(chunkName); + Objects.requireNonNull(sourceText); + + return compileTextChunk(chunkName, sourceText).newInstance(env); + } + + @Override + public ChunkFactory compileTextChunk(String chunkName, String sourceText) throws LoaderException { + Objects.requireNonNull(chunkName); + Objects.requireNonNull(sourceText); + + synchronized (this) { + try { + String rootClassName = rootClassPrefix + (idx++); + + CompiledModule result = compiler.compile(sourceText, chunkName, rootClassName); + + String mainClassName = chunkClassLoader.install(result); + //noinspection unchecked + return new ChunkFactory( + (Class>) chunkClassLoader + .loadClass(mainClassName), chunkName); + } catch (TokenMgrError ex) { + String msg = ex.getMessage(); + int line = 0; // TODO + boolean partial = msg != null && msg + .contains("Encountered: "); // TODO: is there really no better way? + throw new LoaderException(ex, chunkName, line, partial); + } catch (ParseException ex) { + boolean partial = ex.currentToken != null + && ex.currentToken.next != null + && ex.currentToken.next.kind == Parser.EOF; + int line = ex.currentToken != null + ? ex.currentToken.beginLine + : 0; + throw new LoaderException(ex, chunkName, line, partial); + } catch (ClassNotFoundException e) { + throw new LoaderException(e, chunkName, 0, false); + } + } + } + +// @Override +// public LuaFunction loadBinaryChunk(Variable env, String chunkName, byte[] bytes, int offset, int len) throws LoaderException { +// throw new UnsupportedOperationException(); // TODO +// } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/CompilerSettings.java b/luna/src/main/java/org/classdump/luna/compiler/CompilerSettings.java new file mode 100644 index 00000000..bb0c1a04 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/CompilerSettings.java @@ -0,0 +1,291 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import java.util.Objects; + +/** + * An immutable class encapsulating the settings of the compilation of Lua to Java bytecode + * by {@link LuaCompiler}. + * + *

The settings are the following

+ *
    + *
  • CPU accounting ({@link CPUAccountingMode}): controls whether and how + * the functions generated by the compiler account for number of ticks spent in execution + * and pause when their time slice has expired;
  • + *
  • const folding (boolean): when {@code true}, constants are folded at compile + * time (note that this does not have an influence on the number of ticks counted);
  • + *
  • const caching (boolean): when {@code true}, boxed numeric constants are stored + * as static fields rather than being instantiated (and boxed) at execution time;
  • + *
  • node size limit (int): when positive, long functions are split up into smaller + * Java methods (each containing at most the specified number of IR nodes); otherwise, + * a single method containing the entire function code is generated. Java class files + * impose a strict limit of 64 kB per method: this setting allows the compilation + * of arbitrarily-long Lua functions.
  • + *
+ * + *

To obtain the settings with sensible defaults, use {@link CompilerSettings#defaultSettings()}. + * To obtain the default settings with CPU accounting disabled, + * use {@link CompilerSettings#defaultNoAccountingSettings()}. + *

+ */ +public final class CompilerSettings { + + /** + * The default CPU accounting mode. + */ + public static final CPUAccountingMode DEFAULT_CPU_ACCOUNTING_MODE = CPUAccountingMode.IN_EVERY_BASIC_BLOCK; + /** + * The default const folding mode. + */ + public static final boolean DEFAULT_CONST_FOLDING_MODE = true; + /** + * The default const caching mode. + */ + public static final boolean DEFAULT_CONST_CACHING_MODE = true; + /** + * The default byte string mode. + */ + public static final boolean DEFAULT_BYTE_STRING_MODE = true; + /** + * The default method size limit. + */ + public static final int DEFAULT_NODE_SIZE_LIMIT = 2000; + private final CPUAccountingMode cpuAccountingMode; + private final boolean constFolding; + private final boolean constCaching; + private final boolean byteStrings; + private final int nodeSizeLimit; + CompilerSettings( + CPUAccountingMode cpuAccountingMode, + boolean constFolding, + boolean constCaching, + boolean byteStrings, + int nodeSizeLimit) { + + this.cpuAccountingMode = Objects.requireNonNull(cpuAccountingMode); + this.constFolding = constFolding; + this.constCaching = constCaching; + this.byteStrings = byteStrings; + this.nodeSizeLimit = nodeSizeLimit; + } + + /** + * Returns the compiler settings with the given parameters. + * + *

When {@code nodeSizeLimit} is non-positive, no chunking of the body method + * will be performed.

+ * + * @param cpuAccountingMode CPU accounting mode, must not be {@code null} + * @param constFolding const folding mode + * @param constCaching const caching mode + * @param byteStrings byte string mode + * @param nodeSizeLimit node size limit + * @return the corresponding compiler settings + * @throws NullPointerException if {@code cpuAccountingMode} is {@code null} + */ + public static CompilerSettings of( + CPUAccountingMode cpuAccountingMode, + boolean constFolding, + boolean constCaching, + boolean byteStrings, + int nodeSizeLimit) { + + return new CompilerSettings( + cpuAccountingMode, constFolding, constCaching, byteStrings, nodeSizeLimit); + } + + /** + * Returns the default compiler settings. + * + * @return the default compiler settings + */ + public static CompilerSettings defaultSettings() { + return CompilerSettings.of( + DEFAULT_CPU_ACCOUNTING_MODE, + DEFAULT_CONST_FOLDING_MODE, + DEFAULT_CONST_CACHING_MODE, + DEFAULT_BYTE_STRING_MODE, + DEFAULT_NODE_SIZE_LIMIT); + } + + /** + * Returns the default compiler settings without CPU accounting. + * + * @return the default compiler settings without CPU accounting + */ + public static CompilerSettings defaultNoAccountingSettings() { + return defaultSettings().withCPUAccountingMode(CPUAccountingMode.NO_CPU_ACCOUNTING); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CompilerSettings that = (CompilerSettings) o; + + return this.cpuAccountingMode == that.cpuAccountingMode + && this.constFolding == that.constFolding + && this.constCaching == that.constCaching + && this.byteStrings == that.byteStrings + && this.nodeSizeLimit == that.nodeSizeLimit; + } + + @Override + public int hashCode() { + int result = cpuAccountingMode.hashCode(); + result = 31 * result + (constFolding ? 1 : 0); + result = 31 * result + (constCaching ? 1 : 0); + result = 31 * result + (byteStrings ? 1 : 0); + result = 31 * result + nodeSizeLimit; + return result; + } + + /** + * Returns the CPU accounting mode. + * + * @return the CPU accounting mode + */ + public CPUAccountingMode cpuAccountingMode() { + return cpuAccountingMode; + } + + /** + * Returns the const folding mode. + * + * @return the const folding mode + */ + public boolean constFolding() { + return constFolding; + } + + /** + * Returns the const caching mode. + * + * @return the const caching mode + */ + public boolean constCaching() { + return constCaching; + } + + public boolean byteStrings() { + return byteStrings; + } + + /** + * Returns the node size limit. + * + * @return the node size limit + */ + public int nodeSizeLimit() { + return nodeSizeLimit; + } + + /** + * Returns compiler settings derived from this compiler settings by updating + * the CPU accounting mode to {@code mode}. + * + * @param mode new CPU accounting mode, must not be {@code null} + * @return settings derived from {@code this} by updating the CPU accounting mode to {@code mode} + * @throws NullPointerException if {@code mode} is {@code null} + */ + public CompilerSettings withCPUAccountingMode(CPUAccountingMode mode) { + return mode != this.cpuAccountingMode + ? new CompilerSettings(mode, constFolding, constCaching, byteStrings, nodeSizeLimit) + : this; + } + + /** + * Returns compiler settings derived from this compiler settings by updating + * the const folding mode to {@code mode}. + * + * @param mode new const folding mode + * @return settings derived from {@code this} by updating the const folding mode to {@code mode} + */ + public CompilerSettings withConstFolding(boolean mode) { + return mode != this.constFolding + ? new CompilerSettings(cpuAccountingMode, mode, constCaching, byteStrings, nodeSizeLimit) + : this; + } + + /** + * Returns compiler settings derived from this compiler settings by updating + * the const caching mode to {@code mode}. + * + * @param mode new const caching mode + * @return settings derived from {@code this} by updating the const caching mode to {@code mode} + */ + public CompilerSettings withConstCaching(boolean mode) { + return mode != this.constCaching + ? new CompilerSettings(cpuAccountingMode, constFolding, mode, byteStrings, nodeSizeLimit) + : this; + } + + /** + * Returns compiler settings derived from this compiler settings by updating + * the byte string mode to {@code mode}. + * + * @param mode new byte string mode + * @return settings derived from {@code this} by updating the byte string mode to {@code mode} + */ + public CompilerSettings withByteStrings(boolean mode) { + return mode != this.byteStrings + ? new CompilerSettings(cpuAccountingMode, constFolding, constCaching, mode, nodeSizeLimit) + : this; + } + + /** + * Returns compiler settings derived from this compiler settings by updating + * the node size limit to {@code limit}. + * + * @param limit new node size limit + * @return settings derived from {@code this} by updating the node size limit to {@code limit} + */ + public CompilerSettings withNodeSizeLimit(int limit) { + return limit != this.nodeSizeLimit + ? new CompilerSettings(cpuAccountingMode, constFolding, constCaching, byteStrings, limit) + : this; + } + + /** + * CPU accounting mode. + */ + public enum CPUAccountingMode { + + /** + * Do not do CPU accounting. + */ + NO_CPU_ACCOUNTING, + + /** + * Check CPU time usage at the beginning of every basic block. + * + *

At the beginning of every basic block, update the ticks by invoking + * {@link org.classdump.luna.runtime.ExecutionContext#registerTicks(int)} + * and potentially pause by invoking + * {@link org.classdump.luna.runtime.ExecutionContext#pauseIfRequested()}.

+ */ + IN_EVERY_BASIC_BLOCK + + } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/FunctionId.java b/luna/src/main/java/org/classdump/luna/compiler/FunctionId.java new file mode 100644 index 00000000..864feb7d --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/FunctionId.java @@ -0,0 +1,133 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import org.classdump.luna.compiler.gen.ClassNameTranslator; +import org.classdump.luna.util.Check; + +public class FunctionId { + + public static final Comparator LEXICOGRAPHIC_COMPARATOR = new Comparator() { + @Override + public int compare(FunctionId a, FunctionId b) { + int la = a.indices.size(); + int lb = b.indices.size(); + + int len = Math.min(la, lb); + for (int i = 0; i < len; i++) { + int ai = a.indices.get(i); + int bi = b.indices.get(i); + + int diff = ai - bi; + if (diff != 0) { + return diff; + } + } + + return la - lb; + } + }; + private final static FunctionId ROOT = new FunctionId(Collections.emptyList()); + private final List indices; + + private FunctionId(List indices) { + this.indices = Objects.requireNonNull(indices); + } + + public static FunctionId root() { + return ROOT; + } + + public static FunctionId fromIndices(List indices) { + Objects.requireNonNull(indices); // FIXME: make a copy? + return indices.isEmpty() ? root() : new FunctionId(indices); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FunctionId that = (FunctionId) o; + + return this.indices.equals(that.indices); + } + + @Override + public int hashCode() { + return indices.hashCode(); + } + + @Override + public String toString() { + StringBuilder bld = new StringBuilder(); + Iterator it = indices.iterator(); + bld.append("/"); + while (it.hasNext()) { + int i = it.next(); + bld.append(i); + if (it.hasNext()) { + bld.append("/"); + } + } + return bld.toString(); + } + + public List indices() { + return indices; + } + + public boolean isRoot() { + return indices.isEmpty(); + } + + public FunctionId child(int index) { + Check.nonNegative(index); + List childIndices = new ArrayList<>(indices.size() + 1); + childIndices.addAll(indices); + childIndices.add(index); + return new FunctionId(Collections.unmodifiableList(childIndices)); + } + + public FunctionId parent() { + if (isRoot()) { + return null; + } else { + List subIndices = indices.subList(0, indices.size() - 1); + return new FunctionId(subIndices); + } + } + + public String toClassName(ClassNameTranslator tr) { + Objects.requireNonNull(tr); + for (Integer index : indices) { + tr = tr.child(index); + } + return tr.className(); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/IRFunc.java b/luna/src/main/java/org/classdump/luna/compiler/IRFunc.java new file mode 100644 index 00000000..7e978a9c --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/IRFunc.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import java.util.List; +import java.util.Objects; +import org.classdump.luna.compiler.ir.Code; +import org.classdump.luna.compiler.ir.UpVar; +import org.classdump.luna.compiler.ir.Var; + +public class IRFunc { + + private final FunctionId id; + private final List params; + private final boolean vararg; + private final List upvals; + private final Code code; + + public IRFunc(FunctionId id, List params, boolean vararg, List upvals, Code code) { + this.id = Objects.requireNonNull(id); + this.params = Objects.requireNonNull(params); + this.vararg = vararg; + this.upvals = Objects.requireNonNull(upvals); + this.code = Objects.requireNonNull(code); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IRFunc that = (IRFunc) o; + return id.equals(that.id) + && params.equals(that.params) + && upvals.equals(that.upvals) + && code.equals(that.code); + } + + @Override + public int hashCode() { + return Objects.hash(id, params, code); + } + + public FunctionId id() { + return id; + } + + public List params() { + return params; + } + + public boolean isVararg() { + return vararg; + } + + public List upvals() { + return upvals; + } + + public Code code() { + return code; + } + + public IRFunc update(Code code) { + if (this.code.equals(code)) { + return this; + } else { + return new IRFunc(id, params, vararg, upvals, code); + } + } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/IRTranslator.java b/luna/src/main/java/org/classdump/luna/compiler/IRTranslator.java new file mode 100644 index 00000000..4e3cb498 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/IRTranslator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import org.classdump.luna.parser.ast.Chunk; + +public class IRTranslator { + + public static Module translate(Chunk chunk) { + ModuleBuilder moduleBuilder = new ModuleBuilder(); + IRTranslatorTransformer translator = new IRTranslatorTransformer(moduleBuilder); + translator.transform(chunk); + return moduleBuilder.build(); + } + +} diff --git a/luna/src/main/java/org/classdump/luna/compiler/IRTranslatorTransformer.java b/luna/src/main/java/org/classdump/luna/compiler/IRTranslatorTransformer.java new file mode 100644 index 00000000..39fc4756 --- /dev/null +++ b/luna/src/main/java/org/classdump/luna/compiler/IRTranslatorTransformer.java @@ -0,0 +1,1159 @@ +/* + * Copyright 2016 Miroslav Janíček + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.classdump.luna.compiler; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.classdump.luna.compiler.ir.AbstractVal; +import org.classdump.luna.compiler.ir.AbstractVar; +import org.classdump.luna.compiler.ir.BinOp; +import org.classdump.luna.compiler.ir.Branch; +import org.classdump.luna.compiler.ir.Call; +import org.classdump.luna.compiler.ir.Closure; +import org.classdump.luna.compiler.ir.CodeBuilder; +import org.classdump.luna.compiler.ir.Jmp; +import org.classdump.luna.compiler.ir.Label; +import org.classdump.luna.compiler.ir.LoadConst; +import org.classdump.luna.compiler.ir.MultiGet; +import org.classdump.luna.compiler.ir.MultiVal; +import org.classdump.luna.compiler.ir.PhiLoad; +import org.classdump.luna.compiler.ir.PhiStore; +import org.classdump.luna.compiler.ir.PhiVal; +import org.classdump.luna.compiler.ir.RegProvider; +import org.classdump.luna.compiler.ir.Ret; +import org.classdump.luna.compiler.ir.TCall; +import org.classdump.luna.compiler.ir.TabGet; +import org.classdump.luna.compiler.ir.TabNew; +import org.classdump.luna.compiler.ir.TabRawAppendMulti; +import org.classdump.luna.compiler.ir.TabRawSet; +import org.classdump.luna.compiler.ir.TabRawSetInt; +import org.classdump.luna.compiler.ir.TabSet; +import org.classdump.luna.compiler.ir.ToNumber; +import org.classdump.luna.compiler.ir.UnOp; +import org.classdump.luna.compiler.ir.UpLoad; +import org.classdump.luna.compiler.ir.UpStore; +import org.classdump.luna.compiler.ir.UpVar; +import org.classdump.luna.compiler.ir.VList; +import org.classdump.luna.compiler.ir.Val; +import org.classdump.luna.compiler.ir.Var; +import org.classdump.luna.compiler.ir.VarInit; +import org.classdump.luna.compiler.ir.VarLoad; +import org.classdump.luna.compiler.ir.VarStore; +import org.classdump.luna.compiler.ir.Vararg; +import org.classdump.luna.parser.analysis.FunctionVarInfo; +import org.classdump.luna.parser.analysis.ResolvedLabel; +import org.classdump.luna.parser.analysis.ResolvedVariable; +import org.classdump.luna.parser.analysis.VarMapping; +import org.classdump.luna.parser.analysis.Variable; +import org.classdump.luna.parser.ast.AssignStatement; +import org.classdump.luna.parser.ast.BinaryOperationExpr; +import org.classdump.luna.parser.ast.Block; +import org.classdump.luna.parser.ast.BodyStatement; +import org.classdump.luna.parser.ast.BooleanLiteral; +import org.classdump.luna.parser.ast.BreakStatement; +import org.classdump.luna.parser.ast.CallExpr; +import org.classdump.luna.parser.ast.CallStatement; +import org.classdump.luna.parser.ast.Chunk; +import org.classdump.luna.parser.ast.ConditionalBlock; +import org.classdump.luna.parser.ast.Expr; +import org.classdump.luna.parser.ast.FunctionDefExpr; +import org.classdump.luna.parser.ast.GenericForStatement; +import org.classdump.luna.parser.ast.GotoStatement; +import org.classdump.luna.parser.ast.IfStatement; +import org.classdump.luna.parser.ast.IndexExpr; +import org.classdump.luna.parser.ast.LValueExpr; +import org.classdump.luna.parser.ast.LabelStatement; +import org.classdump.luna.parser.ast.Literal; +import org.classdump.luna.parser.ast.LiteralExpr; +import org.classdump.luna.parser.ast.LocalDeclStatement; +import org.classdump.luna.parser.ast.MultiExpr; +import org.classdump.luna.parser.ast.NilLiteral; +import org.classdump.luna.parser.ast.Numeral; +import org.classdump.luna.parser.ast.NumericForStatement; +import org.classdump.luna.parser.ast.Operator; +import org.classdump.luna.parser.ast.ParenExpr; +import org.classdump.luna.parser.ast.RepeatUntilStatement; +import org.classdump.luna.parser.ast.ReturnStatement; +import org.classdump.luna.parser.ast.StringLiteral; +import org.classdump.luna.parser.ast.TableConstructorExpr; +import org.classdump.luna.parser.ast.Transformer; +import org.classdump.luna.parser.ast.UnaryOperationExpr; +import org.classdump.luna.parser.ast.VarExpr; +import org.classdump.luna.parser.ast.VarargsExpr; +import org.classdump.luna.parser.ast.WhileStatement; +import org.classdump.luna.parser.ast.util.AttributeUtils; + +class IRTranslatorTransformer extends Transformer { + + private final ModuleBuilder moduleBuilder; + + private final FunctionId id; + + private final CodeBuilder insns; + + private final RegProvider provider; + private final Deque vals; + private final Deque multis; + + private final Deque