Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A CEK Evaluator #223

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

doyougnu
Copy link
Collaborator

@doyougnu doyougnu commented Jan 14, 2024

Still a draft but all but one test is passing.

TODOs:

  • A more robust note
  • add proper error handling
  • rebase onto main
  • remove the old evaluator

@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch 2 times, most recently from fa83318 to ea1aea6 Compare January 14, 2024 00:20
@gelisam
Copy link
Owner

gelisam commented Jan 14, 2024

I'm experimenting with an AI-based code review tool called "CodeRabbit", please let me know if its comments are annoying and I'll turn it off.

@gelisam gelisam added the CodeRabbit summons the AI reviewing bot label Jan 20, 2024
@gelisam gelisam mentioned this pull request Jan 20, 2024
Copy link

coderabbitai bot commented Feb 12, 2024

Warning

Rate limit exceeded

@doyougnu has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 50 minutes and 39 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Commits

Files that changed from the base of the PR and between 906a85e and d3cca7a.

Walkthrough

The recent updates enhance the evaluation process by introducing a new evaluateIn function, restructuring the Evaluator module with new data types and functions, and refining error handling. These changes improve the CEK interpreter's logic and control flow. Additionally, the Expander module sees function renaming and refactoring, while the Pretty module adds a new error case for better error messaging.

Changes

Files Change Summaries
repl/Main.hs Replaced runExceptT and runReaderT with evaluateIn; updated error handling and result printing logic.
src/Evaluator.hs Added language extensions, new data types (EvalResult, Kont, EState), and multiple new functions for CEK interpreter logic.
src/Expander.hs, .../Monad.hs Renamed expandEval to evalInCurrentPhase; refactored functions and adjusted error handling.
src/Pretty.hs Added error case for EvalErrorIdent in Pretty VarInfo EvalError instance.

Sequence Diagram(s) (Beta)

sequenceDiagram
    participant User
    participant REPL
    participant Evaluator
    participant Expander

    User->>REPL: Enter expression
    REPL->>Evaluator: evaluateIn(expression)
    Evaluator->>Expander: evalInCurrentPhase(expression)
    Expander-->>Evaluator: Expanded expression
    Evaluator-->>REPL: Evaluated result
    REPL-->>User: Display result
Loading

Poem

🐇 In the realm of code so grand,
A rabbit hops with changes planned.
Evaluations now refined,
Errors clearer, functions aligned.
With evaluateIn we stride,
Through logic’s flow, we now glide.
Hopping forward, side by side. 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@doyougnu
Copy link
Collaborator Author

Should be ready for review now. Its a bit slower than what we had. Why is that? Well:

$  perf stat -- cabal test --test-show-details=streaming
 Performance counter stats for 'cabal test --test-show-details=streaming':

         18,349.25 msec task-clock:u                     #    0.996 CPUs utilized          
                 0      context-switches:u               #    0.000 /sec                   
                 0      cpu-migrations:u                 #    0.000 /sec                   
           717,329      page-faults:u                    #   39.093 K/sec                  
    77,585,648,085      cycles:u                         #    4.228 GHz                    
   169,000,946,915      instructions:u                   #    2.18  insn per cycle         
    36,205,500,147      branches:u                       #    1.973 G/sec                  
       497,651,939      branch-misses:u                  #    1.37% of all branches        
   387,384,692,920      slots:u                          #   21.112 G/sec                  
   111,272,326,760      topdown-retiring:u               #     19.8% Retiring              
   335,353,029,163      topdown-bad-spec:u               #     59.7% Bad Speculation       
    75,804,429,827      topdown-fe-bound:u               #     13.5% Frontend Bound        
    39,530,241,508      topdown-be-bound:u               #      7.0% Backend Bound         

      18.416893454 seconds time elapsed

      17.278805000 seconds user
       1.124952000 seconds sys

Because it destroys the branch predictor in my laptop! {59.7% bad speculation)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 67d4eed and aa970da.
Files selected for processing (5)
  • repl/Main.hs (2 hunks)
  • src/Evaluator.hs (4 hunks)
  • src/Expander.hs (13 hunks)
  • src/Expander/Monad.hs (2 hunks)
  • src/Pretty.hs (1 hunks)
Additional comments: 18
repl/Main.hs (1)
  • 149-151: The replacement of runExceptT and runReaderT with evaluateIn directly affects the error handling mechanism. This change simplifies the control flow by directly invoking evaluateIn for evaluation, which could potentially make the error handling more specific and tailored to the needs of the REPL environment. However, ensure that this new approach does not overlook any errors that were previously caught by the monadic computations. It's also important to verify that the performance impact, if any, is within acceptable limits given the concerns raised about the new evaluator's efficiency.
src/Pretty.hs (1)
  • 627-627: Adding the case pp env (EvalErrorIdent v) to handle errors related to binding identifiers to non-values is a valuable enhancement to the error reporting mechanism. This addition ensures that such errors are reported in a user-friendly manner, aiding in debugging and error resolution. It's crucial to ensure that this new error case is adequately tested to confirm that it correctly identifies and reports the intended error scenarios.
src/Evaluator.hs (3)
  • 87-90: The introduction of the EvalResult data type with constructors like ExampleResult and IOResult is a significant change. It's important to ensure that all parts of the codebase that interact with evaluation results are updated to handle these new cases appropriately.
  • 111-168: The Kont data type is central to the CEK interpreter's operation, representing various states of the continuation. The detailed constructors like InArg, InFun, InLetDef, etc., are well-documented. However, it's crucial to ensure that all pattern matches against Kont are exhaustive to prevent runtime errors.
  • 450-666: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [441-666]

