Skip to content

Commit fa0f096

Browse files
committed
[feat] Add support to object string properties
1 parent d82d2f9 commit fa0f096

File tree

5 files changed

+168
-44
lines changed

5 files changed

+168
-44
lines changed

Strata/DL/Heap/HEval.lean

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ partial def evalApp (state : HState) (originalExpr e1 e2 : HExpr) : HState × HE
149149
-- Third application to DynamicFieldAssign - now we can evaluate
150150
-- This handles obj[field] = value where field is dynamic
151151
evalDynamicFieldAssign state2 objExpr fieldExpr e2'
152+
| .deferredOp "DynamicFieldAssignReturnObj" _ =>
153+
-- First application to DynamicFieldAssignReturnObj - return partially applied
154+
(state2, .app (.deferredOp "DynamicFieldAssignReturnObj" none) e2')
155+
| .app (.deferredOp "DynamicFieldAssignReturnObj" _) objExpr =>
156+
-- Second application to DynamicFieldAssignReturnObj - return partially applied
157+
(state2, .app (.app (.deferredOp "DynamicFieldAssignReturnObj" none) objExpr) e2')
158+
| .app (.app (.deferredOp "DynamicFieldAssignReturnObj" _) objExpr) fieldExpr =>
159+
-- Third application to DynamicFieldAssignReturnObj - now we can evaluate
160+
evalDynamicFieldAssignReturnObj state2 objExpr fieldExpr e2'
152161
| .deferredOp "StringFieldAccess" _ =>
153162
-- First application to StringFieldAccess - return partially applied
154163
(state2, .app (.deferredOp "StringFieldAccess" none) e2')
@@ -179,25 +188,69 @@ partial def evalApp (state : HState) (originalExpr e1 e2 : HExpr) : HState × HE
179188

180189
-- Handle dynamic field access: obj[field] where field is dynamic
181190
partial def evalDynamicFieldAccess (state : HState) (objExpr fieldExpr : HExpr) : HState × HExpr :=
182-
-- First try to extract a numeric field index from the field expression
183-
match extractFieldIndex fieldExpr with
184-
| some fieldIndex =>
185-
-- We have a numeric field index, use regular deref
186-
evalHExpr state (.deref objExpr fieldIndex)
187-
| none =>
188-
-- Can't extract a numeric field index, return error
189-
(state, .lambda (LExpr.const "error_dynamic_field_access_failed" none))
191+
let (s1, objVal) := evalHExpr state objExpr
192+
let (s2, keyVal) := evalHExpr s1 fieldExpr
193+
match objVal with
194+
| .address addr =>
195+
match keyVal with
196+
| .lambda (LExpr.const k _) =>
197+
match k.toNat? with
198+
| some n =>
199+
match s2.getField addr n with
200+
| some v => (s2, v)
201+
| none => (s2, .lambda (LExpr.const s!"error_field_{n}_not_found" none))
202+
| none =>
203+
match s2.getStringField addr k with
204+
| some v => (s2, v)
205+
| none => (s2, .lambda (LExpr.const s!"error_string_field_{k}_not_found" none))
206+
| _ => (s2, .lambda (LExpr.const "error_invalid_field_key" none))
207+
| .null => (s2, .lambda (LExpr.const "error_null_dereference" none))
208+
| _ => (s2, .lambda (LExpr.const "error_invalid_address" none))
190209

191210
-- Handle dynamic field assignment: obj[field] = value where field is dynamic
192211
partial def evalDynamicFieldAssign (state : HState) (objExpr fieldExpr valueExpr : HExpr) : HState × HExpr :=
193-
-- First try to extract a numeric field index from the field expression
194-
match extractFieldIndex fieldExpr with
195-
| some fieldIndex =>
196-
-- We have a numeric field index, use regular assign
197-
evalHExpr state (.assign objExpr fieldIndex valueExpr)
198-
| none =>
199-
-- Can't extract a numeric field index, return error
200-
(state, .lambda (LExpr.const "error_dynamic_field_assign_failed" none))
212+
let (s1, objVal) := evalHExpr state objExpr
213+
let (s2, keyVal) := evalHExpr s1 fieldExpr
214+
let (s3, valVal) := evalHExpr s2 valueExpr
215+
match objVal with
216+
| .address addr =>
217+
match keyVal with
218+
| .lambda (LExpr.const k _) =>
219+
match k.toNat? with
220+
| some n =>
221+
match s3.setField addr n valVal with
222+
| some s' => (s', valVal)
223+
| none => (s3, .lambda (LExpr.const s!"error_cannot_update_field_{n}" none))
224+
| none =>
225+
match s3.setStringField addr k valVal with
226+
| some s' => (s', valVal)
227+
| none => (s3, .lambda (LExpr.const s!"error_cannot_update_string_field_{k}" none))
228+
| _ => (s3, .lambda (LExpr.const "error_invalid_field_key" none))
229+
| .null => (s3, .lambda (LExpr.const "error_null_assignment" none))
230+
| _ => (s3, .lambda (LExpr.const "error_invalid_address_assignment" none))
231+
232+
-- Handle dynamic field assignment but return the *object address* to allow chaining
233+
partial def evalDynamicFieldAssignReturnObj (state : HState) (objExpr fieldExpr valueExpr : HExpr) : HState × HExpr :=
234+
let (s1, objVal) := evalHExpr state objExpr
235+
let (s2, keyVal) := evalHExpr s1 fieldExpr
236+
let (s3, valVal) := evalHExpr s2 valueExpr
237+
match objVal with
238+
| .address addr =>
239+
match keyVal with
240+
| .lambda (LExpr.const k _) =>
241+
match k.toNat? with
242+
| some n =>
243+
match s3.setField addr n valVal with
244+
| some s' => (s', .address addr)
245+
| none => (s3, .lambda (LExpr.const s!"error_cannot_update_field_{n}" none))
246+
| none =>
247+
match s3.setStringField addr k valVal with
248+
| some s' => (s', .address addr)
249+
| none => (s3, .lambda (LExpr.const s!"error_cannot_update_string_field_{k}" none))
250+
| _ => (s3, .lambda (LExpr.const "error_invalid_field_key" none))
251+
| .null => (s3, .lambda (LExpr.const "error_null_assignment" none))
252+
| _ => (s3, .lambda (LExpr.const "error_invalid_address_assignment" none))
253+
201254

202255
-- Handle string field access: str.fieldName where fieldName is a string literal
203256
partial def evalStringFieldAccess (state : HState) (objExpr fieldExpr : HExpr) : HState × HExpr :=
@@ -223,7 +276,6 @@ partial def evalStringFieldAccess (state : HState) (objExpr fieldExpr : HExpr) :
223276
partial def extractFieldIndex (expr : HExpr) : Option Nat :=
224277
match expr with
225278
| .lambda (LExpr.const s _) =>
226-
-- Try to parse the string as a natural number
227279
s.toNat?
228280
| _ => none
229281

@@ -253,6 +305,25 @@ namespace HExpr
253305
def allocSimple (fields : List (Nat × HExpr)) : HExpr :=
254306
.alloc (HMonoTy.mkObj fields.length HMonoTy.int) fields
255307

308+
def dynamicAssign (obj : HExpr) (key : HExpr) (val : HExpr) : HExpr :=
309+
HExpr.app
310+
(HExpr.app
311+
(HExpr.app (HExpr.deferredOp "DynamicFieldAssign" none) obj)
312+
key)
313+
val
314+
315+
/-- Like `dynamicAssign`, but evaluates to the *object* so it can be chained. --/
316+
def dynamicAssignReturnObj (obj : HExpr) (key : HExpr) (val : HExpr) : HExpr :=
317+
HExpr.app
318+
(HExpr.app
319+
(HExpr.app (HExpr.deferredOp "DynamicFieldAssignReturnObj" none) obj)
320+
key)
321+
val
322+
323+
def dynamicAlloc (numFields : List (Nat × HExpr)) (dynFields : List (HExpr × HExpr)) : HExpr :=
324+
let obj0 := allocSimple numFields
325+
dynFields.foldl (fun acc (k, v) => dynamicAssignReturnObj acc k v) obj0
326+
256327
end HExpr
257328

