Compare commits
No commits in common. "d0d79d2f07b29a54ae6629ea660c372dc447119b" and "15617f699837ade99ecdc6ab1c7148e80265ac79" have entirely different histories.
d0d79d2f07
...
15617f6998
@ -1,66 +0,0 @@
|
||||
# Legacy Digest Functions (`lyng.legacy_digest`)
|
||||
|
||||
> ⚠️ **Security warning:** The functions in this module use cryptographically broken
|
||||
> algorithms. Do **not** use them for passwords, digital signatures, integrity
|
||||
> verification against adversarial tampering, or any other security-sensitive
|
||||
> purpose. They exist solely for compatibility with legacy protocols and file
|
||||
> formats that require specific hash values.
|
||||
|
||||
Import when you need to produce a SHA-1 digest for an existing protocol or format:
|
||||
|
||||
```lyng
|
||||
import lyng.legacy_digest
|
||||
```
|
||||
|
||||
## `LegacyDigest` Object
|
||||
|
||||
### `sha1(data): String`
|
||||
|
||||
Computes the SHA-1 digest of `data` and returns it as a 40-character lowercase
|
||||
hex string.
|
||||
|
||||
`data` can be:
|
||||
|
||||
| Type | Behaviour |
|
||||
|----------|----------------------------------------|
|
||||
| `String` | Encoded as UTF-8, then hashed |
|
||||
| `Buffer` | Raw bytes hashed directly |
|
||||
| anything | Falls back to `toString()` then UTF-8 |
|
||||
|
||||
```lyng
|
||||
import lyng.legacy_digest
|
||||
|
||||
// String input
|
||||
val h = LegacyDigest.sha1("abc")
|
||||
assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", h)
|
||||
|
||||
// Empty string
|
||||
assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", LegacyDigest.sha1(""))
|
||||
```
|
||||
|
||||
```lyng
|
||||
import lyng.legacy_digest
|
||||
import lyng.buffer
|
||||
|
||||
// Buffer input (raw bytes)
|
||||
val buf = Buffer.decodeHex("616263") // 0x61 0x62 0x63 = "abc"
|
||||
assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", LegacyDigest.sha1(buf))
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Pure Kotlin/KMP — no native libraries or extra dependencies.
|
||||
- Follows FIPS 180-4.
|
||||
- The output is always lowercase hex, never uppercase or binary.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use `lyng.legacy_digest` only when an external system you cannot change requires
|
||||
a SHA-1 value, for example:
|
||||
|
||||
- old git-style content addresses
|
||||
- some OAuth 1.0 / HMAC-SHA1 signature schemes
|
||||
- legacy file checksums defined in published specs
|
||||
|
||||
For any new design choose a current hash function (SHA-256 or better) once
|
||||
Lyng adds a `lyng.digest` module.
|
||||
@ -75,9 +75,6 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
|
||||
- `Matrix`, `Vector`, `matrix(rows)`, `vector(values)`, dense linear algebra, inversion, solving, and matrix slicing with `m[row, col]`.
|
||||
- `import lyng.buffer`
|
||||
- `Buffer`, `MutableBuffer`.
|
||||
- `import lyng.legacy_digest`
|
||||
- `LegacyDigest.sha1(data): String` — SHA-1 hex digest; `data` may be `String` (UTF-8) or `Buffer` (raw bytes).
|
||||
- ⚠️ Cryptographically broken. Use only for legacy protocol / file-format compatibility.
|
||||
- `import lyng.serialization`
|
||||
- `Lynon` serialization utilities.
|
||||
- `import lyng.time`
|
||||
|
||||
@ -164,31 +164,6 @@ println(z.exp())
|
||||
|
||||
See [Complex](Complex.md).
|
||||
|
||||
### Legacy Digest Module (`lyng.legacy_digest`)
|
||||
|
||||
For situations where an external protocol or file format requires a SHA-1 value,
|
||||
Lyng now ships a `lyng.legacy_digest` module backed by a pure Kotlin/KMP
|
||||
implementation with no extra dependencies.
|
||||
|
||||
> ⚠️ SHA-1 is cryptographically broken. Use only for legacy-compatibility work.
|
||||
|
||||
```lyng
|
||||
import lyng.legacy_digest
|
||||
|
||||
val hex = LegacyDigest.sha1("abc")
|
||||
// → "a9993e364706816aba3e25717850c26c9cd0d89d"
|
||||
|
||||
// Also accepts raw bytes:
|
||||
import lyng.buffer
|
||||
val buf = Buffer.decodeHex("616263")
|
||||
assertEquals(hex, LegacyDigest.sha1(buf))
|
||||
```
|
||||
|
||||
The name `LegacyDigest` is intentional: it signals that these algorithms belong
|
||||
to a compatibility layer, not to a current security toolkit.
|
||||
|
||||
See [LegacyDigest](LegacyDigest.md).
|
||||
|
||||
### Binary Operator Interop Registry
|
||||
Lyng now provides a general mechanism for mixed binary operators through `lyng.operators`.
|
||||
|
||||
|
||||
@ -31,7 +31,6 @@ sealed class CodeContext {
|
||||
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||
val declaredMembers = mutableSetOf<String>()
|
||||
val declaredMethodNames = mutableSetOf<String>()
|
||||
val classScopeMembers = mutableSetOf<String>()
|
||||
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||
val memberFieldIds = mutableMapOf<String, Int>()
|
||||
|
||||
@ -456,7 +456,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun predeclareClassMembers(target: MutableSet<String>, overrides: MutableMap<String, Boolean>, methodNames: MutableSet<String>? = null) {
|
||||
private fun predeclareClassMembers(target: MutableSet<String>, overrides: MutableMap<String, Boolean>) {
|
||||
val saved = cc.savePos()
|
||||
var depth = 0
|
||||
val modifiers = setOf(
|
||||
@ -478,22 +478,18 @@ class Compiler(
|
||||
Token.Type.RBRACE -> if (depth == 0) break else depth--
|
||||
Token.Type.ID -> if (depth == 0) {
|
||||
var sawOverride = false
|
||||
var sawStatic = false
|
||||
while (t.type == Token.Type.ID && t.value in modifiers) {
|
||||
if (t.value == "override") sawOverride = true
|
||||
if (t.value == "static") sawStatic = true
|
||||
t = nextNonWs()
|
||||
}
|
||||
when (t.value) {
|
||||
"fun", "fn", "val", "var" -> {
|
||||
val isMethod = t.value == "fun" || t.value == "fn"
|
||||
val nameToken = nextNonWs()
|
||||
if (nameToken.type == Token.Type.ID) {
|
||||
val afterName = cc.peekNextNonWhitespace()
|
||||
if (afterName.type != Token.Type.DOT) {
|
||||
target.add(nameToken.value)
|
||||
overrides[nameToken.value] = sawOverride
|
||||
if (isMethod && !sawStatic) methodNames?.add(nameToken.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2787,7 +2783,7 @@ class Compiler(
|
||||
Token.Type.LPAREN -> {
|
||||
cc.next()
|
||||
if (shouldTreatAsClassScopeCall(left, next.value)) {
|
||||
val parsed = parseArgs(null, implicitItTypeForMemberLambda(left, next.value))
|
||||
val parsed = parseArgs(null, implicitItTypeNameForMemberLambda(left, next.value))
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
isCall = true
|
||||
@ -2804,7 +2800,7 @@ class Compiler(
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val parsed = parseArgs(receiverType, implicitItTypeForMemberLambda(left, next.value))
|
||||
val parsed = parseArgs(receiverType, implicitItTypeNameForMemberLambda(left, next.value))
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
if (left is LocalVarRef && left.name == "scope") {
|
||||
@ -2883,7 +2879,7 @@ class Compiler(
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val itType = implicitItTypeForMemberLambda(left, next.value)
|
||||
val itType = implicitItTypeNameForMemberLambda(left, next.value)
|
||||
val lambda = parseLambdaExpression(receiverType, implicitItType = itType)
|
||||
val argPos = next.pos
|
||||
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
|
||||
@ -3291,7 +3287,7 @@ class Compiler(
|
||||
private suspend fun parseLambdaExpression(
|
||||
expectedReceiverType: String? = null,
|
||||
wrapAsExtensionCallable: Boolean = false,
|
||||
implicitItType: TypeDecl? = null
|
||||
implicitItType: String? = null
|
||||
): ObjRef {
|
||||
// lambda args are different:
|
||||
val startPos = cc.currentPos()
|
||||
@ -3308,15 +3304,14 @@ class Compiler(
|
||||
val slotParamNames = if (hasImplicitIt) paramNames + "it" else paramNames
|
||||
val paramSlotPlan = buildParamSlotPlan(slotParamNames)
|
||||
if (implicitItType != null) {
|
||||
val cls = resolveTypeDeclObjClass(implicitItType)
|
||||
val cls = resolveClassByName(implicitItType)
|
||||
?: resolveTypeDeclObjClass(TypeDecl.Simple(implicitItType, false))
|
||||
val itSlot = paramSlotPlan.slots["it"]?.index
|
||||
if (itSlot != null) {
|
||||
if (cls != null) {
|
||||
if (cls != null && itSlot != null) {
|
||||
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||
paramTypeMap[itSlot] = cls
|
||||
}
|
||||
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||
paramTypeDeclMap[itSlot] = implicitItType
|
||||
paramTypeDeclMap[itSlot] = TypeDecl.Simple(implicitItType, false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4750,13 +4745,6 @@ class Compiler(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun classMethodReturnTypeDecl(typeName: String?, name: String): TypeDecl? {
|
||||
if (typeName == null) return null
|
||||
classMethodReturnTypeDeclByName[typeName]?.get(name)?.let { return it }
|
||||
classMethodReturnTypeByName[typeName]?.get(name)?.let { return TypeDecl.Simple(it.className, false) }
|
||||
return null
|
||||
}
|
||||
|
||||
private fun classMethodReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
|
||||
if (targetClass == null) return null
|
||||
if (targetClass == ObjDynamic.type) return ObjDynamic.type
|
||||
@ -4870,26 +4858,6 @@ class Compiler(
|
||||
classMethodReturnTypeDecl(targetClass, "getAt")
|
||||
}
|
||||
is MethodCallRef -> methodReturnTypeDeclByRef[ref] ?: inferMethodCallReturnTypeDecl(ref)
|
||||
is ImplicitThisMethodCallRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val receiverDecl = typeName?.let { TypeDecl.Simple(it, false) }
|
||||
inferMethodCallReturnTypeDecl(ref.methodName(), receiverDecl, ref.arguments())
|
||||
?: classMethodReturnTypeDecl(typeName, ref.methodName())
|
||||
?: typeName?.let { resolveClassByName(it) }?.let { classMethodReturnTypeDecl(it, ref.methodName()) }
|
||||
}
|
||||
is ThisMethodSlotCallRef -> {
|
||||
val typeName = currentImplicitThisTypeName()
|
||||
val receiverDecl = typeName?.let { TypeDecl.Simple(it, false) }
|
||||
inferMethodCallReturnTypeDecl(ref.methodName(), receiverDecl, ref.arguments())
|
||||
?: classMethodReturnTypeDecl(typeName, ref.methodName())
|
||||
?: typeName?.let { resolveClassByName(it) }?.let { classMethodReturnTypeDecl(it, ref.methodName()) }
|
||||
}
|
||||
is QualifiedThisMethodSlotCallRef -> {
|
||||
val receiverDecl = TypeDecl.Simple(ref.receiverTypeName(), false)
|
||||
inferMethodCallReturnTypeDecl(ref.methodName(), receiverDecl, ref.arguments())
|
||||
?: classMethodReturnTypeDecl(ref.receiverTypeName(), ref.methodName())
|
||||
?: resolveClassByName(ref.receiverTypeName())?.let { classMethodReturnTypeDecl(it, ref.methodName()) }
|
||||
}
|
||||
is CallRef -> callReturnTypeDeclByRef[ref] ?: inferCallReturnTypeDecl(ref)
|
||||
is BinaryOpRef -> inferBinaryOpReturnTypeDecl(ref)
|
||||
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
|
||||
@ -5154,7 +5122,6 @@ class Compiler(
|
||||
private fun inferMethodCallReturnTypeDecl(ref: MethodCallRef): TypeDecl? {
|
||||
methodReturnTypeDeclByRef[ref]?.let { return it }
|
||||
val inferred = inferMethodCallReturnTypeDecl(ref.name, resolveReceiverTypeDecl(ref.receiver), ref.args)
|
||||
?: classMethodReturnTypeDecl(resolveReceiverClassForMember(ref.receiver), ref.name)
|
||||
if (inferred != null) {
|
||||
methodReturnTypeDeclByRef[ref] = inferred
|
||||
}
|
||||
@ -5305,17 +5272,21 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun implicitItTypeForMemberLambda(receiver: ObjRef, memberName: String): TypeDecl? {
|
||||
private fun implicitItTypeNameForMemberLambda(receiver: ObjRef, memberName: String): String? {
|
||||
if (memberName == "fill" && isListTypeRef(receiver)) {
|
||||
return TypeDecl.Simple("Int", false)
|
||||
return "Int"
|
||||
}
|
||||
if (memberName == "let" || memberName == "also") {
|
||||
val receiverType = inferTypeDeclFromRef(receiver) ?: resolveReceiverTypeDecl(receiver)
|
||||
return receiverType?.let { makeTypeDeclNonNullable(it) }
|
||||
return inferReceiverTypeFromRef(receiver)
|
||||
}
|
||||
return when (memberName) {
|
||||
val typeDecl = when (memberName) {
|
||||
"forEach", "map" -> inferIterableElementTypeDecl(receiver)
|
||||
else -> null
|
||||
} ?: return null
|
||||
return when (typeDecl) {
|
||||
is TypeDecl.Simple -> typeDecl.name.substringAfterLast('.')
|
||||
is TypeDecl.Generic -> typeDecl.name.substringAfterLast('.')
|
||||
else -> resolveTypeDeclObjClass(typeDecl)?.className
|
||||
}
|
||||
}
|
||||
|
||||
@ -6322,7 +6293,7 @@ class Compiler(
|
||||
*/
|
||||
private suspend fun parseArgs(
|
||||
expectedTailBlockReceiver: String? = null,
|
||||
implicitItType: TypeDecl? = null
|
||||
implicitItType: String? = null
|
||||
): Pair<List<ParsedArgument>, Boolean> {
|
||||
|
||||
val args = mutableListOf<ParsedArgument>()
|
||||
@ -7761,7 +7732,7 @@ class Compiler(
|
||||
classCtx?.let { ctx ->
|
||||
val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() }
|
||||
predeclareClassScopeMembers(qualifiedName, ctx.classScopeMembers, callableMembers)
|
||||
predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides, ctx.declaredMethodNames)
|
||||
predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides)
|
||||
val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null
|
||||
if (existingExternInfo != null) {
|
||||
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
|
||||
@ -7810,13 +7781,6 @@ class Compiler(
|
||||
ctx.memberFieldIds[param.name] = ctx.nextFieldId++
|
||||
}
|
||||
}
|
||||
// Pre-assign method IDs for all declared methods so forward
|
||||
// references within the class body resolve correctly.
|
||||
for (method in ctx.declaredMethodNames) {
|
||||
if (!ctx.memberMethodIds.containsKey(method)) {
|
||||
ctx.memberMethodIds[method] = ctx.nextMethodId++
|
||||
}
|
||||
}
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
packageName = packageName,
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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 net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* Pure Kotlin/KMP implementation of legacy hash functions.
|
||||
*
|
||||
* SHA-1 is cryptographically broken and must not be used for security-sensitive
|
||||
* purposes (password hashing, digital signatures, etc.). It is retained here
|
||||
* solely for compatibility with legacy protocols and file formats that require it.
|
||||
*/
|
||||
internal object LegacyDigest {
|
||||
|
||||
/**
|
||||
* Compute the SHA-1 digest of [input] and return it as a lowercase hex string.
|
||||
*
|
||||
* SHA-1 is **cryptographically insecure**. Use only for protocol compatibility.
|
||||
*/
|
||||
fun sha1Hex(input: ByteArray): String {
|
||||
val digest = sha1(input)
|
||||
return buildString(40) {
|
||||
for (b in digest) {
|
||||
val v = b.toInt() and 0xFF
|
||||
if (v < 16) append('0')
|
||||
append(v.toString(16))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sha1(input: ByteArray): ByteArray {
|
||||
// Initial hash values
|
||||
var h0 = 0x67452301
|
||||
var h1 = 0xEFCDAB89.toInt()
|
||||
var h2 = 0x98BADCFE.toInt()
|
||||
var h3 = 0x10325476
|
||||
var h4 = 0xC3D2E1F0.toInt()
|
||||
|
||||
// Pre-processing: pad message to a multiple of 512 bits (64 bytes).
|
||||
// Append 0x80, then zeros, then the 64-bit big-endian bit-length.
|
||||
val msgLen = input.size
|
||||
val bitLen = msgLen.toLong() * 8L
|
||||
// Minimum padding: 1 byte (0x80) + 8 bytes (length) = 9 bytes.
|
||||
// Total length must be ≡ 0 (mod 64).
|
||||
val padded = run {
|
||||
val totalLen = ((msgLen + 1 + 8 + 63) / 64) * 64
|
||||
ByteArray(totalLen).also { buf ->
|
||||
input.copyInto(buf)
|
||||
buf[msgLen] = 0x80.toByte()
|
||||
// Big-endian 64-bit bit-length in the last 8 bytes
|
||||
for (i in 0..7) {
|
||||
buf[totalLen - 8 + i] = ((bitLen ushr (56 - i * 8)) and 0xFF).toByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val w = IntArray(80)
|
||||
|
||||
var blockStart = 0
|
||||
while (blockStart < padded.size) {
|
||||
// Build the 80-word message schedule
|
||||
for (i in 0..15) {
|
||||
val off = blockStart + i * 4
|
||||
w[i] = ((padded[off].toInt() and 0xFF) shl 24) or
|
||||
((padded[off + 1].toInt() and 0xFF) shl 16) or
|
||||
((padded[off + 2].toInt() and 0xFF) shl 8) or
|
||||
(padded[off + 3].toInt() and 0xFF)
|
||||
}
|
||||
for (i in 16..79) {
|
||||
val x = w[i - 3] xor w[i - 8] xor w[i - 14] xor w[i - 16]
|
||||
w[i] = (x shl 1) or (x ushr 31) // ROTL-1
|
||||
}
|
||||
|
||||
var a = h0; var b = h1; var c = h2; var d = h3; var e = h4
|
||||
|
||||
for (t in 0..19) {
|
||||
val f = (b and c) or (b.inv() and d)
|
||||
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0x5A827999 + w[t]
|
||||
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
|
||||
}
|
||||
for (t in 20..39) {
|
||||
val f = b xor c xor d
|
||||
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0x6ED9EBA1 + w[t]
|
||||
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
|
||||
}
|
||||
for (t in 40..59) {
|
||||
val f = (b and c) or (b and d) or (c and d)
|
||||
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0x8F1BBCDC.toInt() + w[t]
|
||||
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
|
||||
}
|
||||
for (t in 60..79) {
|
||||
val f = b xor c xor d
|
||||
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0xCA62C1D6.toInt() + w[t]
|
||||
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
|
||||
}
|
||||
|
||||
h0 += a; h1 += b; h2 += c; h3 += d; h4 += e
|
||||
blockStart += 64
|
||||
}
|
||||
|
||||
return ByteArray(20).also { out ->
|
||||
fun putInt(off: Int, v: Int) {
|
||||
out[off] = (v ushr 24).toByte()
|
||||
out[off + 1] = (v ushr 16).toByte()
|
||||
out[off + 2] = (v ushr 8).toByte()
|
||||
out[off + 3] = v.toByte()
|
||||
}
|
||||
putInt(0, h0); putInt(4, h1); putInt(8, h2); putInt(12, h3); putInt(16, h4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,7 +29,6 @@ import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.stdlib_included.complexLyng
|
||||
import net.sergeych.lyng.stdlib_included.decimalLyng
|
||||
import net.sergeych.lyng.stdlib_included.legacyDigestLyng
|
||||
import net.sergeych.lyng.stdlib_included.matrixLyng
|
||||
import net.sergeych.lyng.stdlib_included.observableLyng
|
||||
import net.sergeych.lyng.stdlib_included.operatorsLyng
|
||||
@ -897,19 +896,6 @@ class Script(
|
||||
module.eval(Source("lyng.complex", complexLyng))
|
||||
ObjComplexSupport.bindTo(module)
|
||||
}
|
||||
addPackage("lyng.legacy_digest") { module ->
|
||||
module.eval(Source("lyng.legacy_digest", legacyDigestLyng))
|
||||
module.bindObject("LegacyDigest") {
|
||||
addFun("sha1") {
|
||||
val data = requiredArg<Obj>(0)
|
||||
val bytes = when (data) {
|
||||
is ObjBuffer -> data.byteArray.toByteArray()
|
||||
else -> data.toString().encodeToByteArray()
|
||||
}
|
||||
ObjString(LegacyDigest.sha1Hex(bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
addPackage("lyng.buffer") {
|
||||
it.addConstDoc(
|
||||
name = "Buffer",
|
||||
|
||||
@ -926,16 +926,11 @@ open class ObjClass(
|
||||
candidate.methodId
|
||||
} else null
|
||||
}
|
||||
val id = methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
||||
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
||||
methodIdMap[name] = nextMethodId
|
||||
nextMethodId++
|
||||
methodIdMap[name]!!
|
||||
}
|
||||
// Register the resolved ID so subsequent assignMethodId calls (e.g. for static
|
||||
// methods) don't reuse the same numeric slot for a different member.
|
||||
methodIdMap[name] = id
|
||||
if (id >= nextMethodId) nextMethodId = id + 1
|
||||
id
|
||||
} else {
|
||||
methodId
|
||||
}
|
||||
|
||||
@ -1102,119 +1102,4 @@ class OOTest {
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExtendingObjectWithExternals2() = runTest {
|
||||
val s = EvalSession()
|
||||
s.eval("""
|
||||
import lyng.serialization
|
||||
object Storage {
|
||||
extern val spaceUsed: Int
|
||||
extern val spaceAvailable: Int
|
||||
|
||||
/*
|
||||
Return packed binary data or null
|
||||
*/
|
||||
extern fun getPacked(key: String): Buffer?
|
||||
|
||||
/*
|
||||
Upsert packed binary data
|
||||
*/
|
||||
extern fun putPacked(key: String,value: Buffer)
|
||||
|
||||
/*
|
||||
Delete data.
|
||||
@return true if data were actually deleted, false means
|
||||
there were no data for the key.
|
||||
*/
|
||||
extern fun delete(key: String): Bool
|
||||
|
||||
override fun putAt(key: String,value: Object) {
|
||||
putPacked(key, Lynon.encode(value).toBuffer())
|
||||
}
|
||||
|
||||
override fun getAt(key: String): Object? =
|
||||
getPacked(key)?.let { Lynon.decode(it.toBitInput()) }
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
val scope = s.getScope() as ModuleScope
|
||||
scope.bindObject("Storage") {
|
||||
init { _ ->
|
||||
data = mutableMapOf<String, ObjBuffer>()
|
||||
}
|
||||
addVal("spaceUsed") {
|
||||
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
|
||||
ObjInt(storage.values.sumOf { it.size }.toLong())
|
||||
}
|
||||
addVal("spaceAvailable") {
|
||||
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
|
||||
val capacity = 1_024
|
||||
ObjInt((capacity - storage.values.sumOf { it.size }).toLong())
|
||||
}
|
||||
addFun("getPacked") {
|
||||
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
|
||||
val key = (args.list[0] as ObjString).value
|
||||
storage[key] ?: ObjNull
|
||||
}
|
||||
addFun("putPacked") {
|
||||
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
|
||||
val key = (args.list[0] as ObjString).value
|
||||
val value = args.list[1] as ObjBuffer
|
||||
storage[key] = value
|
||||
ObjVoid
|
||||
}
|
||||
addFun("delete") {
|
||||
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
|
||||
val key = (args.list[0] as ObjString).value
|
||||
ObjBool(storage.remove(key) != null)
|
||||
}
|
||||
}
|
||||
s.eval("""
|
||||
assertEquals(0, Storage.spaceUsed)
|
||||
assertEquals(1024, Storage.spaceAvailable)
|
||||
val missing: String? = Storage["missing"]
|
||||
assertEquals(null, missing)
|
||||
|
||||
Storage["name"] = "alice"
|
||||
Storage["count"] = 42
|
||||
|
||||
val name: String? = Storage["name"]
|
||||
val count: Int? = Storage["count"]
|
||||
assertEquals("alice", name)
|
||||
assertEquals(42, count)
|
||||
assert(Storage.spaceUsed > 0)
|
||||
assert(Storage.spaceAvailable < 1024)
|
||||
|
||||
val wrappedName: String? = Storage.getAt("name")
|
||||
assertEquals("alice", wrappedName)
|
||||
Storage.putAt("flag", true)
|
||||
val flag: Bool? = Storage["flag"]
|
||||
assertEquals(true, flag)
|
||||
|
||||
assert(Storage.delete("name"))
|
||||
val deletedName: String? = Storage["name"]
|
||||
assertEquals(null, deletedName)
|
||||
assert(!Storage.delete("name"))
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForwardSymbolsUsageMustBeAllowed() = runTest {
|
||||
eval("""
|
||||
class Foo(x) {
|
||||
fn fn2() {
|
||||
fn1()
|
||||
println("fn2")
|
||||
}
|
||||
fn fn1() {
|
||||
println("fn1")
|
||||
}
|
||||
}
|
||||
|
||||
val foo = Foo(33)
|
||||
foo.fn2()
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -109,19 +109,6 @@ class ScriptTest {
|
||||
assertTrue(res is ObjString && res.value.isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNullableLetKeepsReceiverMemberType() = runTest {
|
||||
Script.newScope().eval(
|
||||
"""
|
||||
import lyng.serialization
|
||||
|
||||
val packed: Buffer? = Lynon.encode("alice").toBuffer()
|
||||
val decoded = packed?.let { Lynon.decode(it.toBitInput()) }
|
||||
assertEquals("alice", decoded)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoInfiniteRecursionOnUnknownInNestedClosure() = runTest {
|
||||
val scope = Script.newScope()
|
||||
|
||||
@ -111,22 +111,4 @@ class ComplexModuleTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOperatorSlotDispatch() = runTest {
|
||||
val scope = Script.newScope()
|
||||
scope.eval(
|
||||
"""
|
||||
import lyng.complex
|
||||
val a = Complex(1.0, 2.0)
|
||||
val b = Complex(3.0, -1.0)
|
||||
val product = a * b
|
||||
assertEquals(5.0, product.re)
|
||||
assertEquals(5.0, product.im)
|
||||
val sum = a + Complex(0.0, 0.0)
|
||||
assertEquals(1.0, sum.re)
|
||||
assertEquals(2.0, sum.im)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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 net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class LegacyDigestTest {
|
||||
|
||||
// --- Kotlin-level unit tests for the SHA-1 implementation ---
|
||||
|
||||
@Test
|
||||
fun sha1KotlinEmptyString() {
|
||||
// SHA-1("") = da39a3ee5e6b4b0d3255bfef95601890afd80709
|
||||
assertEquals(
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
LegacyDigest.sha1Hex(ByteArray(0))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sha1KotlinAbc() {
|
||||
// SHA-1("abc") = a9993e364706816aba3e25717850c26c9cd0d89d
|
||||
assertEquals(
|
||||
"a9993e364706816aba3e25717850c26c9cd0d89d",
|
||||
LegacyDigest.sha1Hex("abc".encodeToByteArray())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sha1KotlinLongerMessage() {
|
||||
// SHA-1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
|
||||
// = 84983e441c3bd26ebaae4aa1f95129e5e54670f1
|
||||
val msg = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
|
||||
assertEquals(
|
||||
"84983e441c3bd26ebaae4aa1f95129e5e54670f1",
|
||||
LegacyDigest.sha1Hex(msg.encodeToByteArray())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sha1KotlinExactlyOneBlock() {
|
||||
// "The quick brown fox jumps over the lazy dog"
|
||||
// SHA-1 = 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
|
||||
val msg = "The quick brown fox jumps over the lazy dog"
|
||||
assertEquals(
|
||||
"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
|
||||
LegacyDigest.sha1Hex(msg.encodeToByteArray())
|
||||
)
|
||||
}
|
||||
|
||||
// --- Lyng-level integration tests ---
|
||||
|
||||
@Test
|
||||
fun sha1LyngStringInput() = runTest {
|
||||
eval(
|
||||
"""
|
||||
import lyng.legacy_digest
|
||||
assertEquals(
|
||||
"a9993e364706816aba3e25717850c26c9cd0d89d",
|
||||
LegacyDigest.sha1("abc")
|
||||
)
|
||||
assertEquals(
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
LegacyDigest.sha1("")
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sha1LyngBufferInput() = runTest {
|
||||
eval(
|
||||
"""
|
||||
import lyng.legacy_digest
|
||||
import lyng.buffer
|
||||
val buf = Buffer.decodeHex("616263") // "abc" in hex
|
||||
assertEquals(
|
||||
"a9993e364706816aba3e25717850c26c9cd0d89d",
|
||||
LegacyDigest.sha1(buf)
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sha1LyngReturnType() = runTest {
|
||||
eval(
|
||||
"""
|
||||
import lyng.legacy_digest
|
||||
val h = LegacyDigest.sha1("hello")
|
||||
assert(h is String)
|
||||
assertEquals(40, h.length)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package lyng.legacy_digest
|
||||
|
||||
/*
|
||||
Legacy cryptographic digest functions.
|
||||
|
||||
⚠️ WARNING: The functions in this module use algorithms that are
|
||||
CRYPTOGRAPHICALLY BROKEN and must NOT be used for any security-sensitive
|
||||
purpose, including:
|
||||
- Password hashing
|
||||
- Digital signatures or MACs
|
||||
- Integrity verification against adversarial tampering
|
||||
|
||||
Use only for interoperability with legacy protocols, file formats, or
|
||||
external systems that require these specific hash values.
|
||||
|
||||
For secure hashing, use SHA-256 or better (not yet available in this stdlib).
|
||||
*/
|
||||
|
||||
extern object LegacyDigest {
|
||||
/*
|
||||
Compute the SHA-1 digest of `data` and return it as a lowercase hex string.
|
||||
|
||||
`data` may be a String (hashed as UTF-8) or a Buffer (hashed as raw bytes).
|
||||
|
||||
⚠️ SHA-1 is cryptographically broken. Use only for protocol compatibility.
|
||||
|
||||
Example:
|
||||
import lyng.legacy_digest
|
||||
val digest = LegacyDigest.sha1("hello world")
|
||||
// "2aae6c69a64d7c8b96eacbbe0ced5df20f3a8e89" (note: known SHA-1 of "hello world")
|
||||
*/
|
||||
extern fun sha1(data): String
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user