The helper functions and the top-level API (evaluate, evaluateIn, evaluateWithExtendedEnv, projectError, erroneousValue) are crucial for the evaluator's usability and integration with the rest of the codebase. It's important to ensure that these functions are correctly implemented and that their interfaces are clear and consistent with the rest of the project.

Verification successful

The usage of the top-level API functions (evaluate, evaluateIn, evaluateWithExtendedEnv, projectError, erroneousValue) across various parts of the project, including testing, the REPL, expansion, and within the evaluator itself, confirms their correct implementation and integration. Their application in different contexts aligns with their intended purposes, indicating that these functions are crucial for the evaluator's usability and integration with the rest of the codebase. Based on this analysis, the review comment is verified as accurate.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usage of top-level API functions to ensure they are correctly integrated.
rg "evaluate|evaluateIn|evaluateWithExtendedEnv|projectError|erroneousValue" --type hs

Length of output: 3679

src/Expander/Monad.hs (2)
  • 9-9: The addition of the FlexibleContexts language extension is noted. This extension allows for more flexible context specifications in type signatures, which can be necessary for certain function signatures or typeclass instances involving complex type constraints. Given the changes in the expandEval function, this addition seems justified.
  • 864-871: The signature of expandEval has been changed from Eval a -> Expand a to Core -> Expand Value, indicating a shift in how evaluation is performed. This change aligns with the PR's objective to introduce a new CEK evaluator. The function now directly takes a Core expression and evaluates it within the current environment, handling errors by throwing a MacroEvaluationError with the current phase and the projected error. This approach simplifies the evaluation process by directly working with core expressions and leveraging the existing environment, which is a positive change in terms of maintainability and clarity. However, it's crucial to ensure that all calls to expandEval throughout the codebase have been updated to match this new signature to avoid type mismatches.
src/Expander.hs (11)
  • 86-86: The import statements for Util.Set and Util.Store as Set and S respectively have been added. Ensure that these modules are used appropriately in the file and that their functions are called correctly to avoid any runtime errors or unexpected behavior.
  • 275-275: The call to expandEval within the evalDecl function for the Define case indicates a significant change in how expressions are evaluated during the declaration expansion process. Ensure that expandEval is correctly handling the evaluation of expressions and that the error handling mechanisms are robust enough to catch and report errors effectively.
  • 302-302: Another instance of expandEval being used, this time within the Example case of evalDecl. It's crucial to verify that the evaluation context and environment are correctly set up before calling expandEval to ensure that the expression evaluation proceeds as expected.
  • 305-305: The use of expandEval in the Run case of evalDecl highlights the importance of ensuring that the evaluated expression is of the expected type, especially when dealing with IO actions. Proper type checking and error handling are essential to prevent runtime type errors.
  • 316-316: In the DefineMacros case, the call to expandEval signifies a critical point where macro definitions are evaluated. It's important to ensure that the macro evaluation environment is correctly set up and that macros are expanded and applied correctly to avoid macro expansion errors.
  • 556-556: The handling of the bind-IO macro action involves a complex interaction between IO actions and function application. Ensure that the environment and closure application are correctly managed to avoid issues with variable scoping, especially in the context of IO actions.
  • 562-563: The use of evaluateWithExtendedEnv within the bind-IO macro action processing suggests a nuanced handling of environments during the evaluation of expressions in an extended environment. Verify that the environment extension and subsequent evaluation are correctly implemented to maintain the integrity of variable bindings and evaluations.
  • 908-921: The handling of type cases in the AwaitingTypeCase task processing involves branching based on the type of the expression being evaluated. It's crucial to ensure that the type case handling is robust and correctly interprets the types to execute the appropriate branch of code.
  • 936-936: The evaluation of macro implementations within the AwaitingMacro task processing requires careful handling of the macro expansion environment. Verify that the macro implementation is evaluated in the correct phase and that the environment is appropriately extended to support macro expansion.
  • 952-952: In the AwaitingDefn task processing, the evaluation of definitions involves expanding and evaluating expressions in the current environment. Ensure that the environment is correctly managed and that definitions are evaluated and bound correctly to support subsequent expansions.
  • 1314-1335: The processing of user-defined macros involves evaluating the macro implementation and applying it to the syntax object. It's important to ensure that the macro application is correctly handled and that any errors during macro expansion are appropriately reported to aid in debugging macro definitions.

src/Evaluator.hs Outdated
Comment on lines 11 to 48
{- Note [The CEK interpreter]:

The Klister interpreter is a straightforward implementation of a CEK
interpreter. The interpreter keeps three kinds of state:

-- C: Control ::= The thing that is being evaluated
-- E: Environment ::= The interpreter environment
-- K: Kontinuation ::= The syntactic context of the thing that is being interpreted

Why a CEK? A CEK interpreter allows us to have precise control over the
evaluation of a klister program. For example, because the interpreter keeps a
reference to the kontinuation we can provide stack traces. This handle also
makes a more advanced debugger possible. Upon an evaluation error we could save
the kontinuation stack, over write a variable in the environment a la common
lisp or even rewind the evaluation

See Matthias Felleison's course website for a good reference:
https://felleisen.org/matthias/4400-s20/lecture23.html

The bird's eye view:

The evaluator crawl's the input AST and progresses in three modes:

-- 'Down': meaning that the evaluator is searching for a redex to evaluate and
-- therefore moving "down" the AST.

-- 'Up': meaning that the evaluator has evaluated some redex to a value and is
-- passing that valua "up" the execution stack.

-- 'Er': meaning that something has gone wrong, the stack is captured and the Er
-- will float up to be handled by the caller of the evaluator.

All interesting things happen by matching on 'Kont', the continuation. This
allows the evaluator to know exactly what needs to happen in order to continue.

-- TODO: #108 describe the how the debugger hooks in

-}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detailed comments explaining the CEK interpreter provide a clear overview of the interpreter's design and operation. This documentation is valuable for both current and future developers working on the project. However, the TODO comment on line 46 about describing how the debugger hooks in suggests that further documentation is needed for complete clarity.

Would you like assistance in documenting the debugger integration?

Comment on lines 103 to 445
(l:ls) -> Down (unCore l) env (InSyntaxErrorLocations msg_syn ls mempty env kont)
)
(\err -> Er err env kont)
-- done
(InSyntaxErrorLocations msg_syn [] dones env kont) ->
Up (ValueMacroAction
$ MacroActionSyntaxError (SyntaxError { _syntaxErrorMessage = msg_syn
, _syntaxErrorLocations = dones
})) env kont
(InSyntaxErrorLocations msg (l:ls) dones env kont) ->
evalAsSyntax v
(\good -> Down (unCore l) env (InSyntaxErrorLocations msg ls (good : dones) env kont))
(\err -> Er err env kont)

-- the downsweep, searching for a redex to evaluate.
step (Down c env k) =
case c of

-- atoms
(CoreString s) -> Up (ValueString s) env k
(CoreInteger i) -> Up (ValueInteger i) env k
(CoreIntegerSyntax (ScopedInteger int scope)) -> Down (unCore int) env (InInteger scope env k)
(CoreStringSyntax (ScopedString str scope)) -> Down (unCore str) env (InString scope env k)
(CoreSyntax s) -> Up (ValueSyntax s) env k
(CoreError what) -> Down (unCore what) env (InError env k)
(CoreEmpty (ScopedEmpty scope)) -> Down (unCore scope) env (InScope (List mempty) env k)
CoreMakeIntroducer -> Up (ValueMacroAction MacroActionIntroducer) env k
CoreWhichProblem -> Up (ValueMacroAction MacroActionWhichProblem) env k


-- variables and binders
(CoreVar var) ->
case lookupVal var env of
Just val -> Up val env k
_ -> Er (EvalErrorUnbound var) env k

(CoreLet ident var def body) ->
Down (unCore def) env (InLetDef ident var (unCore body) env k)

(CoreLetFun fIdent fVar argIdent argVar def body) ->
let vFun = ValueClosure $ FO $ FOClosure
{ _closureEnv = Env.insert fVar fIdent vFun env
, _closureIdent = argIdent
, _closureVar = argVar
, _closureBody = def
}
newEnv = Env.insert fVar fIdent vFun env
in Down (unCore body) newEnv k

(CoreCtor con args) -> case args of
-- just a symbol, shortcut out
[] -> Up (ValueCtor con mempty) env k
-- process fields left to right
(f:fs) -> Down (unCore f) env (InCtor con (fmap unCore fs) mempty env k)


-- lambdas and application
(CoreLam ident var body) ->
let lam = ValueClosure $ FO $ FOClosure
{ _closureEnv = env
, _closureIdent = ident
, _closureVar = var
, _closureBody = body
}
in Up lam env k
(CoreApp fun arg) -> Down (unCore arg) env (InArg (unCore fun) env k)


-- cases
(CoreCase loc scrutinee cases) -> Down (unCore scrutinee) env (InCaseScrut cases loc env k)
(CoreDataCase loc scrutinee cases) -> Down (unCore scrutinee) env (InDataCaseScrut cases loc env k)
(CoreTypeCase loc scrut cases) -> Down (unCore scrut) env (InTypeCaseScrut cases loc env k)

(CoreIdent (ScopedIdent ident scope)) -> Down (unCore ident) env (InIdent scope env k)
(CoreIdentEq how l r) -> Down (unCore l) env (InIdentEqL how r env k)

(CoreCons (ScopedCons hd tl scope)) -> Down (unCore hd) env (InConsHd scope (unCore tl) env k)
-- empty, short circuit
(CoreList (ScopedList ls scope)) -> case ls of
[] -> Down (unCore scope) env (InScope (List []) env k)
(e:es) -> Down (unCore e) env (InList scope es mempty env k)
(CoreReplaceLoc loc stx) -> Down (unCore loc) env (InLoc stx env k)


-- macros
(CorePureMacro arg) -> Down (unCore arg) env (InPureMacro env k)
(CoreBindMacro hd tl) -> Down (unCore hd) env (InBindMacroHd tl env k)


-- others
(CoreLog msg) -> Down (unCore msg) env (InLog env k)
(CoreSyntaxError err) ->
Down (unCore $ _syntaxErrorMessage err) env (InSyntaxErrorMessage (_syntaxErrorLocations err) env k)


-- -----------------------------------------------------------------------------
-- Helper Functions

evalErrorText :: EvalError -> Text
evalErrorText (EvalErrorUnbound x) = "Unbound: " <> T.pack (show x)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [187-534]