258329
end Heap

Strata/DL/Heap/HState.lean

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ abbrev HeapMemory := Std.HashMap Address HeapObject
3434
-- Heap variable environment - maps variable names to (type, value) pairs
3535
abbrev HeapVarEnv := Std.HashMap String (HMonoTy × HExpr)
3636

37+
-- String-keyed fields for objects
38+
abbrev StringFieldMap := Std.HashMap String HExpr
39+
abbrev StringFields := Std.HashMap Nat StringFieldMap
40+
3741
/--
3842
Heap State extending Lambda state with heap memory and heap variable environment.
3943
-/
@@ -46,9 +50,11 @@ structure HState where
4650
nextAddr : Address
4751
-- Heap variable environment (parallel to Lambda's variable environment)
4852
heapVars : HeapVarEnv
53+
-- String-keyed fields: address -> (key -> value)
54+
stringFields : StringFields
4955

5056
instance : Repr HState where
51-
reprPrec s _ := s!"HState(nextAddr: {s.nextAddr}, heap: <{s.heap.size} objects>, heapVars: <{s.heapVars.size} vars>)"
57+
reprPrec s _ := s!"HState(nextAddr: {s.nextAddr}, heap: <{s.heap.size} objects>, heapVars: <{s.heapVars.size} vars>, stringFields: <{s.stringFields.size} props>)"
5258

5359
namespace HState
5460

@@ -81,9 +87,10 @@ def empty : HState :=
8187
| .ok state => state
8288
| .error _ => lambdaState -- Fallback to basic state if factory addition fails
8389
{ lambdaState := lambdaStateWithFactory,
84-
heap := Std.HashMap.empty,
90+
heap := Std.HashMap.emptyWithCapacity,
8591
nextAddr := 1, -- Start addresses from 1 (0 can represent null)
86-
heapVars := Std.HashMap.empty }
92+
heapVars := Std.HashMap.emptyWithCapacity,
93+
stringFields := Std.HashMap.emptyWithCapacity }
8794

8895
-- Heap Variable Environment Operations
8996

@@ -161,6 +168,18 @@ def setField (state : HState) (addr : Address) (field : Nat) (value : HExpr) : O
161168
some { state with heap := newHeap }
162169
| none => none
163170

171+
-- String field operations (for objects with string-keyed fields)
172+
def getStringField (s : HState) (addr : Nat) (key : String) : Option HExpr :=
173+
match s.stringFields.get? addr with
174+
| some m => m.get? key
175+
| none => none
176+
177+
-- Set a string field in an object
178+
def setStringField (s : HState) (addr : Nat) (key : String) (val : HExpr) : Option HState :=
179+
let existing : StringFieldMap := (s.stringFields.get? addr).getD (Std.HashMap.emptyWithCapacity)
180+
let updated := existing.insert key val
181+
some { s with stringFields := s.stringFields.insert addr updated }
182+
164183
-- Check if an address is valid (exists in heap)
165184
def isValidAddr (state : HState) (addr : Address) : Bool :=
166185
state.heap.contains addr
@@ -186,6 +205,29 @@ def heapVarsToString (state : HState) : String :=
186205
def toString (state : HState) : String :=
187206
s!"{state.heapToString}\n{state.heapVarsToString}"
188207

208+
-- Return all string-keyed fields for an address as an association list
209+
def getAllStringFields (s : HState) (addr : Nat) : List (String × HExpr) :=
210+
match s.stringFields.get? addr with
211+
| some m => m.toList
212+
| none => []
213+
214+
-- Return all fields (numeric and string) as string-keyed pairs
215+
def getAllFieldsAsStringPairs (s : HState) (addr : Nat) : List (String × HExpr) :=
216+
let numeric : List (String × HExpr) :=
217+
match s.getObject addr with
218+
| some obj => obj.toList.map (fun (i, v) => (s!"{i}", v))
219+
| none => []
220+
let strk : List (String × HExpr) := s.getAllStringFields addr
221+
-- string keys should override numeric if the same string exists
222+
let merged := Id.run do
223+
let mut map : Std.HashMap String HExpr := Std.HashMap.emptyWithCapacity
224+
for (k, v) in numeric do
225+
map := map.insert k v
226+
for (k, v) in strk do
227+
map := map.insert k v
228+
pure map
229+
merged.toList
230+
189231
end HState
190232

