Skip to content

Commit c375cb4

Browse files
authored
Merge pull request #840 from smucclaw/thomasgorissen/inspector
Thomasgorissen/inspector
2 parents 4629a9d + 64555a5 commit c375cb4

File tree

79 files changed

+4967
-910
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4967
-910
lines changed

.claude/settings.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"WebFetch(domain:github.com)",
5+
"WebFetch(domain:raw.githubusercontent.com)",
6+
"Bash(npx svelte-check --tsconfig ./tsconfig.json)",
7+
"Bash(npm run dev)",
8+
"Bash(npm run format)",
9+
"Bash(bash jl4-wasm/scripts/build-wasm.sh --all)",
10+
"Bash(node --experimental-wasm-modules -e \":*)",
11+
"Bash(node -e \":*)",
12+
"Bash(cabal exec ghci -- -package jl4-core -e ':*)",
13+
"Bash(cabal exec:*)",
14+
"Bash(source ~/.ghc-wasm/env)",
15+
"Bash(wasm32-wasi-cabal build jl4-wasm --project-file=cabal-wasm.project)",
16+
"Bash(bash jl4-wasm/scripts/build-wasm.sh)",
17+
"Bash(rm -rf static/wasm/)",
18+
"Bash(bash scripts/maybe-build-wasm.sh)",
19+
"Bash(npx svelte-check)",
20+
"Bash(npx vsce package --no-dependencies)"
21+
],
22+
"additionalDirectories": []
23+
}
24+
}

.gitbook.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ examples/
2222
hie.yaml
2323

2424
.vscode-test/
25+
26+
# Ignore generated WASM artifacts
27+
ts-apps/jl4-web/static/wasm/

IMG_0298.png

-466 KB
Binary file not shown.

doc/dev/specs/.gitignore

Lines changed: 0 additions & 3 deletions
This file was deleted.

jl4-core/libraries/daydate.l4

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ GIVEN day IS A NUMBER
5050
year IS A NUMBER
5151
GIVETH A DATE
5252
`Date` AKA `Days to date` MEANS
53-
DATE_FROM_DMY day month year
53+
DATE_FROM_SERIAL (jan1serial PLUS monthDays PLUS day MINUS 1)
54+
WHERE
55+
jan1serial MEANS DATE_SERIAL (DATE_FROM_DMY 1 1 year)
56+
monthDays MEANS `Months since year start to days` month year
5457

5558
GIVEN days IS A NUMBER
5659
GIVETH A DATE
@@ -92,7 +95,7 @@ GIVEN day IS A NUMBER
9295
year IS A NUMBER
9396
GIVETH A NUMBER
9497
`Day` AKA `Date to days` MEANS
95-
DATE_SERIAL (DATE_FROM_DMY day month year)
98+
DATE_SERIAL (Date day month year)
9699

97100
GIVEN days IS A NUMBER
98101
GIVETH A NUMBER

jl4-core/libraries/tests/daydate.ep.golden

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ GIVEN day IS A NUMBER
5050
year IS A NUMBER
5151
GIVETH A DATE
5252
`Date` AKA `Days to date` MEANS
53-
DATE_FROM_DMY day month year
53+
DATE_FROM_SERIAL (jan1serial PLUS monthDays PLUS day MINUS 1)
54+
WHERE
55+
jan1serial MEANS DATE_SERIAL (DATE_FROM_DMY 1 1 year)
56+
monthDays MEANS `Months since year start to days` month year
5457

5558
GIVEN days IS A NUMBER
5659
GIVETH A DATE
@@ -92,7 +95,7 @@ GIVEN day IS A NUMBER
9295
year IS A NUMBER
9396
GIVETH A NUMBER
9497
`Day` AKA `Date to days` MEANS
95-
DATE_SERIAL (DATE_FROM_DMY day month year)
98+
DATE_SERIAL (Date day month year)
9699

97100
GIVEN days IS A NUMBER
98101
GIVETH A NUMBER

jl4-core/src/L4/API.hs

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3132
import Data.Aeson ((.=))
3233
import qualified Data.Text.Lazy as LazyText
3334
import qualified Data.Text.Lazy.Encoding as LazyText
35+
import qualified Data.List as List
3436
import qualified Data.Vector as Vector
3537

3638
import L4.Lexer (execLexer, PosToken(..), TokenType(..), TLiterals(..), TSpaces(..), TIdentifiers(..))
3739

38-
import L4.Annotation (HasSrcRange(..))
40+
import L4.Annotation (HasSrcRange(..), rangeOfNode)
3941
import L4.Parser.SrcSpan (SrcRange(..), SrcPos(..))
4042
import L4.TypeCheck (severity, prettyCheckErrorWithContext, applyFinalSubstitution)
4143
import L4.TypeCheck.Types (CheckResult(..), CheckErrorWithContext(..), Severity(..), Substitution)
4244
import L4.Print (prettyLayout)
43-
import L4.Syntax (Info(..), Type'(..), Resolved, OptionallyNamedType(..))
45+
import L4.Syntax (Info(..), Type'(..), Resolved, OptionallyNamedType(..), TopDecl(..), Directive(..), Module(..), Section(..))
4446
import L4.Annotation (emptyAnno)
4547
import qualified L4.Utils.IntervalMap as IV
4648
import 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.
253303
evaluateImports :: 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.
309368
foreign 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:

jl4-core/src/L4/EvaluateLazy/Machine.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1882,7 +1882,9 @@ runTernaryBuiltin TernaryDateFromDMY dVal mVal yVal = do
18821882
case Time.fromGregorianValid yInt (fromInteger mInt) (fromInteger dInt) of
18831883
Just day -> Backward (ValDate day)
18841884
Nothing ->
1885-
UserException $ UserError "DATE_FROM_DMY produced an invalid date"
1885+
UserException $ UserError $
1886+
"DATE_FROM_DMY produced an invalid date from day="
1887+
<> Text.pack (show dInt) <> ", month=" <> Text.pack (show mInt) <> ", year=" <> Text.pack (show yInt)
18861888
runTernaryBuiltin TernaryTimeFromHMS hVal mVal sVal = do
18871889
hNum <- expectNumber hVal
18881890
mNum <- expectNumber mVal

jl4-core/src/L4/TypeCheck.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ inferDirective (LazyEvalTrace ann e) = errorContext (WhileCheckingExpression e)
411411
pure (LazyEvalTrace ann re)
412412
inferDirective (Check ann e) = errorContext (WhileCheckingExpression e) do
413413
(re, te) <- prune $ inferExpr e
414-
addError (CheckInfo te)
414+
addError (CheckInfo te (rangeOf ann))
415415
pure (Check ann re)
416416
inferDirective (Contract ann e t evs) = errorContext (WhileCheckingExpression e) do
417417
partyT <- fresh (NormalName "party")
@@ -3108,7 +3108,7 @@ prettyCheckError (IncompleteAppNamed r onts) =
31083108
, "you forgot to supply the following arguments:"
31093109
, ""
31103110
] ++ map (\ ont -> " " <> prettyOptionallyNamedType ont) onts
3111-
prettyCheckError (CheckInfo t) = [prettyLayout t]
3111+
prettyCheckError (CheckInfo t _) = [prettyLayout t]
31123112
prettyCheckError (IllegalTypeInKindSignature t) =
31133113
[ "In a signature of a type declaration, all parameters must be of type"
31143114
, ""

0 commit comments

Comments
 (0)