diff --git a/Main.lean b/Main.lean index 2cd9ce2..d2b49a4 100644 --- a/Main.lean +++ b/Main.lean @@ -18,8 +18,22 @@ def refineJudgmentFromValue (value : Value) (judgment : Judgment) : Judgment := | _ => judgment | _ => judgment +def renderValueForJudgment (value : Value) (judgment : Judgment) : String := + match judgment.ty, value with + | .decimal, .number (.exact q) => renderDecimal q + | .int, .number (.exact q) => + if q.den = 1 then toString q.num else renderValue value + | _, _ => renderValue value + +def renderNumericStability (value : Value) (judgment : Judgment) : String := + match judgment.ty with + | .number => + s!" [rational={rationalStabilityOfValue value}, decimal={decimalStabilityOfValue value}]" + | _ => "" + def renderResult (value : Value) (judgment : Judgment) : String := - s!"{renderValue value} : {renderJudgment (refineJudgmentFromValue value judgment)}" + let refined := refineJudgmentFromValue value judgment + s!"{renderValueForJudgment value refined} : {renderJudgment refined}{renderNumericStability value refined}" unsafe def checkExprsWith (tyEnv : TyEnv) (env : Env) (exprs : List Expr) : IO (Except String (List (Value × Judgment))) := do let rec loop (pending : List Expr) (acc : List (Value × Judgment)) : IO (Except String (List (Value × Judgment))) := do @@ -28,7 +42,7 @@ unsafe def checkExprsWith (tyEnv : TyEnv) (env : Env) (exprs : List Expr) : IO ( | expr :: rest => match inferType tyEnv expr with | .error err => - pure (.error s!"type error: {reprStr err}") + pure (.error s!"type error: {renderTypeError err}") | .ok judgment => match (← (eval env expr).toIO') with | .ok value => @@ -86,7 +100,7 @@ unsafe def repl (tyEnv : TyEnv := []) (env : Env := []) : IO UInt32 := do | .bind name value => match inferType tyEnv value with | .error err => - IO.eprintln s!"type error: {reprStr err}" + IO.eprintln s!"type error: {renderTypeError err}" repl tyEnv env | .ok judgment => match (← (eval env value).toIO') with diff --git a/Mlang/Http.lean b/Mlang/Http.lean index f95c573..a2fd317 100644 --- a/Mlang/Http.lean +++ b/Mlang/Http.lean @@ -1,5 +1,6 @@ namespace Mlang.Http @[extern "mlang_http_get"] opaque httpGet (url : @& String) : IO String +@[extern "mlang_aturi_parse_string"] opaque parseAturi (source : @& String) : IO String end Mlang.Http diff --git a/Mlang/Interpreter.lean b/Mlang/Interpreter.lean index b634b56..0ce46cd 100644 --- a/Mlang/Interpreter.lean +++ b/Mlang/Interpreter.lean @@ -7,6 +7,154 @@ namespace Mlang open Nickel abbrev Name := String +structure Rational where + num : Int + den : Nat + deriving Repr, Inhabited, BEq + +abbrev Decimal := Rational + +mutual + inductive Generator where + | exactRatio : Rational → Generator + | exactDecimal : Decimal → Generator + | rationalRefiner : LazyReal → Generator + | decimalRefiner : LazyReal → Generator + | piChudnovsky : Generator + | sqrt : Decimal → Generator + | add : Generator → Generator → Generator + | sub : Generator → Generator → Generator + | mul : Generator → Generator → Generator + | div : Generator → Generator → Generator + deriving Repr, Inhabited, BEq + + inductive LazyReal where + | exact : Decimal → LazyReal + | ratioProcess : Rational → LazyReal + | decimalProcess : Decimal → LazyReal + | pi : LazyReal + | sqrt : Decimal → LazyReal + | add : LazyReal → LazyReal → LazyReal + | sub : LazyReal → LazyReal → LazyReal + | mul : LazyReal → LazyReal → LazyReal + | div : LazyReal → LazyReal → LazyReal + | generated : Generator → LazyReal + deriving Repr, Inhabited, BEq +end + +def normalizeDecimalParts (negative : Bool) (whole frac : String) : String := + let wholeChars := whole.toList.dropWhile (· = '0') + let whole' := if wholeChars.isEmpty then "0" else String.ofList wholeChars + let fracChars := frac.toList.reverse.dropWhile (· = '0') |>.reverse + let frac' := if fracChars.isEmpty then "0" else String.ofList fracChars + let sign := if negative && !(whole' = "0" && frac' = "0") then "-" else "" + sign ++ whole' ++ "." ++ frac' + +def parseDecimalParts (repr : String) : Bool × String × String := + let chars := repr.toList + let (negative, chars) := + match chars with + | '-' :: rest => (true, rest) + | _ => (false, chars) + let rec split (acc : List Char) : List Char → List Char × List Char + | [] => (acc.reverse, []) + | '.' :: rest => (acc.reverse, rest) + | c :: rest => split (c :: acc) rest + let (wholeChars, fracChars) := split [] chars + (negative, String.ofList wholeChars, String.ofList fracChars) + +def pow10 : Nat → Int + | 0 => 1 + | n + 1 => 10 * pow10 n + +def powNat (base : Nat) : Nat → Nat + | 0 => 1 + | n + 1 => base * powNat base n + +def mkRational (num : Int) (den : Nat) : Rational := + if den = 0 then + { num := 0, den := 1 } + else if num = 0 then + { num := 0, den := 1 } + else + let g := Nat.gcd num.natAbs den + { num := num / Int.ofNat g, den := den / g } + +def parseDecimal (repr : String) : Decimal := + let (negative, whole, frac) := parseDecimalParts repr + let digits := whole ++ frac + let magnitude := String.toInt? digits |>.getD 0 + let signed := if negative then -magnitude else magnitude + mkRational signed (powNat 10 frac.length) + +def renderDecimalScaled (scaled : Int) (scale : Nat) : String := + let negative := scaled < 0 + let mag := scaled.natAbs + let digits := toString mag + if scale = 0 then + normalizeDecimalParts negative digits "0" + else if digits.length <= scale then + let zeros := String.ofList (List.replicate (scale - digits.length + 1) '0') + let padded := zeros ++ digits + let wholeLen := padded.length - scale + normalizeDecimalParts negative (padded.take wholeLen |>.toString) (padded.drop wholeLen |>.toString) + else + let wholeLen := digits.length - scale + normalizeDecimalParts negative (digits.take wholeLen |>.toString) (digits.drop wholeLen |>.toString) + +def decimalOfInt (n : Int) : Decimal := + mkRational n 1 + +def addDecimals (lhs rhs : Decimal) : Decimal := + mkRational (lhs.num * Int.ofNat rhs.den + rhs.num * Int.ofNat lhs.den) (lhs.den * rhs.den) + +def subDecimals (lhs rhs : Decimal) : Decimal := + mkRational (lhs.num * Int.ofNat rhs.den - rhs.num * Int.ofNat lhs.den) (lhs.den * rhs.den) + +def mulDecimals (lhs rhs : Decimal) : Decimal := + mkRational (lhs.num * rhs.num) (lhs.den * rhs.den) + +def rationalEq (lhs rhs : Decimal) : Bool := + lhs.num == rhs.num && lhs.den == rhs.den + +partial def countFactor (factor : Nat) (n : Nat) : Nat := + let rec loop (count current : Nat) := + if current = 0 then count + else if current % factor = 0 then loop (count + 1) (current / factor) + else count + loop 0 n + +partial def stripFactor (factor : Nat) (n : Nat) : Nat := + let rec loop (current : Nat) := + if current = 0 then 0 + else if current % factor = 0 then loop (current / factor) + else current + loop n + +def renderDecimal (d : Decimal) : String := + if d.den = 1 then + renderDecimalScaled d.num 0 + else + let twos := countFactor 2 d.den + let withoutTwos := stripFactor 2 d.den + let fives := countFactor 5 withoutTwos + let rest := stripFactor 5 withoutTwos + if rest = 1 then + let scale := max twos fives + let mult2 := powNat 2 (scale - twos) + let mult5 := powNat 5 (scale - fives) + let scaledNum := d.num * Int.ofNat (mult2 * mult5) + renderDecimalScaled scaledNum scale + else + s!"{d.num}/{d.den}" + +def isTerminatingDecimal (q : Rational) : Bool := + if q.den = 1 then + true + else + let withoutTwos := stripFactor 2 q.den + let withoutFives := stripFactor 5 withoutTwos + withoutFives = 1 inductive Effect where | error : Effect @@ -36,11 +184,18 @@ def Effects.render : Effects → String "{" ++ String.intercalate ", " names ++ "}" inductive Ty where + | number : Ty | int : Ty + | decimal : Ty + | rational : Ty + | lazyReal : Ty + | stability : Ty | bool : Ty | string : Ty | data : Ty | dataInt : Ty + | dataDecimal : Ty + | dataRational : Ty | dataBool : Ty | dataString : Ty | dataNull : Ty @@ -56,6 +211,7 @@ inductive Data where | null : Data | bool : Bool → Data | int : Int → Data + | decimal : Decimal → Data | string : String → Data | array : List Data → Data | record : List (String × Data) → Data @@ -63,6 +219,7 @@ inductive Data where inductive Expr where | int : Int → Expr + | decimal : Decimal → Expr | bool : Bool → Expr | string : String → Expr | null : Expr @@ -82,8 +239,14 @@ inductive Expr where | app : Expr → Expr → Expr deriving Repr, Inhabited +inductive Numeric where + | exact : Rational → Numeric + | composite : LazyReal → Numeric + deriving Repr, Inhabited, BEq + inductive Value where - | int : Int → Value + | number : Numeric → Value + | stability : String → Value | bool : Bool → Value | string : String → Value | data : Data → Value @@ -96,20 +259,57 @@ inductive Value where | builtinReadFileNickel : Value | builtinParseNickel : Value | builtinParseJson : Value + | builtinGetAturi : Value | builtinDataGet : Value - | builtinDataGetField : Data → Value + | builtinDataGetField : Value → Value | builtinDataAt : Value - | builtinDataAtIndex : Data → Value + | builtinDataAtIndex : Value → Value | builtinDataAsInt : Value + | builtinDataAsDecimal : Value + | builtinRationalToInt : Value + | builtinRationalToDecimal : Value | builtinDataAsString : Value | builtinDataAsBool : Value | builtinDataToJson : Value | builtinDataToNickel : Value + | builtinSqrt : Value + | builtinProcess : Value + | builtinSynthesize : Value + | builtinFormula : Value + | builtinApprox : Value + | builtinApproxStep : Value → Value + | builtinStabilize : Value + | builtinStabilizeStep : Value → Value + | builtinBounds : Value + | builtinBoundsStep : Value → Value + | builtinRationalStability : Value + | builtinDecimalStability : Value | closure : Name → Expr → List (Name × Value) → Value deriving Repr, Inhabited abbrev Env := List (Name × Value) +inductive Observation where + | asRational : Observation + | asDecimal : Observation + | asInt : Observation + | rationalStability : Observation + | decimalStability : Observation + | getField : String → Observation + | atIndex : Nat → Observation + | approx : Nat → Observation + | bounds : Nat → Observation + deriving Repr, Inhabited + +inductive ObservationResult where + | rational : Rational → ObservationResult + | decimal : Decimal → ObservationResult + | int : Int → ObservationResult + | stability : String → ObservationResult + | data : Data → ObservationResult + | bounds : Decimal → Decimal → ObservationResult + deriving Repr, Inhabited + inductive RuntimeError where | unboundVariable : Name → RuntimeError | typeError : String → RuntimeError @@ -119,15 +319,365 @@ inductive RuntimeError where abbrev EvalM := EIO RuntimeError +def numericOfInt (n : Int) : Numeric := + .exact (decimalOfInt n) + +def numericOfDecimal (d : Decimal) : Numeric := + .exact d + +def valueToDecimal : Value → EvalM Decimal + | .number (.exact q) => pure q + | _ => throw (.typeError "expected a numeric value") + +def divDecimals (lhs rhs : Decimal) : EvalM Decimal := do + if rhs.num = 0 then + throw .divisionByZero + else + pure (mkRational (lhs.num * Int.ofNat rhs.den) (lhs.den * rhs.num.natAbs) |> + fun q => if rhs.num < 0 then { q with num := -q.num } else q) + +def valueToLazyReal : Value → EvalM LazyReal + | .number (.composite r) => pure r + | .number (.exact q) => pure (.exact q) + | _ => throw (.typeError "expected a numeric or LazyReal value") + +def generatorComplexity : Generator → Nat + | .exactRatio _ => 1 + | .exactDecimal _ => 1 + | .rationalRefiner _ => 2 + | .decimalRefiner _ => 2 + | .piChudnovsky => 3 + | .sqrt _ => 2 + | .add lhs rhs => 1 + generatorComplexity lhs + generatorComplexity rhs + | .sub lhs rhs => 1 + generatorComplexity lhs + generatorComplexity rhs + | .mul lhs rhs => 1 + generatorComplexity lhs + generatorComplexity rhs + | .div lhs rhs => 1 + generatorComplexity lhs + generatorComplexity rhs + +def selectFirstGoodEnough [Inhabited α] (candidates : List α) (goodEnough : α → Bool) : α := + match candidates.find? goodEnough with + | some candidate => candidate + | none => + match candidates with + | candidate :: _ => candidate + | [] => default + +def generatorGoodEnough : Generator → Bool + | .exactRatio _ => true + | .exactDecimal _ => true + | .rationalRefiner _ => true + | .decimalRefiner _ => true + | .piChudnovsky => true + | .sqrt _ => true + | .add lhs rhs => generatorGoodEnough lhs && generatorGoodEnough rhs + | .sub lhs rhs => generatorGoodEnough lhs && generatorGoodEnough rhs + | .mul lhs rhs => generatorGoodEnough lhs && generatorGoodEnough rhs + | .div lhs rhs => generatorGoodEnough lhs && generatorGoodEnough rhs + +partial def synthesizedCandidates (r : LazyReal) : List Generator := + match r with + | .exact q => + if isTerminatingDecimal q then + [.exactDecimal q, .exactRatio q] + else + [.exactRatio q, .decimalRefiner (.exact q)] + | .ratioProcess q => + if isTerminatingDecimal q then + [.exactDecimal q, .exactRatio q] + else + [.exactRatio q, .decimalRefiner (.ratioProcess q)] + | .decimalProcess d => + [.exactDecimal d, .exactRatio d] + | .generated g => + [g, .decimalRefiner (.generated g), .rationalRefiner (.generated g)] + | .pi => + [.decimalRefiner .pi, .rationalRefiner .pi, .piChudnovsky] + | .sqrt d => + [.decimalRefiner (.sqrt d), .rationalRefiner (.sqrt d), .sqrt d] + | .add lhs rhs => + let lhsGen := selectFirstGoodEnough (synthesizedCandidates lhs) generatorGoodEnough + let rhsGen := selectFirstGoodEnough (synthesizedCandidates rhs) generatorGoodEnough + [.add lhsGen rhsGen, .decimalRefiner (.add lhs rhs), .rationalRefiner (.add lhs rhs)] + | .sub lhs rhs => + let lhsGen := selectFirstGoodEnough (synthesizedCandidates lhs) generatorGoodEnough + let rhsGen := selectFirstGoodEnough (synthesizedCandidates rhs) generatorGoodEnough + [.sub lhsGen rhsGen, .decimalRefiner (.sub lhs rhs), .rationalRefiner (.sub lhs rhs)] + | .mul lhs rhs => + let lhsGen := selectFirstGoodEnough (synthesizedCandidates lhs) generatorGoodEnough + let rhsGen := selectFirstGoodEnough (synthesizedCandidates rhs) generatorGoodEnough + [.mul lhsGen rhsGen, .decimalRefiner (.mul lhs rhs), .rationalRefiner (.mul lhs rhs)] + | .div lhs rhs => + let lhsGen := selectFirstGoodEnough (synthesizedCandidates lhs) generatorGoodEnough + let rhsGen := selectFirstGoodEnough (synthesizedCandidates rhs) generatorGoodEnough + [.div lhsGen rhsGen, .decimalRefiner (.div lhs rhs), .rationalRefiner (.div lhs rhs)] + +def chooseSynthesizedGenerator (r : LazyReal) : Generator := + selectFirstGoodEnough (synthesizedCandidates r) generatorGoodEnough + +mutual + partial def renderGeneratorFormula : Generator → String + | .exactRatio q => s!"{q.num}/{q.den}" + | .exactDecimal d => renderDecimal d + | .rationalRefiner r => s!"rationalRefine({renderLazyFormula r})" + | .decimalRefiner r => s!"decimalRefine({renderLazyFormula r})" + | .piChudnovsky => + "426880 * sqrt(10005) / sum(k => ((-1)^k * (6k)! * (13591409 + 545140134k)) / ((3k)! * (k!)^3 * 640320^(3k)), k = 0..inf)" + | .sqrt d => s!"sqrt({renderDecimal d})" + | .add lhs rhs => s!"({renderGeneratorFormula lhs}) + ({renderGeneratorFormula rhs})" + | .sub lhs rhs => s!"({renderGeneratorFormula lhs}) - ({renderGeneratorFormula rhs})" + | .mul lhs rhs => s!"({renderGeneratorFormula lhs}) * ({renderGeneratorFormula rhs})" + | .div lhs rhs => s!"({renderGeneratorFormula lhs}) / ({renderGeneratorFormula rhs})" + + partial def renderLazyFormula : LazyReal → String + | .exact q => renderDecimal q + | .ratioProcess q => s!"ratio({q.num}/{q.den})" + | .decimalProcess d => s!"decimal({renderDecimal d})" + | .pi => "pi" + | .sqrt d => s!"sqrt({renderDecimal d})" + | .add lhs rhs => s!"({renderLazyFormula lhs}) + ({renderLazyFormula rhs})" + | .sub lhs rhs => s!"({renderLazyFormula lhs}) - ({renderLazyFormula rhs})" + | .mul lhs rhs => s!"({renderLazyFormula lhs}) * ({renderLazyFormula rhs})" + | .div lhs rhs => s!"({renderLazyFormula lhs}) / ({renderLazyFormula rhs})" + | .generated g => renderGeneratorFormula g +end + +def formulaOfValue (value : Value) : EvalM String := do + match value with + | .number (.exact q) => + pure (renderGeneratorFormula (chooseSynthesizedGenerator (.exact q))) + | .number (.composite r) => + pure (renderGeneratorFormula (chooseSynthesizedGenerator r)) + | _ => + let q ← valueToDecimal value + pure (renderGeneratorFormula (chooseSynthesizedGenerator (.exact q))) + +def synthesizeNumeric (value : Value) : EvalM Numeric := do + match value with + | .number (.exact q) => + pure (.composite (.generated (chooseSynthesizedGenerator (.exact q)))) + | .number (.composite r) => + pure (.composite (.generated (chooseSynthesizedGenerator r))) + | _ => + let q ← valueToDecimal value + pure (.composite (.generated (chooseSynthesizedGenerator (.exact q)))) + +partial def natSqrtSearch (target lo hi : Nat) : Nat := + if lo + 1 >= hi then + lo + else + let mid := lo + (hi - lo) / 2 + if mid * mid ≤ target then + natSqrtSearch target mid hi + else + natSqrtSearch target lo mid + +def natSqrtFloor (target : Nat) : Nat := + natSqrtSearch target 0 (target + 1) + +def sqrtBoundsOfDecimal (d : Decimal) (steps : Nat) : EvalM (Decimal × Decimal) := do + if d.num < 0 then + throw (.typeError "sqrt expects a non-negative decimal") + else + let scale := steps + let scaledTarget := d.num.natAbs * powNat 10 (2 * scale) / d.den + let loNat := natSqrtFloor scaledTarget + let lo := mkRational (Int.ofNat loNat) (powNat 10 scale) + let step := mkRational 1 (powNat 10 scale) + pure (lo, addDecimals lo step) + +def natFactorial : Nat → Nat + | 0 => 1 + | n + 1 => (n + 1) * natFactorial n + +def chudnovskyTermMag (k : Nat) : Decimal := + let sixFact := natFactorial (6 * k) + let threeFact := natFactorial (3 * k) + let kFact := natFactorial k + let coeff : Int := 13591409 + 545140134 * Int.ofNat k + let num := Int.ofNat sixFact * coeff + let den := threeFact * kFact * kFact * kFact * powNat 640320 (3 * k) + mkRational num den + +def chudnovskySignedTerm (k : Nat) : Decimal := + let mag := chudnovskyTermMag k + if k % 2 = 0 then mag else { mag with num := -mag.num } + +def chudnovskySum (terms : Nat) : Decimal := + let rec loop (k : Nat) (acc : Decimal) := + if k < terms then + loop (k + 1) (addDecimals acc (chudnovskySignedTerm k)) + else + acc + loop 0 (decimalOfInt 0) + +def piBounds (steps : Nat) : EvalM (Decimal × Decimal) := do + let terms := steps / 14 + 1 + let sum := chudnovskySum terms + let tail := chudnovskyTermMag terms + let sumLo := subDecimals sum tail + let sumHi := addDecimals sum tail + let (sqrtLo, sqrtHi) ← sqrtBoundsOfDecimal (decimalOfInt 10005) (steps + 2) + let coeff := decimalOfInt 426880 + let cLo := mulDecimals coeff sqrtLo + let cHi := mulDecimals coeff sqrtHi + let lo ← divDecimals cLo sumHi + let hi ← divDecimals cHi sumLo + pure (lo, hi) + +def minDecimal (a b : Decimal) : Decimal := + if a.num * Int.ofNat b.den <= b.num * Int.ofNat a.den then a else b + +def maxDecimal (a b : Decimal) : Decimal := + if a.num * Int.ofNat b.den >= b.num * Int.ofNat a.den then a else b + +def roundRationalToScale (q : Rational) (scale : Nat) : Rational := + let factor := powNat 10 scale + let scaledNum := q.num * Int.ofNat factor + let denInt := Int.ofNat q.den + let adjust := Int.ofNat (q.den / 2) + let rounded := + if scaledNum < 0 then + (scaledNum - adjust) / denInt + else + (scaledNum + adjust) / denInt + mkRational rounded factor + +def mulBounds (lo1 hi1 lo2 hi2 : Decimal) : Decimal × Decimal := + let p1 := mulDecimals lo1 lo2 + let p2 := mulDecimals lo1 hi2 + let p3 := mulDecimals hi1 lo2 + let p4 := mulDecimals hi1 hi2 + let lo := minDecimal (minDecimal p1 p2) (minDecimal p3 p4) + let hi := maxDecimal (maxDecimal p1 p2) (maxDecimal p3 p4) + (lo, hi) + +mutual + partial def boundsGenerator (g : Generator) (steps : Nat) : EvalM (Decimal × Decimal) := do + match g with + | .exactRatio q => pure (q, q) + | .exactDecimal d => pure (d, d) + | .rationalRefiner r => + let q ← approxLazyReal r steps + pure (q, q) + | .decimalRefiner r => + let q ← approxLazyReal r steps + pure (roundRationalToScale q steps, roundRationalToScale q steps) + | .piChudnovsky => piBounds steps + | .sqrt d => sqrtBoundsOfDecimal d steps + | .add lhs rhs => + let (llo, lhi) ← boundsGenerator lhs steps + let (rlo, rhi) ← boundsGenerator rhs steps + pure (addDecimals llo rlo, addDecimals lhi rhi) + | .sub lhs rhs => + let (llo, lhi) ← boundsGenerator lhs steps + let (rlo, rhi) ← boundsGenerator rhs steps + pure (subDecimals llo rhi, subDecimals lhi rlo) + | .mul lhs rhs => + let (llo, lhi) ← boundsGenerator lhs steps + let (rlo, rhi) ← boundsGenerator rhs steps + pure (mulBounds llo lhi rlo rhi) + | .div lhs rhs => + let (llo, lhi) ← boundsGenerator lhs steps + let (rlo, rhi) ← boundsGenerator rhs steps + if rlo.num <= 0 && 0 <= rhi.num then + throw (.typeError "generated division interval crosses zero") + else + let invLo ← divDecimals (decimalOfInt 1) rhi + let invHi ← divDecimals (decimalOfInt 1) rlo + pure (mulBounds llo lhi invLo invHi) + + partial def approxGenerator (g : Generator) (steps : Nat) : EvalM Decimal := do + match g with + | .exactRatio q => pure q + | .exactDecimal d => pure d + | .rationalRefiner r => approxLazyReal r steps + | .decimalRefiner r => + let q ← approxLazyReal r steps + pure (roundRationalToScale q steps) + | .piChudnovsky => + let (lo, hi) ← piBounds steps + divDecimals (addDecimals lo hi) (decimalOfInt 2) + | .sqrt d => + let (lo, hi) ← sqrtBoundsOfDecimal d steps + divDecimals (addDecimals lo hi) (decimalOfInt 2) + | .add _ _ | .sub _ _ | .mul _ _ | .div _ _ => + let (lo, hi) ← boundsGenerator g steps + divDecimals (addDecimals lo hi) (decimalOfInt 2) + + partial def boundsLazyReal (r : LazyReal) (steps : Nat) : EvalM (Decimal × Decimal) := do + match r with + | .exact d => pure (d, d) + | .ratioProcess q => pure (q, q) + | .decimalProcess d => pure (d, d) + | .generated g => boundsGenerator g steps + | .pi => piBounds steps + | .sqrt d => sqrtBoundsOfDecimal d steps + | .add lhs rhs => + let (llo, lhi) ← boundsLazyReal lhs steps + let (rlo, rhi) ← boundsLazyReal rhs steps + pure (addDecimals llo rlo, addDecimals lhi rhi) + | .sub lhs rhs => + let (llo, lhi) ← boundsLazyReal lhs steps + let (rlo, rhi) ← boundsLazyReal rhs steps + pure (subDecimals llo rhi, subDecimals lhi rlo) + | .mul lhs rhs => + let (llo, lhi) ← boundsLazyReal lhs steps + let (rlo, rhi) ← boundsLazyReal rhs steps + pure (mulBounds llo lhi rlo rhi) + | .div lhs rhs => + let (llo, lhi) ← boundsLazyReal lhs steps + let (rlo, rhi) ← boundsLazyReal rhs steps + if rlo.num <= 0 && 0 <= rhi.num then + throw (.typeError "lazy division interval crosses zero") + else + let invLo ← divDecimals (decimalOfInt 1) rhi + let invHi ← divDecimals (decimalOfInt 1) rlo + pure (mulBounds llo lhi invLo invHi) + + partial def approxLazyReal (r : LazyReal) (steps : Nat) : EvalM Decimal := do + match r with + | .exact d => pure d + | .ratioProcess q => pure q + | .decimalProcess d => pure d + | .generated g => approxGenerator g steps + | .pi => + let (lo, hi) ← piBounds steps + divDecimals (addDecimals lo hi) (decimalOfInt 2) + | .sqrt d => + let (lo, hi) ← sqrtBoundsOfDecimal d steps + divDecimals (addDecimals lo hi) (decimalOfInt 2) + | .add _ _ | .sub _ _ | .mul _ _ | .div _ _ => + let (lo, hi) ← boundsLazyReal r steps + divDecimals (addDecimals lo hi) (decimalOfInt 2) +end + +def stabilizeNumeric (value : Value) (steps : Nat) : EvalM Rational := do + match value with + | .number (.exact q) => pure (roundRationalToScale q steps) + | .number (.composite r) => + let q ← approxLazyReal r steps + pure (roundRationalToScale q steps) + | _ => + let q ← valueToDecimal value + pure (roundRationalToScale q steps) + def Env.lookup (env : Env) (name : Name) : Option Value := match env with | [] => none | (n, v) :: rest => if n = name then some v else rest.lookup name def expectInt : Value → EvalM Int - | .int n => pure n + | .number (.exact q) => + if q.den = 1 then pure q.num else throw (.typeError "expected an integer") | _ => throw (.typeError "expected an integer") +def expectDecimal : Value → EvalM Decimal + | .number (.exact q) => pure q + | _ => throw (.typeError "expected a decimal") + +def expectLazyReal : Value → EvalM LazyReal + | .number (.composite r) => pure r + | _ => throw (.typeError "expected a LazyReal") + def expectBool : Value → EvalM Bool | .bool b => pure b | _ => throw (.typeError "expected a boolean") @@ -140,17 +690,119 @@ def expectData : Value → EvalM Data | .data term => pure term | _ => throw (.typeError "expected a data value") +def expectWholeNat (value : Value) : EvalM Nat := do + let q ← valueToDecimal value + if q.den ≠ 1 then + throw (.typeError "expected a whole rational") + else if q.num < 0 then + throw (.typeError "expected a non-negative whole rational") + else + pure q.num.toNat + +def rationalStabilityOfValue (value : Value) : String := + match value with + | .number (.composite (.ratioProcess _)) => "Immediate" + | .number (.composite (.decimalProcess _)) => "Immediate" + | .number (.composite (.generated (.exactRatio _))) => "Immediate" + | .number (.composite (.generated (.exactDecimal _))) => "Immediate" + | .number (.exact _) => "Immediate" + | .number (.composite _) => "Refining" + | _ => "Unavailable" + +def decimalStabilityOfRational (q : Rational) : String := + if isTerminatingDecimal q then "Terminating" else "Periodic" + +def decimalStabilityOfValue (value : Value) : String := + match value with + | .number (.composite (.ratioProcess q)) => decimalStabilityOfRational q + | .number (.composite (.decimalProcess d)) => decimalStabilityOfRational d + | .number (.composite (.generated (.exactRatio q))) => decimalStabilityOfRational q + | .number (.composite (.generated (.exactDecimal d))) => decimalStabilityOfRational d + | .number (.exact q) => decimalStabilityOfRational q + | .number (.composite _) => "Refining" + | _ => "Unavailable" + +def observeValue (value : Value) (obs : Observation) : EvalM ObservationResult := do + match obs with + | .asRational => + match value with + | .number (.exact q) => pure (.rational q) + | .number (.composite _) => + throw (.typeError s!"rational observation failed: number is {rationalStabilityOfValue value}, not Immediate") + | _ => + pure (.rational (← valueToDecimal value)) + | .asDecimal => + match value with + | .number (.exact q) => + if isTerminatingDecimal q then + pure (.decimal q) + else + throw (.typeError s!"decimal observation failed: number is {decimalStabilityOfValue value}, not Terminating") + | .number (.composite _) => + throw (.typeError s!"decimal observation failed: number is {decimalStabilityOfValue value}, not Terminating") + | _ => + let q ← valueToDecimal value + if isTerminatingDecimal q then + pure (.decimal q) + else + throw (.typeError s!"decimal observation failed: number is {decimalStabilityOfValue value}, not Terminating") + | .asInt => + match value with + | .number (.exact q) => + if q.den = 1 then + pure (.int q.num) + else + throw (.typeError "integer observation failed: number is not whole") + | .number (.composite _) => + throw (.typeError s!"integer observation failed: number is {rationalStabilityOfValue value}, not Immediate") + | _ => + let q ← valueToDecimal value + if q.den = 1 then + pure (.int q.num) + else + throw (.typeError "integer observation failed: number is not whole") + | .rationalStability => + pure (.stability (rationalStabilityOfValue value)) + | .decimalStability => + pure (.stability (decimalStabilityOfValue value)) + | .getField key => + match value with + | .data (.record fields) => + match fields.find? (fun (name, _) => name = key) with + | some (_, field) => pure (.data field) + | none => throw (.typeError s!"missing field: {key}") + | _ => throw (.typeError "get expects a record") + | .atIndex idx => + let rec arrayGet? : List Data → Nat → Option Data + | [], _ => none + | x :: _, 0 => some x + | _ :: xs, n + 1 => arrayGet? xs n + match value with + | .data (.array items) => + match arrayGet? items idx with + | some item => pure (.data item) + | none => throw (RuntimeError.typeError s!"index out of bounds: {idx}") + | _ => throw (RuntimeError.typeError "at expects an array") + | .approx steps => + let r ← valueToLazyReal value + pure (.rational (← approxLazyReal r steps)) + | .bounds steps => + let r ← valueToLazyReal value + let (lo, hi) ← boundsLazyReal r steps + pure (.bounds lo hi) + def valueToData : Value → EvalM Data | .data term => pure term - | .int n => pure (.int n) + | .number (.exact q) => pure (.decimal q) | .bool b => pure (.bool b) | .string s => pure (.string s) - | _ => throw (.typeError "data literals only support Int, Bool, String, and Data values") + | _ => throw (.typeError "data literals only support Int, Decimal, Bool, String, and Data values") partial def dataType : Data → Ty | .null => .dataNull | .bool _ => .dataBool | .int _ => .dataInt + | .decimal _ => .dataRational | .string _ => .dataString | .array [] => .dataArray .data | .array (x :: xs) => @@ -169,6 +821,8 @@ def dataRecordField? : List (String × Ty) → String → Option Ty def liftScalarDataTy : Ty → Ty | .dataInt => .int + | .dataDecimal => .decimal + | .dataRational => .rational | .dataBool => .bool | .dataString => .string | ty => ty @@ -176,6 +830,8 @@ def liftScalarDataTy : Ty → Ty partial def refineKnownDataTy (ty : Ty) : Ty := match ty with | .dataInt => .int + | .dataDecimal => .decimal + | .dataRational => .rational | .dataBool => .bool | .dataString => .string | .dataNull => .data @@ -205,6 +861,7 @@ partial def Data.ofNickel : Nickel.Term → Data | .null => .null | .bool b => .bool b | .int n => .int n + | .decimal d => .decimal (parseDecimal d) | .string s => .string s | .array xs => .array (xs.map Data.ofNickel) | .record fields => .record (fields.map (fun (k, v) => (k, Data.ofNickel v))) @@ -248,6 +905,7 @@ partial def renderNickelData : Data → String | .null => "null" | .bool b => toString b | .int n => toString n + | .decimal d => renderDecimal d | .string s => renderJsonString s | .array xs => "[" ++ String.intercalate ", " (xs.map renderNickelData) ++ "]" | .record fields => @@ -258,6 +916,7 @@ partial def renderJsonData : Data → String | .null => "null" | .bool b => if b then "true" else "false" | .int n => toString n + | .decimal d => renderDecimal d | .string s => renderJsonString s | .array xs => "[" ++ String.intercalate ", " (xs.map renderJsonData) ++ "]" | .record fields => @@ -271,13 +930,29 @@ def builtinType? (name : Name) : Option Ty := | "readFileNickel" => some (.funTy .string [.error, .io] .data) | "parseNickel" => some (.funTy .string [.error] .data) | "parseJson" => some (.funTy .string [.error] .data) + | "getAturi" => some (.funTy .string [.error, .io] .data) | "get" => some (.funTy .data [] (.funTy .string [.error] .data)) - | "at" => some (.funTy .data [] (.funTy .int [.error] .data)) + | "at" => some (.funTy .data [] (.funTy .number [.error] .data)) | "asInt" => some (.funTy .data [.error] .int) + | "asDecimal" => some (.funTy .data [.error] .decimal) + | "toInt" => some (.funTy .number [.error] .int) + | "toDecimal" => some (.funTy .number [.error] .decimal) | "asString" => some (.funTy .data [.error] .string) | "asBool" => some (.funTy .data [.error] .bool) | "toJson" => some (.funTy .data [] .string) | "toNickel" => some (.funTy .data [] .string) + | "rationalStability" => some (.funTy .number [] .stability) + | "decimalStability" => some (.funTy .number [] .stability) + | "pi" => some .number + | "sqrt" => some (.funTy .number [.error] .number) + | "process" => some (.funTy .number [] .number) + | "synthesize" => some (.funTy .number [.error] .number) + | "formula" => some (.funTy .number [.error] .string) + | "lazyRationalStability" => some (.funTy .number [] .stability) + | "lazyDecimalStability" => some (.funTy .number [] .stability) + | "stabilize" => some (.funTy .number [] (.funTy .number [.error] .number)) + | "approx" => some (.funTy .number [] (.funTy .number [.error] .number)) + | "bounds" => some (.funTy .number [] (.funTy .number [.error] .data)) | _ => none def builtinValue? (name : Name) : Option Value := @@ -287,13 +962,29 @@ def builtinValue? (name : Name) : Option Value := | "readFileNickel" => some .builtinReadFileNickel | "parseNickel" => some .builtinParseNickel | "parseJson" => some .builtinParseJson + | "getAturi" => some .builtinGetAturi | "get" => some .builtinDataGet | "at" => some .builtinDataAt | "asInt" => some .builtinDataAsInt + | "asDecimal" => some .builtinDataAsDecimal + | "toInt" => some .builtinRationalToInt + | "toDecimal" => some .builtinRationalToDecimal | "asString" => some .builtinDataAsString | "asBool" => some .builtinDataAsBool | "toJson" => some .builtinDataToJson | "toNickel" => some .builtinDataToNickel + | "rationalStability" => some .builtinRationalStability + | "decimalStability" => some .builtinDecimalStability + | "pi" => some (.number (.composite .pi)) + | "sqrt" => some .builtinSqrt + | "process" => some .builtinProcess + | "synthesize" => some .builtinSynthesize + | "formula" => some .builtinFormula + | "lazyRationalStability" => some .builtinRationalStability + | "lazyDecimalStability" => some .builtinDecimalStability + | "stabilize" => some .builtinStabilize + | "approx" => some .builtinApprox + | "bounds" => some .builtinBounds | _ => none mutual @@ -321,36 +1012,40 @@ mutual let source ← expectString argVal let rendered ← IO.toEIO (fun err => .ioError (toString err)) (NickelHost.evalJsonString source) pure (.data (← parseRenderedData rendered)) - | .builtinDataGet => do - let term ← expectData argVal - pure (.builtinDataGetField term) - | .builtinDataGetField term => do + | .builtinGetAturi => do + let source ← expectString argVal + let rendered ← IO.toEIO (fun err => .ioError (toString err)) (Http.parseAturi source) + pure (.data (← parseRenderedData rendered)) + | .builtinDataGet => pure (.builtinDataGetField argVal) + | .builtinDataGetField target => do let key ← expectString argVal - match term with - | .record fields => - match fields.find? (fun (name, _) => name = key) with - | some (_, value) => pure (.data value) - | none => throw (.typeError s!"missing field: {key}") - | _ => throw (.typeError "get expects a record") - | .builtinDataAt => do - let term ← expectData argVal - pure (.builtinDataAtIndex term) - | .builtinDataAtIndex term => do - let idx ← expectInt argVal - if idx < 0 then - throw (.typeError s!"negative index: {idx}") - else - match term with - | .array items => - match dataArrayGet? items idx.toNat with - | some value => pure (.data value) - | none => throw (RuntimeError.typeError s!"index out of bounds: {idx}") - | _ => throw (.typeError "at expects an array") + match (← observeValue target (.getField key)) with + | .data value => pure (.data value) + | _ => throw (.typeError "get observation returned non-data") + | .builtinDataAt => pure (.builtinDataAtIndex argVal) + | .builtinDataAtIndex target => do + let idx ← expectWholeNat argVal + match (← observeValue target (.atIndex idx)) with + | .data value => pure (.data value) + | _ => throw (.typeError "at observation returned non-data") | .builtinDataAsInt => do let term ← expectData argVal match term with - | .int n => pure (.int n) + | .int n => pure (.number (numericOfInt n)) | _ => throw (.typeError "asInt expects an integer") + | .builtinDataAsDecimal => do + let term ← expectData argVal + match term with + | .decimal d => pure (.number (numericOfDecimal d)) + | _ => throw (.typeError "asDecimal expects a decimal") + | .builtinRationalToInt => do + match (← observeValue argVal .asInt) with + | .int n => pure (.number (numericOfInt n)) + | _ => throw (.typeError "toInt observation returned non-int") + | .builtinRationalToDecimal => do + match (← observeValue argVal .asDecimal) with + | .decimal d => pure (.number (numericOfDecimal d)) + | _ => throw (.typeError "toDecimal observation returned non-decimal") | .builtinDataAsString => do let term ← expectData argVal match term with @@ -367,28 +1062,63 @@ mutual | .builtinDataToNickel => do let term ← expectData argVal pure (.string (renderNickelData term)) + | .builtinRationalStability => do + match (← observeValue argVal .rationalStability) with + | .stability label => pure (.stability label) + | _ => throw (.typeError "rationalStability observation returned non-stability") + | .builtinDecimalStability => do + match (← observeValue argVal .decimalStability) with + | .stability label => pure (.stability label) + | _ => throw (.typeError "decimalStability observation returned non-stability") + | .builtinSqrt => do + let d ← valueToDecimal argVal + if d.num < 0 then + throw (.typeError "sqrt expects a non-negative decimal") + else + pure (.number (.composite (.sqrt d))) + | .builtinProcess => do + match argVal with + | .number (.exact q) => pure (.number (.composite (.exact q))) + | .number (.composite r) => pure (.number (.composite r)) + | _ => throw (.typeError "process expects a number") + | .builtinSynthesize => do + pure (.number (← synthesizeNumeric argVal)) + | .builtinFormula => do + pure (.string (← formulaOfValue argVal)) + | .builtinStabilize => pure (.builtinStabilizeStep argVal) + | .builtinStabilizeStep target => do + let steps ← expectWholeNat argVal + pure (.number (.exact (← stabilizeNumeric target steps))) + | .builtinApprox => pure (.builtinApproxStep argVal) + | .builtinApproxStep target => do + let steps ← expectWholeNat argVal + match (← observeValue target (.approx steps)) with + | .rational q => pure (.number (.exact q)) + | _ => throw (.typeError "approx observation returned non-rational") + | .builtinBounds => pure (.builtinBoundsStep argVal) + | .builtinBoundsStep target => do + let steps ← expectWholeNat argVal + match (← observeValue target (.bounds steps)) with + | .bounds lo hi => pure (.data (.record [("lo", .decimal lo), ("hi", .decimal hi)])) + | _ => throw (.typeError "bounds observation returned non-bounds") + | .data (.record fields) => + let key ← expectString argVal + match fields.find? (fun (name, _) => name = key) with + | some (_, value) => pure (.data value) + | none => throw (.typeError s!"missing field: {key}") + | .data (.array items) => + let idx ← expectWholeNat argVal + match dataArrayGet? items idx with + | some value => pure (.data value) + | none => throw (RuntimeError.typeError s!"index out of bounds: {idx}") + | .data _ => + throw (.typeError "data value is not callable with this argument") | _ => - match fnVal, argVal with - | .data (.record fields), .string key => - match fields.find? (fun (name, _) => name = key) with - | some (_, value) => pure (.data value) - | none => throw (.typeError s!"missing field: {key}") - | .data (.array items), .int idx => - if idx < 0 then - throw (.typeError s!"negative index: {idx}") - else - match dataArrayGet? items idx.toNat with - | some value => pure (.data value) - | none => throw (RuntimeError.typeError s!"index out of bounds: {idx}") - | .data _, .string _ => - throw (.typeError "string dispatch expects a record") - | .data _, .int _ => - throw (.typeError "int dispatch expects an array") - | _, _ => - throw (.typeError "expected a function") + throw (.typeError "expected a function") partial def eval (env : Env) : Expr → EvalM Value - | .int n => pure (.int n) + | .int n => pure (.number (numericOfInt n)) + | .decimal d => pure (.number (numericOfDecimal d)) | .bool b => pure (.bool b) | .string s => pure (.string s) | .null => pure (.data .null) @@ -408,28 +1138,43 @@ mutual pure (name, (← valueToData value'))) pure (.data (.record fields')) | .add lhs rhs => do - let l ← expectInt (← eval env lhs) - let r ← expectInt (← eval env rhs) - pure (.int (l + r)) + let lVal ← eval env lhs + let rVal ← eval env rhs + match lVal, rVal with + | .number (.composite _), _ | _, .number (.composite _) => + pure (.number (.composite (.add (← valueToLazyReal lVal) (← valueToLazyReal rVal)))) + | _, _ => + pure (.number (.exact (addDecimals (← valueToDecimal lVal) (← valueToDecimal rVal)))) | .sub lhs rhs => do - let l ← expectInt (← eval env lhs) - let r ← expectInt (← eval env rhs) - pure (.int (l - r)) + let lVal ← eval env lhs + let rVal ← eval env rhs + match lVal, rVal with + | .number (.composite _), _ | _, .number (.composite _) => + pure (.number (.composite (.sub (← valueToLazyReal lVal) (← valueToLazyReal rVal)))) + | _, _ => + pure (.number (.exact (subDecimals (← valueToDecimal lVal) (← valueToDecimal rVal)))) | .mul lhs rhs => do - let l ← expectInt (← eval env lhs) - let r ← expectInt (← eval env rhs) - pure (.int (l * r)) + let lVal ← eval env lhs + let rVal ← eval env rhs + match lVal, rVal with + | .number (.composite _), _ | _, .number (.composite _) => + pure (.number (.composite (.mul (← valueToLazyReal lVal) (← valueToLazyReal rVal)))) + | _, _ => + pure (.number (.exact (mulDecimals (← valueToDecimal lVal) (← valueToDecimal rVal)))) | .div lhs rhs => do - let l ← expectInt (← eval env lhs) - let r ← expectInt (← eval env rhs) - if r = 0 then - throw .divisionByZero - else - pure (.int (l / r)) + let lVal ← eval env lhs + let rVal ← eval env rhs + match lVal, rVal with + | .number (.composite _), _ | _, .number (.composite _) => + pure (.number (.composite (.div (← valueToLazyReal lVal) (← valueToLazyReal rVal)))) + | _, _ => + pure (.number (.exact (← divDecimals (← valueToDecimal lVal) (← valueToDecimal rVal)))) | .eq lhs rhs => do - let l ← expectInt (← eval env lhs) - let r ← expectInt (← eval env rhs) - pure (.bool (l = r)) + let l ← eval env lhs + let r ← eval env rhs + match l, r with + | .number _, .number _ => pure (.bool (rationalEq (← valueToDecimal l) (← valueToDecimal r))) + | _, _ => throw (.typeError "equality expects matching Int or Decimal operands") | .letE name value body => do let value' ← eval env value eval ((name, value') :: env) body @@ -464,8 +1209,36 @@ end def renderData : Data → String := renderNickelData +def renderObservedScalar (value : Value) : Option String := + match value with + | .number (.exact q) => some s!"{q.num}/{q.den}" + | _ => none + def renderValue : Value → String - | .int n => toString n + | value@(.number (.exact _)) => + match renderObservedScalar value with + | some rendered => rendered + | none => "" + | .stability label => label + | .number (.composite (.ratioProcess q)) => s!"" + | .number (.composite (.decimalProcess d)) => s!"" + | .number (.composite (.generated (.exactRatio q))) => s!"" + | .number (.composite (.generated (.exactDecimal d))) => s!"" + | .number (.composite (.generated (.rationalRefiner _))) => "" + | .number (.composite (.generated (.decimalRefiner _))) => "" + | .number (.composite (.generated .piChudnovsky)) => "" + | .number (.composite (.generated (.sqrt d))) => s!"" + | .number (.composite (.generated (.add _ _))) => "" + | .number (.composite (.generated (.sub _ _))) => "" + | .number (.composite (.generated (.mul _ _))) => "" + | .number (.composite (.generated (.div _ _))) => "" + | .number (.composite (.exact d)) => s!"" + | .number (.composite .pi) => "" + | .number (.composite (.sqrt d)) => s!"" + | .number (.composite (.add _ _)) => "" + | .number (.composite (.sub _ _)) => "" + | .number (.composite (.mul _ _)) => "" + | .number (.composite (.div _ _)) => "" | .bool b => toString b | .string s => s!"\"{s}\"" | .data data => renderData data @@ -478,15 +1251,31 @@ def renderValue : Value → String | .builtinReadFileNickel => "" | .builtinParseNickel => "" | .builtinParseJson => "" + | .builtinGetAturi => "" | .builtinDataGet => "" | .builtinDataGetField _ => "" | .builtinDataAt => "" | .builtinDataAtIndex _ => "" | .builtinDataAsInt => "" + | .builtinDataAsDecimal => "" + | .builtinRationalToInt => "" + | .builtinRationalToDecimal => "" | .builtinDataAsString => "" | .builtinDataAsBool => "" | .builtinDataToJson => "" | .builtinDataToNickel => "" + | .builtinRationalStability => "" + | .builtinDecimalStability => "" + | .builtinSqrt => "" + | .builtinProcess => "" + | .builtinSynthesize => "" + | .builtinFormula => "" + | .builtinStabilize => "" + | .builtinStabilizeStep _ => "" + | .builtinApprox => "" + | .builtinApproxStep _ => "" + | .builtinBounds => "" + | .builtinBoundsStep _ => "" | .closure _ _ _ => "" abbrev TyEnv := List (Name × Ty) @@ -515,11 +1304,15 @@ def ensureType (expected actual : Ty) : CheckM Unit := else throw (.mismatch expected actual) +def isNumericTy : Ty → Bool + | .number | .int | .decimal | .rational | .lazyReal => true + | _ => false + def pureJudgment (ty : Ty) : Judgment := { ty := ty, effects := [] } def eraseDataRefinement : Ty → Ty - | .dataInt | .dataBool | .dataString | .dataNull | .dataArray _ | .dataRecord _ => .data + | .dataInt | .dataDecimal | .dataRational | .dataBool | .dataString | .dataNull | .dataArray _ | .dataRecord _ => .data | ty => ty def canFlowTo (actual expected : Ty) : Bool := @@ -527,7 +1320,20 @@ def canFlowTo (actual expected : Ty) : Bool := true else match actual, expected with + | .number, .number => true + | .int, .number => true + | .decimal, .number => true + | .rational, .number => true + | .lazyReal, .number => true + | .number, .decimal => true + | .number, .rational => true + | .int, .decimal => true + | .int, .rational => true + | .decimal, .rational => true + | .rational, .decimal => true | .dataInt, .data => true + | .dataDecimal, .data => true + | .dataRational, .data => true | .dataBool, .data => true | .dataString, .data => true | .dataNull, .data => true @@ -544,6 +1350,7 @@ unsafe def evalConstData? (env : List (Name × Data)) : Expr → Option Data | .null => some .null | .string s => some (.string s) | .int n => some (.int n) + | .decimal d => some (.decimal d) | .bool b => some (.bool b) | .var name => env.lookup name | .arrayE items => do @@ -592,6 +1399,17 @@ unsafe def evalConstData? (env : List (Name × Data)) : Expr → Option Data | .ok body => some (.string body) | .error _ => none | _ => none + | .var "getAturi" => + let source ← evalConstData? env arg + match source with + | .string s => + match unsafeIO (Http.parseAturi s) with + | .ok rendered => + match Nickel.parse rendered with + | .ok term => some (Data.ofNickel term) + | .error _ => none + | .error _ => none + | _ => none | .var "readFileNickel" => let path ← evalConstData? env arg match path with @@ -623,7 +1441,8 @@ unsafe def evalConstData? (env : List (Name × Data)) : Expr → Option Data | _ => none unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment - | .int _ => pure (pureJudgment .int) + | .int _ => pure (pureJudgment .number) + | .decimal _ => pure (pureJudgment .number) | .bool _ => pure (pureJudgment .bool) | .string _ => pure (pureJudgment .string) | .null => pure (pureJudgment .dataNull) @@ -638,11 +1457,16 @@ unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment let itemJs ← items.mapM (inferType env) let dataTys ← itemJs.mapM (fun j => match j.ty with + | .number => pure .dataRational | .int => pure .dataInt + | .decimal => pure .dataDecimal + | .rational => pure .dataRational | .bool => pure .dataBool | .string => pure .dataString | .data => pure .data | .dataInt => pure .dataInt + | .dataDecimal => pure .dataDecimal + | .dataRational => pure .dataRational | .dataBool => pure .dataBool | .dataString => pure .dataString | .dataNull => pure .dataNull @@ -661,11 +1485,16 @@ unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment let fieldJs ← fields.mapM (fun (name, value) => do pure (name, ← inferType env value)) let fieldTys ← fieldJs.mapM (fun (name, j) => do let ty ← match j.ty with + | .number => pure .dataRational | .int => pure .dataInt + | .decimal => pure .dataDecimal + | .rational => pure .dataRational | .bool => pure .dataBool | .string => pure .dataString | .data => pure .data | .dataInt => pure .dataInt + | .dataDecimal => pure .dataDecimal + | .dataRational => pure .dataRational | .dataBool => pure .dataBool | .dataString => pure .dataString | .dataNull => pure .dataNull @@ -682,24 +1511,30 @@ unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment | .mul lhs rhs => do let lhsJ ← inferType env lhs let rhsJ ← inferType env rhs - ensureType .int lhsJ.ty - ensureType .int rhsJ.ty - pure { ty := .int, effects := lhsJ.effects.union rhsJ.effects } + if isNumericTy lhsJ.ty && isNumericTy rhsJ.ty then + pure { + ty := .number + effects := lhsJ.effects.union rhsJ.effects + } + else + throw (.mismatch lhsJ.ty rhsJ.ty) | .div lhs rhs => do let lhsJ ← inferType env lhs let rhsJ ← inferType env rhs - ensureType .int lhsJ.ty - ensureType .int rhsJ.ty - pure { - ty := .int - effects := (lhsJ.effects.union rhsJ.effects).insert .error - } + if isNumericTy lhsJ.ty && isNumericTy rhsJ.ty then + pure { + ty := .number + effects := (lhsJ.effects.union rhsJ.effects).insert .error + } + else + throw (.mismatch lhsJ.ty rhsJ.ty) | .eq lhs rhs => do let lhsJ ← inferType env lhs let rhsJ ← inferType env rhs - ensureType .int lhsJ.ty - ensureType .int rhsJ.ty - pure { ty := .bool, effects := lhsJ.effects.union rhsJ.effects } + if isNumericTy lhsJ.ty && isNumericTy rhsJ.ty then + pure { ty := .bool, effects := lhsJ.effects.union rhsJ.effects } + else + throw (.mismatch lhsJ.ty rhsJ.ty) | .letE name value body => do let valueJ ← inferType env value let bodyJ ← inferType ((name, valueJ.ty) :: env) body @@ -791,6 +1626,14 @@ unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment match argJ.ty with | .dataInt => .int | _ => .int + | .var "asDecimal", _, .decimal => + match argJ.ty with + | .dataDecimal => .decimal + | _ => .decimal + | .var "toInt", _, .int => + .int + | .var "toDecimal", _, .decimal => + .decimal | .var "asString", _, .string => match argJ.ty with | .dataString => .string @@ -799,6 +1642,12 @@ unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment match argJ.ty with | .dataBool => .bool | _ => .bool + | .app (.var "approx") _, _, .number => + .number + | .app (.var "stabilize") _, _, .number => + .number + | .app (.var "bounds") _, _, .data => + .data | _, _, _ => resultTy pure { ty := refinedResultTy @@ -810,11 +1659,18 @@ unsafe def inferType (env : TyEnv) : Expr → CheckM Judgment throw (.expectedFunction fnJ.ty) partial def renderType : Ty → String + | .number => "Number" | .int => "Int" + | .decimal => "Decimal" + | .rational => "Number" + | .lazyReal => "Number" + | .stability => "Stability" | .bool => "Bool" | .string => "String" | .data => "Data" | .dataInt => "Data:Int" + | .dataDecimal => "Data:Decimal" + | .dataRational => "Data:Rational" | .dataBool => "Data:Bool" | .dataString => "Data:String" | .dataNull => "Data:Null" @@ -827,6 +1683,11 @@ partial def renderType : Ty → String | .error => "Error" | .funTy lhs effects rhs => s!"({renderType lhs} -> {renderType rhs} ! {effects.render})" +def renderTypeError : TypeError → String + | .unboundVariable name => s!"unbound variable {name}" + | .mismatch expected actual => s!"expected {renderType expected}, got {renderType actual}" + | .expectedFunction actual => s!"expected function, got {renderType actual}" + def renderJudgment (judgment : Judgment) : String := s!"{renderType judgment.ty} ! {judgment.effects.render}" diff --git a/Mlang/Nickel.lean b/Mlang/Nickel.lean index 00d36fb..94ab75d 100644 --- a/Mlang/Nickel.lean +++ b/Mlang/Nickel.lean @@ -1,9 +1,12 @@ namespace Mlang.Nickel +abbrev Decimal := String + inductive Term where | null : Term | bool : Bool → Term | int : Int → Term + | decimal : Decimal → Term | string : String → Term | array : List Term → Term | record : List (String × Term) → Term @@ -77,6 +80,27 @@ def parseInt (input : Input) : ParseM (Int × Input) := do | some n => pure (n, rest) | none => throw "invalid integer literal" +def parseNumber (input : Input) : ParseM (Term × Input) := do + let (sign, rest) := + match input with + | '-' :: tail => ("-", tail) + | _ => ("", input) + let (wholeDigits, rest) := spanChars isDigit rest + if wholeDigits.isEmpty then + throw "expected numeric literal" + else + match rest with + | '.' :: fracRest => + let (fracDigits, tail) := spanChars isDigit fracRest + if fracDigits.isEmpty then + throw "invalid decimal literal" + else + pure (.decimal (sign ++ charsToString wholeDigits ++ "." ++ charsToString fracDigits), tail) + | _ => + match String.toInt? (sign ++ charsToString wholeDigits) with + | some n => pure (.int n, rest) + | none => throw "invalid integer literal" + def parseIdent (input : Input) : ParseM (String × Input) := do match input with | c :: rest => @@ -107,12 +131,10 @@ mutual | "null" => pure (.null, rest) | _ => throw s!"unexpected identifier '{name}'" | '-' :: _ => - let (n, rest) ← parseInt input - pure (.int n, rest) + parseNumber input | c :: _ => if isDigit c then - let (n, rest) ← parseInt input - pure (.int n, rest) + parseNumber input else throw s!"unexpected character '{c}'" @@ -178,6 +200,7 @@ partial def render : Term → String | .bool true => "true" | .bool false => "false" | .int n => toString n + | .decimal d => d | .string s => s!"\"{s}\"" | .array xs => "[" ++ String.intercalate ", " (xs.map render) ++ "]" diff --git a/Mlang/Parser.lean b/Mlang/Parser.lean index f3e2664..8b9e1b8 100644 --- a/Mlang/Parser.lean +++ b/Mlang/Parser.lean @@ -4,6 +4,7 @@ namespace Mlang inductive Token where | int : Int → Token + | decimal : Decimal → Token | bool : Bool → Token | string : String → Token | ident : String → Token @@ -54,7 +55,7 @@ def isIdentContinue (c : Char) : Bool := isIdentStart c || isDigit c def isAtomStart : Token → Bool - | .int _ | .bool _ | .string _ | .ident _ | .lparen | .lbracket | .lbrace | .kwNull => true + | .int _ | .decimal _ | .bool _ | .string _ | .ident _ | .lparen | .lbracket | .lbrace | .kwNull => true | _ => false partial def spanChars (p : Char → Bool) : List Char → List Char × List Char @@ -91,9 +92,17 @@ def lexIdent (chars : List Char) : Token × List Char := def lexNumber (chars : List Char) : ParseM (Token × List Char) := do let (digits, rest) := spanChars isDigit chars - match String.toInt? (charsToString digits) with - | some n => pure (.int n, rest) - | none => throw (.lexError "invalid integer literal") + match rest with + | '.' :: fracRest => + let (fracDigits, tail) := spanChars isDigit fracRest + if fracDigits.isEmpty then + throw (.lexError "invalid decimal literal") + else + pure (.decimal (parseDecimal (charsToString digits ++ "." ++ charsToString fracDigits)), tail) + | _ => + match String.toInt? (charsToString digits) with + | some n => pure (.int n, rest) + | none => throw (.lexError "invalid integer literal") partial def takeStringChars : List Char → ParseM (List Char × List Char) | [] => throw (.lexError "unterminated string literal") @@ -186,8 +195,13 @@ mutual | _ => pure (lhs, rest) partial def parseTyAtom : ParserState → ParseM (Ty × ParserState) + | .ident "Number" :: rest => pure (.number, rest) | .ident "Int" :: rest => pure (.int, rest) + | .ident "Decimal" :: rest => pure (.decimal, rest) + | .ident "Rational" :: rest => pure (.number, rest) + | .ident "Stability" :: rest => pure (.stability, rest) | .ident "Bool" :: rest => pure (.bool, rest) + | .ident "LazyReal" :: rest => pure (.number, rest) | .ident "String" :: rest => pure (.string, rest) | .ident "Data" :: rest => pure (.data, rest) | .ident "List" :: rest => pure (.list, rest) @@ -347,6 +361,7 @@ mutual partial def parseAtom : ParserState → ParseM (Expr × ParserState) | .int n :: rest => pure (.int n, rest) + | .decimal d :: rest => pure (.decimal d, rest) | .bool b :: rest => pure (.bool b, rest) | .string s :: rest => pure (.string s, rest) | .kwNull :: rest => pure (.null, rest) diff --git a/README.md b/README.md index 9ed67df..7469e27 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,13 @@ A small functional language prototype written in Lean 4. Current scope: -- integer and boolean literals +- integer, decimal, and boolean literals - variables - `let` - `if` - typed lambdas - function application -- integer arithmetic -- integer division +- decimal arithmetic with implicit `Int` promotion - strings - native parsed data values - lexical closures @@ -68,11 +67,11 @@ expr ::= let name = expr in expr | { key = expr, ... } | null | (expr) - | true | false | 123 | "text" | name + | true | false | 123 | 1.23 | "text" | name program ::= expr (';' expr)* -type ::= Int | Bool | String | Data | List | Result | Error | type -> type | (type) +type ::= Int | Decimal | LazyReal | Bool | String | Data | List | Result | Error | type -> type | (type) ``` Inside the REPL, there is also a top-level binding form: @@ -88,18 +87,29 @@ let x = 42 x + 1 ``` -Expressions are checked as `Type ! Effects`. Pure expressions get `{}`; division carries `{Error}` implicitly. `readFile` is a builtin with type `String -> String ! {IO, Error}`. `readFileNickel` is a builtin with type `String -> Data ! {IO, Error}`. `parseNickel` is a builtin with type `String -> Data ! {Error}` and is backed by the real Nickel Rust crate. Nickel source is evaluated on the host side and converted back into `mlang` `Data`. Parsed data inspection is available through: +Expressions are checked as `Type ! Effects`. Plain numeric literals now default to `Rational`, so `1`, `1.25`, `1 + 2`, and `5 / 2` are all rational values unless you explicitly cast them. Division carries `{Error}` implicitly. `readFile` is a builtin with type `String -> String ! {IO, Error}`. `readFileNickel` is a builtin with type `String -> Data ! {IO, Error}`. `parseNickel` is a builtin with type `String -> Data ! {Error}` and is backed by the real Nickel Rust crate. Nickel source is evaluated on the host side and converted back into `mlang` `Data`. Parsed data inspection is available through: - `httpGet : String -> String ! {IO, Error}` - `parseJson : String -> Data ! {Error}` - `get : Data -> String -> Data ! {Error}` -- `at : Data -> Int -> Data ! {Error}` +- `at : Data -> Rational -> Data ! {Error}` - `asInt : Data -> Int ! {Error}` +- `asDecimal : Data -> Decimal ! {Error}` - `asString : Data -> String ! {Error}` - `asBool : Data -> Bool ! {Error}` +- `toInt : Rational -> Int ! {Error}` +- `toDecimal : Rational -> Decimal ! {Error}` - `toJson : Data -> String` - `toNickel : Data -> String` +- `rationalStability : Rational -> Stability` +- `decimalStability : Rational -> Stability` +- `sqrt : Decimal -> LazyReal ! {Error}` +- `pi : LazyReal` +- `lazyRationalStability : LazyReal -> Stability` +- `lazyDecimalStability : LazyReal -> Stability` +- `approx : LazyReal -> Rational -> Rational ! {Error}` +- `bounds : LazyReal -> Rational -> Data ! {Error}` `httpGet` is implemented through a thin Rust host library plus a small C shim for Lean FFI. The Rust layer uses a real HTTP client crate rather than spawning an external tool. `try ... with name => ...` handles `Error`, binds the caught error as an `Error` value, and removes `Error` from the resulting effect set when recovered locally. @@ -108,11 +118,12 @@ The host-side Nickel bridge currently maps evaluated Nickel values back into `ml - `null` - booleans - integers +- decimals - strings - arrays - records -Non-integer numeric Nickel values are not supported yet by `mlang`'s `Data` model. +Exact rational values are supported in native literals and in `Data` values coming from JSON or Nickel. Native `Data` literals are also supported directly in `mlang`: @@ -120,7 +131,28 @@ Native `Data` literals are also supported directly in `mlang`: { answer = 42, flags = [true, false], note = "ok", empty = null } ``` -Fields and array elements may be `Int`, `Bool`, `String`, or existing `Data` expressions. +Fields and array elements may be `Rational`, `Bool`, `String`, or existing `Data` expressions. + +`LazyReal` represents process-like numeric values that are observed through refinement. The current implementation supports decimal input to `sqrt`, while `approx` and `bounds` observe lazies as rationals: + +```mlang +let r = sqrt 2.0 +approx r 4 +bounds r 4 +approx pi 4 +toDecimal (approx pi 4) +``` + +Ordinary arithmetic lifts over `LazyReal`, so expressions like `sqrt 2.0 + 3.0` stay lazy until observed with `approx` or `bounds`. + +Stability observations classify how a number settles under rational and decimal observation: + +```mlang +rationalStability (1 / 3) -- Immediate +decimalStability (1 / 3) -- Periodic +lazyRationalStability pi -- Refining +lazyDecimalStability (sqrt 2) -- Refining +``` `pmap name in expr => body` expects `expr` to evaluate to a native `Data` array. It evaluates `body` concurrently for each element bound to `name`, preserves input order, and returns a native `List` of native `Result` values instead of failing the whole traversal. diff --git a/examples/samples.mlg b/examples/samples.mlg index 7e96aa9..461a6ae 100644 --- a/examples/samples.mlg +++ b/examples/samples.mlg @@ -1,4 +1,19 @@ let inc = fun (x : Int) => x + 1 in inc 41; +1.25; +1.25 = 1.250; +5 / 2; +1 + 2.5; +toInt 2; +toDecimal (1 / 2); +rationalStability (1 / 3); +decimalStability (1 / 3); +approx (sqrt 2.0) 4; +bounds (sqrt 2.0) 4; +approx (sqrt 2.0 + 3.0) 4; +approx pi 4; +approx (pi + 1.0) 4; +lazyRationalStability pi; +lazyDecimalStability (sqrt 2.0); let x = 10 in let f = fun (y : Int) => x + y in let x = 100 in f 5; if 6 * 7 = 42 then 1 else 0; 10 / 2; @@ -6,13 +21,16 @@ readFile "examples/sample.ncl"; readFileNickel "examples/sample.ncl"; parseNickel "{ answer = 42, flags = [true, false], note = \"ok\" }"; parseJson "{\"answer\":42,\"flags\":[true,false],\"note\":\"ok\",\"empty\":null}"; +parseJson "{\"price\":12.50,\"tax\":0.075}"; { answer = 42, flags = [true, false], note = "ok", empty = null }; +{ price = 12.50, tax = 0.075 }; toJson (parseNickel "{ answer = 42, flags = [true, false], note = \"ok\" }"); toJson (parseJson "{\"answer\":42,\"flags\":[true,false],\"note\":\"ok\",\"empty\":null}"); toJson { answer = 42, flags = [true, false], note = "ok", empty = null }; toNickel (parseJson "{\"answer\":42,\"flags\":[true,false],\"note\":\"ok\",\"odd key\":null}"); get (parseNickel "{ answer = 42, flags = [true, false] }") "answer"; get (parseJson "{\"answer\":42,\"flags\":[true,false]}") "answer"; +get (parseJson "{\"price\":12.50}") "price"; get { answer = 42, flags = [true, false] } "answer"; at (get (parseNickel "{ flags = [true, false] }") "flags") 1; -> "examples/sample.ncl" readFileNickel (get "answer"); diff --git a/native/mlang_http/Cargo.lock b/native/mlang_http/Cargo.lock index 9ed1ed8..55814c4 100644 --- a/native/mlang_http/Cargo.lock +++ b/native/mlang_http/Cargo.lock @@ -32,6 +32,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "1.0.0" @@ -82,18 +88,36 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "arraydeque" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "ascii-canvas" version = "4.0.0" @@ -103,6 +127,156 @@ dependencies = [ "term", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atproto-client" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c1438d67d1fe2d06b5e5f8030c58cce991e074bab03b5e226a949507172f24" +dependencies = [ + "anyhow", + "async-trait", + "atproto-identity", + "atproto-oauth", + "atproto-record", + "bytes", + "reqwest", + "reqwest-chain", + "reqwest-middleware", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "urlencoding", +] + +[[package]] +name = "atproto-dasl" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28d38577f468ab23e3351835f370cf34270caf290205a7e1520d352f5be4b28" +dependencies = [ + "blake3", + "cid", + "futures", + "multihash", + "serde", + "serde_bytes", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "atproto-identity" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722940aeee9654eef58cdd8b70d62772357621a2587367191b1c5630742f8e77" +dependencies = [ + "anyhow", + "async-trait", + "atproto-dasl", + "base64", + "chrono", + "data-encoding", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "hickory-resolver", + "idna", + "k256", + "lru", + "multibase", + "p256", + "p384", + "rand 0.10.1", + "reqwest", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "atproto-oauth" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9b1e34b59d7d1f641dd03a2b05b5153a2a4eb2ef1b36d6d7fbbf80a4aa954b" +dependencies = [ + "anyhow", + "async-trait", + "atproto-identity", + "base64", + "chrono", + "ecdsa", + "elliptic-curve", + "k256", + "lru", + "multibase", + "p256", + "p384", + "rand 0.10.1", + "reqwest", + "reqwest-chain", + "reqwest-middleware", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tracing", + "ulid", +] + +[[package]] +name = "atproto-record" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a83494a80a9add1c3678e821f92153a4b5d139c0b6898bbf5eb97501cf2359" +dependencies = [ + "anyhow", + "atproto-dasl", + "atproto-identity", + "base64", + "chrono", + "cid", + "multihash", + "rand 0.10.1", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "backtrace" version = "0.3.76" @@ -127,12 +301,40 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bincode" version = "1.3.3" @@ -169,6 +371,20 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -178,6 +394,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bon" version = "3.9.1" @@ -215,6 +440,12 @@ version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "caseless" version = "0.2.2" @@ -246,6 +477,38 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "cid" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a304f95f84d169a6f31c4d0a30d784643aaa0bbc9c1e449a2c23e963ec4971" +dependencies = [ + "multibase", + "multihash", + "unsigned-varint", +] + [[package]] name = "clap" version = "4.6.1" @@ -343,6 +606,30 @@ dependencies = [ "xdg", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -361,6 +648,22 @@ dependencies = [ "crossterm", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -370,6 +673,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -379,6 +691,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crokey" version = "1.4.0" @@ -488,6 +806,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -498,6 +828,42 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.23.0" @@ -532,6 +898,43 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -575,8 +978,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.2", ] [[package]] @@ -599,12 +1015,76 @@ dependencies = [ "litrs", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "base64ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serde_json", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "emojis" version = "0.6.4" @@ -623,6 +1103,15 @@ dependencies = [ "log", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endian-type" version = "0.1.2" @@ -635,6 +1124,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -648,7 +1149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -668,6 +1169,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "fd-lock" version = "4.0.4" @@ -676,9 +1183,25 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -830,6 +1353,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -839,8 +1363,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", ] [[package]] @@ -849,6 +1403,36 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -864,6 +1448,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", ] @@ -894,6 +1480,70 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.4", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.4", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" version = "0.5.12" @@ -903,6 +1553,116 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.7", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -985,6 +1745,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1042,6 +1808,25 @@ dependencies = [ "rustversion", ] +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is_ci" version = "1.2.0" @@ -1103,13 +1888,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.9", + "signature", +] + [[package]] name = "keccak" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -1167,6 +1966,12 @@ dependencies = [ "syn", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.186" @@ -1250,6 +2055,21 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "malachite" version = "0.9.1" @@ -1312,6 +2132,17 @@ dependencies = [ "serde", ] +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1319,7 +2150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -1358,6 +2189,22 @@ dependencies = [ "syn", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimad" version = "0.14.0" @@ -1393,11 +2240,54 @@ dependencies = [ name = "mlang_http" version = "0.1.0" dependencies = [ + "atproto-client", + "atproto-identity", + "atproto-record", "nickel-lang-core", + "reqwest", "serde_json", + "tokio", "ureq", ] +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" +dependencies = [ + "unsigned-varint", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1449,7 +2339,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha-1", - "sha2", + "sha2 0.10.9", "simple-counter", "smallvec", "strip-ansi-escapes", @@ -1518,6 +2408,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.37.3" @@ -1532,6 +2431,10 @@ name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -1591,6 +2494,32 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "serdect", + "sha2 0.10.9", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "serdect", + "sha2 0.10.9", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1620,6 +2549,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1666,6 +2604,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -1685,6 +2633,12 @@ dependencies = [ "time", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.5" @@ -1700,6 +2654,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1712,7 +2675,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "typed-arena", "unicode-width 0.2.2", ] @@ -1746,6 +2709,16 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", + "serdect", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1777,6 +2750,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.45" @@ -1786,6 +2814,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radix_trie" version = "0.2.1" @@ -1796,6 +2836,61 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rayon" version = "1.12.0" @@ -1854,6 +2949,92 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.7", +] + +[[package]] +name = "reqwest-chain" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5c014fb79a8227db44a0433d748107750d2550b7fca55c59a3d7ee7d2ee2b2" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest-middleware", +] + +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -1862,7 +3043,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1874,6 +3055,12 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1893,7 +3080,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1917,6 +3104,7 @@ version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ + "web-time", "zeroize", ] @@ -2010,6 +3198,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.28" @@ -2026,6 +3229,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -2069,6 +3282,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -2082,6 +3307,16 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -2089,8 +3324,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -2100,8 +3335,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -2110,7 +3356,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -2157,6 +3403,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -2187,6 +3443,26 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2276,6 +3552,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2298,15 +3583,55 @@ dependencies = [ "flate2", "fnv", "once_cell", - "onig", - "plist", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "thiserror", - "walkdir", - "yaml-rust", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.18", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -2339,7 +3664,7 @@ dependencies = [ "lazy-regex", "minimad", "serde", - "thiserror", + "thiserror 2.0.18", "unicode-width 0.1.14", ] @@ -2363,13 +3688,33 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2445,8 +3790,13 @@ version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ + "bytes", + "libc", + "mio", "pin-project-lite", + "socket2", "tokio-macros", + "windows-sys 0.61.2", ] [[package]] @@ -2460,6 +3810,29 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.9.12+spec-1.1.0" @@ -2528,7 +3901,7 @@ dependencies = [ "serde", "serde_json", "streaming-iterator", - "thiserror", + "thiserror 2.0.18", "tokio", "topiary-tree-sitter-facade", "topiary-web-tree-sitter-sys", @@ -2568,6 +3941,82 @@ dependencies = [ "web-sys", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "tree-sitter" version = "0.26.9" @@ -2599,6 +4048,12 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typed-arena" version = "2.0.2" @@ -2611,6 +4066,22 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.4", + "web-time", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2668,6 +4139,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "untrusted" version = "0.9.0" @@ -2702,6 +4179,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2714,6 +4197,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -2739,12 +4233,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2816,6 +4337,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -2826,6 +4381,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -2854,6 +4419,12 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -2876,7 +4447,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2891,6 +4462,35 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2997,6 +4597,100 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.3" @@ -3047,6 +4741,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.8" diff --git a/native/mlang_http/Cargo.toml b/native/mlang_http/Cargo.toml index 07d69f2..dd80ea7 100644 --- a/native/mlang_http/Cargo.toml +++ b/native/mlang_http/Cargo.toml @@ -7,6 +7,11 @@ edition = "2021" crate-type = ["staticlib"] [dependencies] +atproto-client = "0.14" +atproto-identity = "0.14" +atproto-record = "0.14" nickel-lang-core = "0.17" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde_json = "1" +tokio = { version = "1", features = ["rt-multi-thread"] } ureq = { version = "2.10", default-features = true } diff --git a/native/mlang_http/src/lib.rs b/native/mlang_http/src/lib.rs index 5a5d837..8f2fa1b 100644 --- a/native/mlang_http/src/lib.rs +++ b/native/mlang_http/src/lib.rs @@ -1,8 +1,13 @@ +use atproto_client::{HttpRecordResolver, RecordResolver}; +use atproto_identity::resolve::{HickoryDnsResolver, InnerIdentityResolver, SharedIdentityResolver}; +use atproto_record::aturi::ATURI; use nickel_lang_core::deserialize; use std::ffi::{CStr, CString}; use std::io::Read; use std::os::raw::c_char; use std::ptr; +use std::str::FromStr; +use std::sync::Arc; fn render_json_value(value: &serde_json::Value) -> Result { match value { @@ -13,14 +18,8 @@ fn render_json_value(value: &serde_json::Value) -> Result { Ok(i.to_string()) } else if let Some(u) = n.as_u64() { Ok(u.to_string()) - } else if let Some(f) = n.as_f64() { - if f.fract() == 0.0 { - Ok(format!("{:.0}", f)) - } else { - Err(format!("non-integer Nickel number is not supported: {n}")) - } } else { - Err(format!("non-integer Nickel number is not supported: {n}")) + Ok(n.to_string()) } } serde_json::Value::String(s) => serde_json::to_string(s) @@ -74,6 +73,28 @@ fn json_to_data_string(source: &str) -> Result { render_json_value(&value) } +fn aturi_to_data_string(source: &str) -> Result { + ATURI::from_str(source).map_err(|err| format!("{err}"))?; + let runtime = tokio::runtime::Runtime::new() + .map_err(|err| format!("failed to create Tokio runtime: {err}"))?; + let value: serde_json::Value = runtime + .block_on(async { + let http_client = reqwest::Client::new(); + let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&[])); + let identity_resolver = SharedIdentityResolver(Arc::new(InnerIdentityResolver { + dns_resolver, + http_client: http_client.clone(), + plc_hostname: "plc.directory".to_owned(), + })); + let resolver = HttpRecordResolver::new(http_client, Arc::new(identity_resolver)); + resolver.resolve(source).await + }) + .map_err(|err| format!("AT-URI lookup failed: {err}"))?; + let body = + serde_json::to_string(&value).map_err(|err| format!("failed to encode record: {err}"))?; + json_to_data_string(&body) +} + fn alloc_c_string(s: String) -> *mut c_char { CString::new(s) .unwrap_or_else(|_| CString::new("string contained interior NUL").unwrap()) @@ -256,3 +277,42 @@ pub extern "C" fn mlang_json_eval( } } } + +#[no_mangle] +pub extern "C" fn mlang_aturi_parse( + source: *const c_char, + out_body: *mut *mut c_char, + out_error: *mut *mut c_char, +) -> i32 { + if source.is_null() || out_body.is_null() || out_error.is_null() { + return 0; + } + unsafe { + *out_body = ptr::null_mut(); + *out_error = ptr::null_mut(); + } + let source = unsafe { CStr::from_ptr(source) }; + let source = match source.to_str() { + Ok(source) => source, + Err(err) => { + unsafe { + *out_error = alloc_c_string(format!("invalid AT-URI UTF-8: {err}")); + } + return 0; + } + }; + match aturi_to_data_string(source) { + Ok(rendered) => { + unsafe { + *out_body = alloc_c_string(rendered); + } + 1 + } + Err(err) => { + unsafe { + *out_error = alloc_c_string(format!("AT-URI parse error: {err}")); + } + 0 + } + } +} diff --git a/native/mlang_http_ffi.c b/native/mlang_http_ffi.c index bba9ef1..228f289 100644 --- a/native/mlang_http_ffi.c +++ b/native/mlang_http_ffi.c @@ -7,6 +7,7 @@ extern uint8_t mlang_http_get_body(const char *url, char **out_body, char **out_ extern uint8_t mlang_nickel_eval(const char *source, char **out_body, char **out_error); extern uint8_t mlang_nickel_eval_file(const char *path, char **out_body, char **out_error); extern uint8_t mlang_json_eval(const char *source, char **out_body, char **out_error); +extern uint8_t mlang_aturi_parse(const char *source, char **out_body, char **out_error); extern void mlang_http_string_free(char *ptr); extern lean_obj_res lean_mk_io_user_error(lean_obj_arg msg); @@ -74,3 +75,11 @@ LEAN_EXPORT lean_obj_res mlang_json_eval_string(b_lean_obj_arg source, lean_obj_ uint8_t ok = mlang_json_eval(lean_string_cstr(source), &body, &error); return wrap_string_result(ok, body, error); } + +LEAN_EXPORT lean_obj_res mlang_aturi_parse_string(b_lean_obj_arg source, lean_obj_arg world) { + (void)world; + char *body = NULL; + char *error = NULL; + uint8_t ok = mlang_aturi_parse(lean_string_cstr(source), &body, &error); + return wrap_string_result(ok, body, error); +}