191233
end Heap

Strata/DL/Heap/HeapStrataEval.lean

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,16 @@ mutual
151151
| _ => none -- Other expressions not supported yet
152152

153153
partial def extractObjectAsJson (state : HState) (addr : Address) : Option Lean.Json :=
154-
match state.getObject addr with
155-
| some obj =>
156-
-- Convert heap object (field index -> HExpr) to JSON object
157-
let fields := obj.toList
158-
let jsonFields := fields.filterMap fun (fieldIndex, fieldExpr) =>
159-
match extractConcreteValueWithState state fieldExpr with
160-
| some fieldJson =>
161-
-- Use field index as string key for now
162-
some (toString fieldIndex, fieldJson)
163-
| none => none
164-
165-
-- Sort fields by key for consistency
166-
let sortedFields := jsonFields.toArray.qsort (fun a b => a.1 < b.1) |>.toList
167-
some (Lean.Json.mkObj sortedFields)
168-
| none => none
154+
-- Merge numeric fields and string-keyed fields from the heap
155+
let pairs : List (String × HExpr) := state.getAllFieldsAsStringPairs addr
156+
-- Encode each field value recursively
157+
let jsonFields : List (String × Lean.Json) := pairs.filterMap fun (k, v) =>
158+
match extractConcreteValueWithState state v with
159+
| some j => some (k, j)
160+
| none => none
161+
-- Sort for stable output
162+
let sortedFields := jsonFields.toArray.qsort (fun a b => a.1 < b.1) |>.toList
163+
some (Lean.Json.mkObj sortedFields)
169164

170165
-- Legacy function for backward compatibility
171166
partial def extractConcreteValue (expr : HExpr) : Option String :=

Strata/Languages/TypeScript/TS_to_Strata.lean

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,25 @@ partial def translate_expr (e: TS_Expression) : Heap.HExpr :=
191191
Heap.HExpr.app (Heap.HExpr.app (Heap.HExpr.deferredOp "DynamicFieldAccess" none) objExpr) fieldExpr
192192

193193
| .TS_ObjectExpression e =>
194-
-- Translate {1: value1, 5: value5} to heap allocation
195-
let fields := e.properties.toList.map (fun prop =>
196-
let key := match prop.key with
197-
| .TS_NumericLiteral numLit => Float.floor numLit.value |>.toUInt64.toNat
198-
| _ => panic! s!"Expected numeric literal as object key, got: {repr prop.key}"
199-
let value := translate_expr prop.value
200-
(key, value))
201-
-- Use allocSimple which handles the object type automatically
202-
Heap.HExpr.allocSimple fields
194+
-- Collect numeric props for allocSimple, and *one* list of dynamic (keyExpr,valueExpr)
195+
let (numProps, dynProps) :=
196+
e.properties.toList.foldl
197+
(fun (ns, ds) prop =>
198+
let v := translate_expr prop.value
199+
match prop.key with
200+
| .TS_NumericLiteral numLit =>
201+
let idx := Float.floor numLit.value |>.toUInt64.toNat
202+
((idx, v) :: ns, ds)
203+
| .TS_StringLiteral strLit =>
204+
-- unify: string-literal key becomes a constant string expression
205+
(ns, (Heap.HExpr.string strLit.value, v) :: ds)
206+
| other =>
207+
-- computed or identifier: translate to an expr
208+
(ns, (translate_expr other, v) :: ds))
209+
([], [])
210+
211+
let obj := Heap.HExpr.dynamicAlloc (numProps.reverse) (dynProps.reverse)
212+
obj
203213

204214
| .TS_CallExpression call =>
205215
-- Handle function calls - translate to expressions for now
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Test objects with string key-value
2+
let str_obj = {
3+
"name": "Strata",
4+
"type": "Testing",
5+
"version": "1.0"
6+
}

0 commit comments

Comments
 (0)