The step function, which transitions the CEK state machine, is a critical part of the evaluator. It handles various cases like function application, constructors, cases, macros, and syntax and atomics. It's essential to review this function for correctness, especially in handling edge cases and ensuring that the transitions are logically sound. Additionally, performance considerations should be taken into account, given the concerns raised about the new evaluator's performance impact.

Ensure that all transitions in the step function are logically sound and consider potential performance optimizations, especially given the concerns about increased bad speculation and its impact on branch prediction.

src/Evaluator.hs Outdated Show resolved Hide resolved
-- TODO DYG: Move to separate module
projectError :: EState -> EvalError
projectError (Er err _env _k) = err
projectError _ = error "debugger: impossible"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that repl/Main.hs has

case evaluateIn ... of
  Left evalErr -> print $ erroneousValue $ projectError evalErr
  Right val    -> ...

so is this "impossible" in the sense that when evaluateIn returns a Left eState, that EState is always an Er? In that case, wouldn't it make more sense to return Left err or Left (err, _env, _k), to make impossible states unrepresentable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that would make more sense, but I specifically left it this way because this state will become important for the debugger. The debugger will be typed debugger :: EState -> DebugM EState or something like that. But the key point is that the debugger should be able to input EState which is actually at Er ... and return an EState that is not in Er. So I left as much information as possible until the very last projection or pretty printing because when evaluateIn returns a Left (bunch of state) the debugger could take that state and then resume the computation. Perhaps I should add a TODO in a comment

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, that makes sense. But the call to error still seems wrong. I can see several possible solutions:

  1. Add a precondition to projectError's documentation, e.g. "should only be called on the EState inside a Left returned by one of the evaluateX functions.
  2. Have the evaluateX functions return an opaque representation of the error, let's say ErrState, containing (err, env, k). Then, provide a total function projectError :: ErrState -> EvalError and a total function ErrState -> EState, so that the resulting EState can be given to debugger :: EState -> DebugM EState.
  3. Have the evaluateX functions return Left (err, estate), so they can both display the error and resume from the estate, without knowing that the two are related under the hood.

src/Expander/Monad.hs Outdated Show resolved Hide resolved
case out of
Left err -> error (T.unpack (pretty err))
case (evaluateWithExtendedEnv env [(ident, var, vx)] body) of
Left err -> error (T.unpack (pretty $ projectError err))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woah! why do we fail with error here? I think runEval used to only fail if there was a bug in our implementation of the compiler, e.g. if our type checker allowed the construction of an ill-typed Core term. but nowadays evaluating a Core expression can fail with EvalErrorCase or EvalErrorUser if there is a bug in the user's .kl code too, so we should probably use throwError here. this is unrelated to the current PR, so I'll create a separate ticket to track this: #237

$ MacroEvaluationError p
$ evalErrorType "macro action"
$ erroneousValue
$ projectError err
Copy link
Owner

@gelisam gelisam Apr 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this error-handling logic is repeated several time (sometimes with the evalErrorType "macro action" bug, sometimes without), wasn't it nicer when expandEval was running in the Expand monad and was thus able to throw this error right away?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hesitant to DRY it out because I still want to workshop the design of the debugger and how it hooks into expandEval.

throw this error right away

This action will be calling the debugger. Immediately exiting the debugger should then be throwing the error and reporting the error back to the user. Perhaps Expand will have to be a monad transformer over DebugM? I'm honestly not sure if anyone has made a CommonLisp style debugger in Haskell yet.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I understand what erroneousValue does, I don't think this code is correct. It claims that the problem is that there is a value which should be a macro action, but its actual type is something else. But that problem is raised below, on L1001:

throwError $ MacroEvaluationError p $ evalErrorType "macro action" other

The problem on this line is some unknown thing which happened during evaluation, not necessarily a type error. Instead of using err to construct a type error, we should propagate err upwards as-is.

src/Expander.hs Outdated Show resolved Hide resolved
src/Evaluator.hs Outdated Show resolved Hide resolved
@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch 2 times, most recently from b2de4f2 to 906a85e Compare June 2, 2024 14:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between aa970da and 906a85e.

Files selected for processing (5)
  • repl/Main.hs (2 hunks)
  • src/Evaluator.hs (4 hunks)
  • src/Expander.hs (14 hunks)
  • src/Expander/Monad.hs (3 hunks)
  • src/Pretty.hs (1 hunks)
Additional context used
Path-based instructions (5)
repl/Main.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Pretty.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Evaluator.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Expander/Monad.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Expander.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

Additional comments not posted (17)
repl/Main.hs (1)

149-151: The integration of evaluateIn in the REPL's evaluation flow.
This change leverages the new CEK evaluator's evaluateIn function to process expressions. It effectively handles errors by projecting them through projectError and then extracting the erroneous value for display. This is a crucial part of the REPL's functionality, ensuring that evaluation errors are properly managed and communicated to the user.

src/Pretty.hs (1)

627-627: Handling of new error type in pretty-printing.
The addition of the EvalErrorIdent case allows the system to specifically address and display errors related to attempts to bind identifiers to non-values. This enhances the error reporting capabilities of the system, making it easier for users to understand the nature of the errors encountered.

src/Evaluator.hs (4)

11-29: The introductory note provides a clear and concise explanation of the CEK interpreter's design and its components (Control, Environment, Kontinuation). This documentation is crucial for understanding the operational mechanics of the interpreter.


