Skip to content

Implement read-string & read-file#673

Merged
jeaye merged 35 commits intojank-lang:mainfrom
shantanu-sardesai:issues/415-test-suite
Mar 5, 2026
Merged

Implement read-string & read-file#673
jeaye merged 35 commits intojank-lang:mainfrom
shantanu-sardesai:issues/415-test-suite

Conversation

@shantanu-sardesai
Copy link
Contributor

@shantanu-sardesai shantanu-sardesai commented Jan 22, 2026

Implement read and it's siblings along with reader options.

@shantanu-sardesai shantanu-sardesai force-pushed the issues/415-test-suite branch 3 times, most recently from 0699168 to 2c0fb6e Compare January 22, 2026 15:57
@shantanu-sardesai
Copy link
Contributor Author

@jeaye

My current implementation cause jank to crash for the following code:

(binding [clojure.core/*read-eval* true] (read-string {:eof false :read-cond :preserve} "#?(:jank)"))

This is because we throw parse_invalid_reader_conditional error which I think is not playing well with the error reporter.


Backtrace

Process 88216 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00000001838c2388 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`__pthread_kill:
->  0x1838c2388 <+8>:  b.lo   0x1838c23a8    ; <+40>
    0x1838c238c <+12>: pacibsp
    0x1838c2390 <+16>: stp    x29, x30, [sp, #-0x10]!
    0x1838c2394 <+20>: mov    x29, sp
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
  * frame #0: 0x00000001838c2388 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x00000001838fb848 libsystem_pthread.dylib`pthread_kill + 296
    frame #2: 0x00000001838049e4 libsystem_c.dylib`abort + 124
    frame #3: 0x00000001004d6d28 jank`jtl::do_assertion_panic(msg=0x000000016fdfcd58) at assert.cpp:12:5
    frame #4: 0x000000010057f7bc jank`jank::error::snippet::add(this=0x000000010db09870, body_source=0x000000010db6df30, n=0x000000010db6df10) at report.cpp:155:5
    frame #5: 0x0000000100582a90 jank`jank::error::plan::add(this=0x000000016fdfd348, body_source=0x000000010db6df30, n=0x000000010db6df10) at report.cpp:369:17
    frame #6: 0x000000010057f2ac jank`jank::error::plan::add(this=0x000000016fdfd348, n=0x000000010db6df10) at report.cpp:354:5
    frame #7: 0x000000010057f1e4 jank`jank::error::plan::plan(this=0x000000016fdfd348, e=jank::error_ref @ 0x000000016fdfcf00) at report.cpp:100:7
    frame #8: 0x000000010057f308 jank`jank::error::plan::plan(this=0x000000016fdfd348, e=jank::error_ref @ 0x000000016fdfcf38) at report.cpp:95:3
    frame #9: 0x0000000100582bf0 jank`jank::error::report(e=jank::error_ref @ 0x000000016fdfd3a0) at report.cpp:537:16
    frame #10: 0x000000010053cf7c jank`jank::util::print_exception(e=jank::error_ref @ 0x000000016fdfd4c8) at try.cpp:112:5
    frame #11: 0x0000000100005598 jank`jank::repl() at main.cpp:277:7
    frame #12: 0x000000010000356c jank`main::$_0::operator()(this=0x000000016fdfe21f, argc=2, argv=0x000000016fdfea30) const at main.cpp:414:9
    frame #13: 0x0000000100003138 jank`main::$_0::__invoke(argc=2, argv=0x000000016fdfea30) at main.cpp:361:60
    frame #14: 0x00000001004fe4d4 jank`jank_init_with_pch(argc=2, argv=0x000000016fdfea30, init_default_ctx='\0', pch_data=0x0000000000000000, pch_size=0, fn=(jank`main::$_0::__invoke(int, char const**) at main.cpp:361)) at c_api.cpp:1077:14
    frame #15: 0x00000001004fe318 jank`jank_init(argc=2, argv=0x000000016fdfea30, init_default_ctx='\0', fn=(jank`main::$_0::__invoke(int, char const**) at main.cpp:361)) at c_api.cpp:1026:12
    frame #16: 0x0000000100002af4 jank`main(argc=2, argv=0x000000016fdfea30) at main.cpp:361:10
    frame #17: 0x000000018355ab98 dyld`start + 6076

@shantanu-sardesai
Copy link
Contributor Author

shantanu-sardesai commented Jan 22, 2026

Probably caused due to #647.

Shall I just repackage our parser (compile-time) error into a runtime error (std::runtime_error) instead? Since anyways there will be no source info available, at least at the moment.

Copy link
Member

@jeaye jeaye left a comment

Choose a reason for hiding this comment

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

Nice work, Shantanu. The overall direction is sane, but the implementation has a way to go to fit into the idioms of jank's C++.

@jeaye
Copy link
Member

jeaye commented Jan 22, 2026

Probably caused due to #647.

Shall I just repackage our parser (compile-time) error into a runtime error (std::runtime_error) instead? Since anyways there will be no source info available, at least at the moment.

This is from a failed assertion. Why did the assertion fail? What were the values? You are suggesting hacky workarounds, but we want to first understand the problem and then consider robust solutions. Hacky workarounds are a last resort.

@shantanu-sardesai
Copy link
Contributor Author

@jeaye

#673 (comment)

The crash was also fixed, now it gives the same output as was reported in this issue (#647).

user=> (read-string {:eof false :read-cond :preserve} "#?(:jank)")
─ parse/invalid-reader-conditional ─────────────────────────────────────────────────────────────────
error: Invalid reader conditional.

─────┬──────────────────────────────────────────────────────────────────────────────────────────────
     │ /var/folders/xd/9v1dcg0d46l7d19830vq0lhc0000gn/T/jank-repl-WQl59K
─────┼──────────────────────────────────────────────────────────────────────────────────────────────
  1  │ (read-string {:eof false :read-cond :preserve} "#?(:jank)")
     │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Used here.
─────┴──────────────────────────────────────────────────────────────────────────────────────────────
╭ nil ─────────────────────────────────────────────────────────────────────────────────────────────╮
│Unable to map file: Unable to find module 'user'.                                                 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

@shantanu-sardesai shantanu-sardesai marked this pull request as ready for review January 25, 2026 12:39
@shantanu-sardesai
Copy link
Contributor Author

shantanu-sardesai commented Jan 25, 2026

Although part of the CI is busted.

@shantanu-sardesai
Copy link
Contributor Author

shantanu-sardesai commented Jan 25, 2026

Also, is there a way to know when read_string is being invoked by the jank compiler for C++ codegen and when it's being used by the end user? At the moment, clojure.core/*read-eval* could be disabled by binding the var and that could hinder the behaviour of codegen.

Or perhaps it won't matter? Since the clojure.core/*read-eval* var can be overwritten by the end user only after our compiler is done with codegen 🤔. Although the issue of initialising the clojure.core/*read-eval* with :unknown on start-up still remains (currently we don't support that, I've just placed a TODO there, perhaps I'll add a note about the potential for this issue there as well).

@shantanu-sardesai shantanu-sardesai changed the title Implement read & read-string with opts Implement read & siblings with opts Jan 25, 2026
@shantanu-sardesai
Copy link
Contributor Author

There is a new limitation encountered due to the :preserve mode in read-string and jank's choice on reader suppression.

Here is my summary of the problems with the complete context consolidated in a document.

@shantanu-sardesai
Copy link
Contributor Author

I've updated the read-string implementation to adapt to our decision now. Things look great so far!

@shantanu-sardesai shantanu-sardesai marked this pull request as ready for review February 28, 2026 08:21
@shantanu-sardesai shantanu-sardesai changed the title Implement read & siblings with opts Implement read-string & read-file Feb 28, 2026
Copy link
Member

@jeaye jeaye left a comment

Choose a reason for hiding this comment

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

Nice work, Shantanu.

return error::parse_invalid_reader_conditional(
{ start_token.start, latest_token.end },
"Conditional read is not allowed by default. Set the :read-cond reader option to either "
":preserve or :allow. Found a nil instead.");
Copy link
Member

Choose a reason for hiding this comment

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

I don't think Found a nil instead. is helpful here. I'm also not entirely sure if it's accurate, since we're working with a bool and not a runtime object.

Copy link
Contributor Author

@shantanu-sardesai shantanu-sardesai Mar 2, 2026

Choose a reason for hiding this comment

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

The allow_reader_conditional boolean is set using the provided reader option (:read-cond) so for the end user this message is more actionable. What do you think?

I don't think Found a nil instead. is helpful here.

Removed.

Comment on lines +304 to +306
object_ref context::read_string(jtl::immutable_string const &code,
object_ref const reader_opts,
u64 const nth_form)
Copy link
Member

Choose a reason for hiding this comment

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

There's a lot of code in here now, with very few comments. Some of the code is quite dense. Please do a comment pass for your new code, to see where we can explain more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not much to comment. Added in the few places I saw something worth adding.

@shantanu-sardesai shantanu-sardesai force-pushed the issues/415-test-suite branch 4 times, most recently from d1644ab to 263f239 Compare March 4, 2026 15:03
Copy link
Member

@jeaye jeaye left a comment

Choose a reason for hiding this comment

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

Nice job, Shantanu.

processor::processor(lex::processor::iterator const &b,
lex::processor::iterator const &e,
object_ref const &extended_features,
bool const &allow_reader_conditional,
Copy link
Member

Choose a reason for hiding this comment

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

We should not take bool by const &, since it's a primitive.

{
}

reader_conditional::reader_conditional(persistent_list_ref f, boolean_ref s)
Copy link
Member

Choose a reason for hiding this comment

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

These can be const.

@jeaye jeaye merged commit 16ace19 into jank-lang:main Mar 5, 2026
15 checks passed
@shantanu-sardesai shantanu-sardesai deleted the issues/415-test-suite branch March 5, 2026 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants