@@ -21,6 +21,7 @@ module L4.API
2121 , l4Eval
2222 , l4VisualizeByName
2323 , l4CodeLenses
24+ , l4EvalDirective
2425 , l4Definition
2526 , l4References
2627 ) where
@@ -31,16 +32,17 @@ import qualified Data.Aeson as Aeson
3132import Data.Aeson ((.=) )
3233import qualified Data.Text.Lazy as LazyText
3334import qualified Data.Text.Lazy.Encoding as LazyText
35+ import qualified Data.List as List
3436import qualified Data.Vector as Vector
3537
3638import L4.Lexer (execLexer , PosToken (.. ), TokenType (.. ), TLiterals (.. ), TSpaces (.. ), TIdentifiers (.. ))
3739
38- import L4.Annotation (HasSrcRange (.. ))
40+ import L4.Annotation (HasSrcRange (.. ), rangeOfNode )
3941import L4.Parser.SrcSpan (SrcRange (.. ), SrcPos (.. ))
4042import L4.TypeCheck (severity , prettyCheckErrorWithContext , applyFinalSubstitution )
4143import L4.TypeCheck.Types (CheckResult (.. ), CheckErrorWithContext (.. ), Severity (.. ), Substitution )
4244import L4.Print (prettyLayout )
43- import L4.Syntax (Info (.. ), Type' (.. ), Resolved , OptionallyNamedType (.. ))
45+ import L4.Syntax (Info (.. ), Type' (.. ), Resolved , OptionallyNamedType (.. ), TopDecl ( .. ), Directive ( .. ), Module ( .. ), Section ( .. ) )
4446import L4.Annotation (emptyAnno )
4547import qualified L4.Utils.IntervalMap as IV
4648import qualified L4.EvaluateLazy as EL
@@ -226,12 +228,14 @@ l4Eval source =
226228 , " diagnostics" .= Vector. fromList (map importErrorToDiagnostic errors)
227229 ]
228230 Right result ->
229- if not (null result. tcdErrors)
231+ -- Only fail on actual errors; SInfo items (e.g. #CHECK type annotations) are informational
232+ let actualErrors = filter (\ e -> severity e /= SInfo ) result. tcdErrors
233+ in if not (null actualErrors)
230234 then
231235 -- Return type check errors as diagnostics
232236 pure $ encodeJson $ Aeson. object
233237 [ " success" .= False
234- , " diagnostics" .= Vector. fromList (map checkErrorToDiagnostic result . tcdErrors )
238+ , " diagnostics" .= Vector. fromList (map checkErrorToDiagnostic actualErrors )
235239 ]
236240 else do
237241 -- First, evaluate all imported modules to build up the evaluation environment
@@ -248,6 +252,52 @@ l4Eval source =
248252 , " results" .= Vector. fromList (map evalResultToJson results)
249253 ]
250254
255+ -- | Evaluate a specific directive at a given source position.
256+ --
257+ -- Returns JSON-encoded DirectiveResult:
258+ -- @
259+ -- {
260+ -- "directiveType": "#EVAL",
261+ -- "prettyText": "\"String\"",
262+ -- "success": null,
263+ -- "structuredValue": "String"
264+ -- }
265+ -- @
266+ --
267+ -- Or an error:
268+ -- @
269+ -- { "error": "No directive result found at the given position" }
270+ -- @
271+ l4EvalDirective :: Text -> Int -> Int -> Text -> IO Text
272+ l4EvalDirective source line col directiveType =
273+ case checkWithImports emptyVFS source of
274+ Left _errors ->
275+ pure $ encodeJson $ Aeson. object
276+ [ " error" .= (" Parse/import errors prevent evaluation" :: Text ) ]
277+ Right result -> do
278+ evalConfig <- EL. resolveEvalConfigWithSafeMode Nothing (lspDefaultPolicy defaultGraphVizOptions) True
279+ importEnv <- evaluateImports evalConfig result. tcdResolvedImports
280+ (_env, results) <- EL. execEvalModuleWithEnv evalConfig result. tcdEntityInfo importEnv result. tcdModule
281+ let targetPos = MkSrcPos line col
282+ matchesPos (EL. MkEvalDirectiveResult rng _ _) = fmap (. start) rng == Just targetPos
283+ matchingResult = List. find matchesPos results
284+ case matchingResult of
285+ Nothing ->
286+ pure $ encodeJson $ Aeson. object
287+ [ " error" .= (" No directive result found at the given position" :: Text ) ]
288+ Just (EL. MkEvalDirectiveResult _range res _mtrace) ->
289+ pure $ encodeJson $ Aeson. object
290+ [ " directiveType" .= directiveType
291+ , " prettyText" .= prettyEvalResult res
292+ , " success" .= case res of
293+ EL. Assertion b -> Aeson. toJSON b
294+ EL. Reduction _ -> Aeson. Null
295+ , " structuredValue" .= case res of
296+ EL. Assertion b -> Aeson. toJSON b
297+ EL. Reduction (Left _) -> Aeson. Null
298+ EL. Reduction (Right nf) -> Aeson. toJSON (prettyLayout nf)
299+ ]
300+
251301-- | Evaluate a list of resolved imports and combine their environments.
252302-- Returns the combined evaluation environment.
253303evaluateImports :: EL. EvalConfig -> [ResolvedImport ] -> IO ValLazy. Environment
@@ -305,6 +355,15 @@ js_l4_eval source = do
305355 result <- l4Eval $ Text. pack $ fromJSString source
306356 pure $ toJSString $ Text. unpack result
307357
358+ -- | Evaluate a specific directive at a position.
359+ foreign export javascript " l4_eval_directive"
360+ js_l4_eval_directive :: JSString -> Int -> Int -> JSString -> IO JSString
361+
362+ js_l4_eval_directive :: JSString -> Int -> Int -> JSString -> IO JSString
363+ js_l4_eval_directive source line col directiveType = do
364+ result <- l4EvalDirective (Text. pack $ fromJSString source) line col (Text. pack $ fromJSString directiveType)
365+ pure $ toJSString $ Text. unpack result
366+
308367-- | Generate ladder diagram visualization for a specific function by name.
309368foreign export javascript " l4_visualize_by_name"
310369 js_l4_visualize_by_name :: JSString -> JSString -> Int -> JSString -> Bool -> IO JSString
@@ -536,25 +595,15 @@ l4CodeLenses source uriText version =
536595 -- Parse/import errors - return empty array (diagnostics shown separately)
537596 encodeJson ([] :: [Aeson. Value ])
538597 Right result ->
539- if not (null result. tcdErrors)
540- then
541- -- Type check errors - return empty array (diagnostics shown separately)
542- encodeJson ([] :: [Aeson. Value ])
543- else
544- -- Use the URI from type checking result to ensure substitution lookup works correctly
545- let decides = Ladder. findAllVisualizableDecides result. tcdUri result. tcdModule result. tcdSubstitution
546- lenses = concatMap (mkCodeLenses uriText version) decides
547- in encodeJson lenses
598+ -- Generate lenses even when there are type check errors/warnings
599+ -- (the LSP does the same — CodeLens is always shown after successful parse)
600+ let decides = Ladder. findAllVisualizableDecides result. tcdUri result. tcdModule result. tcdSubstitution
601+ vizLenses = map (mkVizLens uriText version) decides
602+ directiveLenses = collectDirectiveLenses (directiveToCodeLens uriText version) result. tcdModule
603+ in encodeJson (vizLenses <> directiveLenses)
548604 where
549- mkCodeLenses :: Text -> Int -> Ladder. VisualizableDecide -> [Aeson. Value ]
550- mkCodeLenses uriTxt ver vd =
551- -- Generate two code lenses: "Visualize" and "Simplify and visualize"
552- [ mkCodeLens uriTxt ver vd False
553- , mkCodeLens uriTxt ver vd True
554- ]
555-
556- mkCodeLens :: Text -> Int -> Ladder. VisualizableDecide -> Bool -> Aeson. Value
557- mkCodeLens uriTxt ver vd simplify = Aeson. object
605+ mkVizLens :: Text -> Int -> Ladder. VisualizableDecide -> Aeson. Value
606+ mkVizLens uriTxt ver vd = Aeson. object
558607 [ " range" .= Aeson. object
559608 -- Monaco uses 1-indexed positions
560609 [ " startLineNumber" .= vd. vdStartLine
@@ -564,15 +613,63 @@ l4CodeLenses source uriText version =
564613 ]
565614 , " command" .= Aeson. object
566615 [ " id" .= (" l4.visualize" :: Text )
567- , " title" .= if simplify then ( " Simplify and visualize " :: Text ) else ( " Visualize " :: Text )
568- , " arguments" .=
616+ , " title" .= ( " Show decision graph " :: Text )
617+ , " arguments" .=
569618 [ Aeson. object [ " uri" .= uriTxt, " version" .= ver ]
570619 , Aeson. String vd. vdName
571- , Aeson. Bool simplify
620+ , Aeson. Bool False
572621 ]
573622 ]
574623 ]
575624
625+ directiveLabel :: Directive Resolved -> Text
626+ directiveLabel = \ case
627+ LazyEval {} -> " #EVAL"
628+ LazyEvalTrace {} -> " #EVALTRACE"
629+ Check {} -> " #CHECK"
630+ Contract {} -> " #CHECK"
631+ Assert {} -> " #ASSERT"
632+
633+ directiveToCodeLens :: Text -> Int -> TopDecl Resolved -> [Aeson. Value ]
634+ directiveToCodeLens uriTxt ver = \ case
635+ Directive _ d ->
636+ case rangeOfNode d of
637+ Just rng ->
638+ let sp = rng. start
639+ in [ Aeson. object
640+ [ " range" .= Aeson. object
641+ [ " startLineNumber" .= sp. line
642+ , " startColumn" .= sp. column
643+ , " endLineNumber" .= sp. line
644+ , " endColumn" .= sp. column
645+ ]
646+ , " command" .= Aeson. object
647+ [ " id" .= (" l4.renderResult" :: Text )
648+ , " title" .= (" Track result" :: Text )
649+ , " arguments" .=
650+ [ Aeson. object [ " uri" .= uriTxt, " version" .= ver ]
651+ , Aeson. object [ " line" .= sp. line, " column" .= sp. column ]
652+ , Aeson. String (directiveLabel d)
653+ ]
654+ ]
655+ ]
656+ ]
657+ _ -> []
658+ _ -> []
659+
660+ -- | Recursively collect directive lenses from all sections (including nested ones).
661+ -- foldTopDecls only traverses one level, but directives can be inside nested Sections.
662+ collectDirectiveLenses :: (TopDecl Resolved -> [Aeson. Value ]) -> Module Resolved -> [Aeson. Value ]
663+ collectDirectiveLenses f (MkModule _ _ section) = collectFromSection f section
664+
665+ collectFromSection :: (TopDecl Resolved -> [Aeson. Value ]) -> Section Resolved -> [Aeson. Value ]
666+ collectFromSection f (MkSection _ _ _ topDecls) = concatMap (collectFromTopDecl f) topDecls
667+
668+ collectFromTopDecl :: (TopDecl Resolved -> [Aeson. Value ]) -> TopDecl Resolved -> [Aeson. Value ]
669+ collectFromTopDecl f td = f td ++ case td of
670+ Section _ s -> collectFromSection f s
671+ _ -> []
672+
576673-- | Go to definition at a position.
577674--
578675-- Returns JSON-encoded location or null:
0 commit comments