654-658: The evaluateWithExtendedEnv function extends the environment for evaluation. This is a critical operation, and it's important to ensure that the environment extension is handled correctly to avoid any potential issues during evaluation.

Verification successful

The evaluateWithExtendedEnv function appears to handle the environment extension correctly and efficiently using foldl' to insert elements into the environment. The usages in src/Expander.hs and src/Evaluator.hs also seem to correctly pass the environment and extensions.

  • src/Evaluator.hs:

    • Line 654: Definition of evaluateWithExtendedEnv.
    • Line 656: Uses foldl' to insert elements into the environment.
  • src/Expander.hs:

    • Line 1: Usage of evaluateWithExtendedEnv with correct parameters.
  • src/Evaluator.hs:

    • Line 1: Usage of evaluateWithExtendedEnv with correct parameters.
    • Line 2: Usage of evaluateWithExtendedEnv with correct parameters.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the correct handling of environment extensions in `evaluateWithExtendedEnv`.

# Test: Search for incorrect or inefficient environment extensions. Expect: Environment is extended correctly and efficiently.
ast-grep --lang haskell --pattern $'evaluateWithExtendedEnv($_, $_, $_) { $$$ }'

Length of output: 81



Script:

#!/bin/bash
# Search for the definition of `evaluateWithExtendedEnv` in the codebase.
ast-grep --lang haskell --pattern 'evaluateWithExtendedEnv($_, $_, $_) = $_'

# Search for all usages of `evaluateWithExtendedEnv` in the codebase.
rg 'evaluateWithExtendedEnv'

Length of output: 601



Script:

#!/bin/bash
# Display the definition of `evaluateWithExtendedEnv` in `src/Evaluator.hs`.
cat src/Evaluator.hs | sed -n '654,658p'

# Display the usages of `evaluateWithExtendedEnv` in `src/Expander.hs`.
rg 'evaluateWithExtendedEnv' src/Expander.hs

# Display the usages of `evaluateWithExtendedEnv` in `src/Evaluator.hs`.
rg 'evaluateWithExtendedEnv' src/Evaluator.hs

Length of output: 748


187-358: The step function, which is central to the CEK machine's operation, handles various states and transitions. It's crucial to ensure that each case is handled correctly to maintain the integrity of the evaluation process. Given the performance concerns mentioned in the PR, consider optimizing the handling of these transitions to reduce complexity and improve efficiency.

Consider refactoring complex cases in the step function to separate helper functions to improve readability and maintainability.


441-442: The apply function and its helper applyInEnv are critical for handling function applications. Ensure that these functions are robust against various edge cases, especially given the new evaluator's performance issues.

Also applies to: 502-510

src/Expander/Monad.hs (2)

9-9: The addition of the FlexibleContexts language extension is appropriate given the changes in function signatures and types.


864-871: The renaming of expandEval to evalInCurrentPhase and its implementation are well-suited to its purpose. The function effectively uses the current environment for evaluation and handles errors appropriately.

src/Expander.hs (9)

25-25: Review the addition of evalInCurrentPhase and related entities.

The addition of evalInCurrentPhase and related entities in the export list is consistent with the changes described in the PR summary and AI-generated summary. This function seems to be central to the new evaluation strategy.


275-275: Review the use of evalInCurrentPhase in evalDecl.

The use of evalInCurrentPhase here for evaluating expressions within declarations is appropriate and aligns with the new evaluation strategy. This ensures that expressions are evaluated in the correct phase of the language's compilation or interpretation process.


302-302: Review the use of evalInCurrentPhase in Example case.

Using evalInCurrentPhase to evaluate the expression in the Example case is correct. It ensures that the example expressions are evaluated in the current phase, which is crucial for testing and example code blocks within the language.


305-305: Review the use of evalInCurrentPhase in Run case.

The use of evalInCurrentPhase in the Run case is appropriate. It ensures that runtime actions are evaluated in the current phase, which is essential for executing IO actions or other runtime tasks.


316-316: Review the use of evalInCurrentPhase in DefineMacros case.

The use of evalInCurrentPhase in the DefineMacros case is appropriate. It ensures that macro definitions are evaluated in the current phase, which is crucial for macro expansion and execution.


556-556: Review the handling of function application in bind-IO.

The handling of function application in the bind-IO primitive is correctly implemented. It uses dynamic environment extension and proper error handling, which are essential for functional programming languages that support first-class functions.


908-920: Review the error handling in AwaitingTypeCase.

The error handling in the AwaitingTypeCase task is robust, correctly distinguishing between different types of macro evaluation errors and providing detailed error messages. This is crucial for debugging and maintaining the language's macro system.


935-935: Review the evaluation of macro implementations in AwaitingMacro.

The evaluation of macro implementations in the AwaitingMacro task is correctly handled. It uses inEarlierPhase to ensure that the macro is evaluated in the correct phase, which is essential for maintaining the phase consistency of the language.


1313-1334: Review the handling of user macros in expandOneForm.

The handling of user macros in expandOneForm is sophisticated and well-implemented. It correctly uses phase adjustments and dynamic scope management to ensure that macros are expanded correctly and efficiently.

All interesting things happen by matching on 'Kont', the continuation. This
allows the evaluator to know exactly what needs to happen in order to continue.

-- TODO: #108 describe the how the debugger hooks in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Document debugger integration.

Would you like assistance in documenting how the debugger integrates with the CEK interpreter? This could enhance the clarity and maintainability of the debugging features.

Comment on lines +660 to +663
projectError :: EState -> EvalError
projectError (Er err _env _k) = err
projectError _ = error "debugger: impossible"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The EState type has three constructors: Down, Up, and Er. The projectError function only handles the Er constructor explicitly and defaults to an error for other cases. This means it does not handle the Down and Up states, which could lead to runtime errors if these states are passed to projectError.

  • Location to address:
    • src/Evaluator.hs lines 660-663: Ensure projectError handles all possible states of EState.
Analysis chain

The projectError function is designed to extract errors from the evaluator's state. Given its critical role in error handling, ensure that it robustly handles all possible error states.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the robustness of the `projectError` function.

# Test: Search for potential issues in error extraction by `projectError`. Expect: All errors are correctly extracted.
ast-grep --lang haskell --pattern $'projectError($_) { $$$ }'

Length of output: 62



Script:

#!/bin/bash
# Description: Identify all possible states of `EState` and verify if `projectError` handles them.

# Step 1: Find the definition of `EState` to list all possible states.
ast-grep --lang haskell --pattern $'data EState = $$$'

# Step 2: Check where `projectError` is used to ensure it handles all states.
rg 'projectError'

Length of output: 780



Script:

#!/bin/bash
# Description: Locate the definition of `EState` and list all possible states.

# Step 1: Find the definition of `EState` to list all possible states.
rg 'data EState' --type haskell

Length of output: 68



Script:

#!/bin/bash
# Description: Extract the full definition of `EState` from `src/Evaluator.hs`.

# Step 1: Extract the full definition of `EState`.
sed -n '/data EState/,/where/p' src/Evaluator.hs

Length of output: 14015

res <- interpretMacroAction prob act
case res of
Left err -> throwError
$ ValueNotMacro
Copy link
Owner

@gelisam gelisam Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a ValueNotMacro error. In this branch, looking up transformerName did successfully find a macro definition of type (-> Syntax (Macro Syntax)). That function was then applied to a Syntax argument, and the evaluation of that function failed for some reason, perhaps because (error 'msg) was called.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha! The existing code already misuses ValueNotMacro. When expanding the macro call site (my-macro), many things can go wrong:

  1. my-macro might not be in scope, in which case resolve (line L1201) throws Unknown and the user sees Unknown: my-macro.
  2. my-macro resolves to EUserMacro transformerName, but that transformerName is somehow not the MacroVar which was added to the transformer environment by (define-macros ([my-macro ...])) at the same time as my-macro was bound to EUserMacro in the binding table. For example, maybe transformerName is not in the binding table at all. This is a bug in Klister, so InternalError is thrown; error would have been fine too.
  3. Same thing, but transformerName is bound to something other than a (-> Syntax (Macro Syntax)). This is also a bug in Klister, but ValueNotMacro is thrown. This is definitely wrong, since the error message associated with ValueNotMacro is "Not a macro monad value", but it's not even a (Macro Syntax) which is expected, it's a (-> Syntax (Macro Syntax)).
  4. transformerName is bound to a value of type (-> Syntax (Macro Syntax)), but applying it to a Syntax value fails before we can get a (Macro Syntax) value out. The current code fails inside expandEval with MacroEvaluationError, whereas this branch fails here with ValueNotMacro. I think it should be MacroEvaluationError.
  5. we do get a value out, but it's not a (Macro Syntax), so ValueNotMacro is thrown. If this happens, that means there is a bug in Klister's type-checker, so InternalError or error would have been fine too.
  6. interpretMacroAction encounters an error while executing the macro action. it fails somewhere inside interpretMacroAction via code which isn't relevant here.

As a conclusion, I think this should be MacroEvaluationError, not ValueNotMacro, but the existing code isn't very good at using the right error constructor, so this isn't a big deal.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same mistake as the other erroneousValue call site. Maybe erroneousValue shouldn't exist at all, since it seems so easy to misuse?

ValueMacroAction act -> interpretMacroAction prob act
other -> throwError $ ValueNotMacro other
view (expanderWorld . worldEnvironments . at phase) $ s
case applyInEnv env closure boundResult of
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another place where this MR highlights a mistake in the original code: all of

phase <- view (expanderLocal . expanderPhase)
s <- getState
let env = ...

and the withEnv env is dead code, because apply overwrites this env with its own call to withEnv. So there is no need for an applyInEnv separate from apply.

Copy link
Owner

@gelisam gelisam Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's what I would write:

          phase <- view (expanderLocal . expanderPhase)
          case apply closure boundResult of
            Left err -> throwError
              $ MacroEvaluationError phase
              $ projectError err

-- 'Er': meaning that something has gone wrong, the stack is captured and the Er
-- will float up to be handled by the caller of the evaluator.

All interesting things happen by matching on 'Kont', the continuation. This
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the Control? What's the Control for, then?

( EvalError (..)
, EvalResult (..)
, TypeError (..)
, evaluate
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evaluate has type Core -> Either EState Value, but EState is not exported. That seems needlessly restrictive: the caller can store the result in a variable, return it to its own caller, but if they do so then they cannot write down a type signature. Would it make sense to at least export it as an opaque type?

( EvalError (..)
, EvalResult (..)
, TypeError (..)
, evaluate
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evaluate looks like it is the main API, with evaluateIn and evaluateWithExtendedEnv being a secondary APIs for special cases. But I see that evaluate is not used anywhere! Perhaps it would make more sense to drop evaluate and to make evaluateIn the main API?

, evaluateIn
, evaluateWithExtendedEnv
, evalErrorType
, evalErrorText
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evalErrorText is not used anywhere? that's strange, where do the errors get printed? Maybe the Show instance is used instead of evalErrorText by mistake?

, projectError
, erroneousValue
, applyInEnv
, apply
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply behaves very similarly to the evaluateX functions, would it perhaps make more sense to call it something like evaluateFunctionApplication?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for doTypeCase. evaluateTypeCase?

@@ -38,8 +103,343 @@ data EvalError
| EvalErrorType TypeError
| EvalErrorCase SrcLoc Value
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we discussed this, but I still find it unintuitive that a SrcLoc is needed for case and type-case, but not for anything else. We should document the reasoning somewhere. Let's see, that reasoning is...

That SrcLoc is used to construct the error message in the case in which no cases match. By comparison, other errors such as looking up a Var don't need a SrcLoc because scope errors are caught during expansion time, before evaluation begins. Are case and type-case really the only ways in which evaluation can fail at runtime, assuming expansion already caught scope errors and type errors?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the current datatype exhaustively lists all the errors which can happen at evaluation-time, and #237 already classifies which ones should already be caught in an earlier phase and which ones really can happen at evaluation-time. There is EvalErrorCase, and EvalErrorUser. EvalErrorUser does not need a separate SrcLoc because the error message is a Syntax, which is already associated with a SrcLoc.

Since we're separating between Case and TypeCase everywhere else in the codebase, I wonder why there isn't a separate EvalErrorTypeCase here? 🤔

Anyway, that's not something which changed in the current MR, moving right along...

, applyInEnv
, apply
, doTypeCase
, try
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is Control.Exception.try re-exported? you don't even throw exceptions in this file 😅

makePrisms ''EvalError
instance Exception EvalError
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvalError is never thrown as an exception, did you perhaps initially intend to use it as an exception and later changed your mind to use Er evalError?


data EvalResult
= ExampleResult SrcLoc VEnv Core (Scheme Ty) Value
| IOResult (IO ())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvalResult is not even used in this file 😅
Perhaps it should be defined next to evalMod instead?

Let's do that in a separate MR, as this MR does not introduce EvalResult, it merely moves it to the top of the file.

data EvalResult
= ExampleResult SrcLoc VEnv Core (Scheme Ty) Value
| IOResult (IO ())

-- TODO: more precise representation
type Type = Text
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text is actually just fine since the type checker has (hopefully) already caught all the type errors, so it doesn't make sense to spend extra effort on type errors which slip through to evaluation-time.

-- ^ Marks the evaluator finishing

-- functions
InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use CoreF TypePattern ConstructorPattern Core instead of the shorter-and-isomorphic Core?

InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
-- ^ The argument is being evaluated, so hold onto the function symbol.
InFun :: !Value -> !VEnv -> !Kont -> Kont
-- ^ The function is being evaluated, so hold onto the evaluated argument.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, you evaluate the argument first? I of course know that Klister is a strict language, which means that the arguments are evaluated before the call is made, but what about the case in which the function is an expression, e.g. ((id (+)) (* 2 2) (* 3 3))? Isn't it standard to evaluate (id (+)) to (+) before evaluating (* 2 2) to 4 and (* 3 3) to 9?

-- functions
InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
-- ^ The argument is being evaluated, so hold onto the function symbol.
InFun :: !Value -> !VEnv -> !Kont -> Kont
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the VEnv needed? It will not be used to further evaluate the Value, since it is already a Value. Is it perhaps so that the debugger can display the captured environment at this point in the stack trace?

-- lists
InConsHd :: !Core -> !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
InConsTl :: !Core -> !Syntax -> !VEnv -> !Kont -> Kont
InList :: !Core -> ![Core] -> ![Syntax] -> !VEnv -> !Kont -> Kont
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame that you had to waste your time reimplementing those outdated direct syntax-manipulating primitives! I only ever use open-syntax and close-syntax now. We should get rid of this old stuff. In a later MR, of course.

InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
-- ^ The argument is being evaluated, so hold onto the function symbol.
InFun :: !Value -> !VEnv -> !Kont -> Kont
-- ^ The function is being evaluated, so hold onto the evaluated argument.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are really helpful, let's add one for each constructor!

  -- functions
  InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
  -- ^ The argument is being evaluated, so hold onto the function expression
  InFun :: !Value -> !VEnv -> !Kont -> Kont
  -- ^ The function is being evaluated, so hold onto the evaluated argument.

  InLetDef :: !Ident -> !Var -> !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
  -- ^ Inside the rhs of (let [x rhs] body), so hold the name x and the body.

  -- constructors
  InCtor :: !Constructor -> ![CoreF TypePattern ConstructorPattern Core] -> ![Value] -> !VEnv -> !Kont -> Kont
  -- ^ Inside arg_i of (Ctor arg_1 ... arg_i ... arg_n), so hold Ctor, the
  -- already evaluated values value_1 ... value_{i-1}, and the
  -- yet-to-be-evaluated expressions arg_{i+1} ... arg_n.

  -- Cases
  InCaseScrut     :: ![(SyntaxPattern, Core)] -> !SrcLoc -> !VEnv -> !Kont -> Kont
  InDataCaseScrut :: ![(ConstructorPattern, Core)] -> !SrcLoc -> !VEnv -> !Kont -> Kont
  InTypeCaseScrut :: ![(TypePattern, Core)] -> !SrcLoc -> !VEnv -> !Kont -> Kont
  -- ^ Inside the scrutinee of (case scrutinee [pat_1 rhs_1] ...), so hold
  -- the branches.

  -- lists
  InConsHd :: !Core -> !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
  -- ^ Inside the head of (cons-list-syntax-cons head tail loc), so hold the tail.
  InConsTl :: !Core -> !Syntax -> !VEnv -> !Kont -> Kont
  -- ^ Inside the tail of (cons-list-syntax-cons head tail loc), so hold the already-evaluated head.
  InList   :: !Core -> ![Core] -> ![Syntax] -> !VEnv -> !Kont -> Kont
  -- ^ Inside arg_i of (list-syntax (arg_1 ... arg_i ... arg_n) loc), so hold
  -- the already-evaluated syntax values stx_1 ... stx_{i-1}, and the
  -- yet-to-be-evaluated expressions arg_{i+1} ... arg_n.

  -- idents
  InIdent :: !Core -> !VEnv -> !Kont -> Kont
  InIdentEqL :: !HowEq -> !Core -> !VEnv -> !Kont -> Kont
  -- ^ Inside the lhs of (<how>-identifier=? lhs rhs), so hold <how> and rhs.
  InIdentEqR :: !HowEq -> !Value -> !VEnv -> !Kont -> Kont
  -- ^ Inside the rhs of (<how>-identifier=? lhs rhs), so hold <how> and the
  -- already-evaluated lhs.

  -- Macros
  InPureMacro :: !VEnv -> !Kont -> Kont
  -- ^ Inside the expr of (pure expr), so nothing to hold.
  InBindMacroHd :: !Core -> !VEnv -> !Kont -> Kont
  -- ^ Inside the expr1 of (>>= expr1 expr2), so hold expr2.
  InBindMacroTl :: !MacroAction -> !VEnv -> !Kont -> Kont
  -- ^ Inside the expr2 of (>>= expr1 expr2), so hold the already-evaluated
  -- expr1.

  -- atomics
  InInteger :: !Core -> !VEnv -> !Kont -> Kont
  InString  :: !Core -> !VEnv -> !Kont -> Kont
  InLoc     :: !Core -> !VEnv -> !Kont -> Kont
  -- ^ Inside the loc of (replace-loc loc stx), so hold the stx.
  InLocStx  :: !SrcLoc -> !VEnv -> !Kont -> Kont
  -- ^ Inside the stx of (replace-loc loc stx), so hold the already-evaluated
  -- loc.

  -- scope
  InScope :: !(ExprF Syntax) -> !VEnv -> !Kont -> Kont
  -- ^ Inside the loc of something like (cons-syntax-list head tail loc), so
  -- remember the Syntax-Contents which needs a scope-set and location.

  -- logs and errors
  InLog   :: !VEnv -> !Kont -> Kont
  -- ^ Inside the expr of (log expr), so nothing to hold.
  InError :: !VEnv -> !Kont -> Kont
  -- ^ Inside the expr of (error expr), so nothing to hold.

-- ^ Evaluating the let def

-- constructors
InCtor :: !Constructor -> ![CoreF TypePattern ConstructorPattern Core] -> ![Value] -> !VEnv -> !Kont -> Kont
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the naming convention, shouldn't it be InCtorArg? We're inside arg_i of (Ctor arg_1 ... arg_i ... arg_n), not inside Ctor, right?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And arg_1 through arg_{i-1} have already been evaluated to Values. Shouldn't the [Value] list be before the [Core] list? Seems weird to use the order [arg_{i+1}, ..., arg_n] [arg_1, ..., arg_{i-1}]. Or are both lists reversed? Is it [arg_n, arg_{n-1}, ..., arg_{i+1}] [arg_{i-1}, arg_{i-2}, ..., arg_1]?

InInteger :: !Core -> !VEnv -> !Kont -> Kont
InString :: !Core -> !VEnv -> !Kont -> Kont
InLoc :: !Core -> !VEnv -> !Kont -> Kont
InLocStx :: !SrcLoc -> !VEnv -> !Kont -> Kont
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think InReplaceLocL and InReplaceLocR would be much clearer than InLoc and InLocStx

(InSyntaxErrorLocations msg_syn [] dones env kont) ->
Up (ValueMacroAction
$ MacroActionSyntaxError (SyntaxError { _syntaxErrorMessage = msg_syn
, _syntaxErrorLocations = dones
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
, _syntaxErrorLocations = dones
, _syntaxErrorLocations = reverse (v : dones)

-- | The state of the evaluator
data EState where
Down :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> EState
-- ^ 'Down', we are searching the AST for a redux and building up the stack of
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- ^ 'Down', we are searching the AST for a redux and building up the stack of
-- ^ 'Down', we are searching the AST for a redex and building up the stack of

, _closureBody = body
}
in Up lam env k
(CoreApp fun arg) -> Down (unCore arg) env (InArg (unCore fun) env k)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eval was recurring on the function position first, not the argument, we think the CEK version should do the same

(currently pair-reviewing with @david-christiansen)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CodeRabbit summons the AI reviewing bot
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants