Commit graph

189 commits

Author SHA1 Message Date
Ruud van Asseldonk
12492e12f1 Revert "Smith: Add "dup" instruction"
With the dup instruction, the smith fuzzer can produce programs that are
exponential in the length of the fuzz input. Armed with that, the fuzzer
has discovered a weak spot of RCL: the formatter is inefficient for
operator chains, because it tries to break at every operator, and it
backtracks a lot.

I was aware of this, it's still somewhere on my list of things to fix.
(Like the recent changes for the CST, an operator chain should parse as
a single node with a list of terms, an n-ary operator if you like. Then
we can format the entire chain wide or tall, which would make more sense
to me anyway.) But if we add this feature to the fuzzer now, then for an
input length of 64 bytes, the fuzzer will regularly find timeout cases
that take longer than 2s to run, so it's a blocker.

Remove the dup again then, for now, until I fix the formatter.
2025-08-25 22:58:11 +02:00
Ruud van Asseldonk
66ff92c73b Hook up the source fuzzer to Mode::PatchIdempotent
I also (temporarily) seeded the corpus with some examples of patch mode
inputs. It started exploring, but it still has not discovered a matching
path at a depth greater than 1 after about 20 minutes of fuzzing (on a
single core). I suspect this is going to take a while, but I suppose it
would get there eventually. It is at least steadily discovering new fuzz
inputs.
2025-08-25 22:58:11 +02:00
Ruud van Asseldonk
59c22e9662 Smith: Add "dup" instruction
If the same expression is needed in two parts of the program, before
this change, they had to be pushed separately, with the same sequence of
instructions. Now we can use a 'Dup' instruction instead to duplicate an
expression from within the stack. I hope that this will enable the
fuzzer to explore a bit faster.

Some context, I modified the patch implementation to be not idempotent
(or so I thought), and the fuzzer did not find a counterexample yet
after several minutes of fuzzing. But as I'm writing this, I realize
that I made a mistake there, and even my sabotaged patch command is
still idempotent. Still, this is a small modification of the smith that
can hopefully make it more efficient at exploring the search space, so
let's keep it.
2025-08-25 22:58:11 +02:00
Ruud van Asseldonk
9d41852180 Hook up the smith fuzzer to Mode::PatchIdempotent
It does find most cases very quickly, but apparently it is hard for it
to create sensible patch paths. It needs to be quite lucky to generate
them with ExprField, and then duplicate the right field names that are
also used in the document. Perhaps there is a better way to explore
faster, but at least we have one entry point.

It hasn't discovered any idempotency violations yet.
2025-08-25 22:58:11 +02:00
Ruud van Asseldonk
2f33d0862a Add a fuzzing mode to verify patch idempotency
The code is not yet hooked up to the concrete fuzzers, but it compiles.
2025-08-25 22:58:11 +02:00
Ruud van Asseldonk
2b18211208 Add tests for 'rcl patch' CLI parsing 2025-08-25 22:56:49 +02:00
Ruud van Asseldonk
4db77f9451 Test parsing of --output-depfile CLI arg
Unrelated to the surrounding work on 'rcl patch', but when looking at
CLI coverage, this flag was untested.
2025-08-25 22:56:49 +02:00
Ruud van Asseldonk
89075e8707 Enable the CST formatter to read from multiple docs
When we do surgery on the CST for the patch feature, and make it combine
inputs from multiple sources, the formatter needs to relove spans that
may not come from the same document.

I expected this to require horrible changes, and at first I was planning
to build a hash map of DocId to &str, but I already had this Inputs type
that does exactly what I need, and the changes are pretty minimal!
2025-08-25 22:56:49 +02:00
Ruud van Asseldonk
84748c11a2 Skip long inputs in fuzz_decimal
This is unrelated to the recent change in decimal formatting; the fuzzer
crash happened even before that. Apparently I didn't run this fuzzer for
a longer time, I need to figure out some kind of continuous fuzzing.
2025-08-23 18:33:51 +02:00
Ruud van Asseldonk
4bafbdbf73 Make document name configurable in the loader
There are many things we can load from a string, we can give them better
names than just "input". This is in preparation for the 'rcl patch'
command, where I'll be loading more different documents at the same
time, and it will be more important to tell them apart.

This change can be made independently, so let's do it now to keep the
diff for the 'rcl patch' feature cleaner.
2025-08-11 18:57:25 +02:00
Ruud van Asseldonk
c89a61efc3 Add parse_number builtin to keywords 2025-08-07 21:42:42 +02:00
Ruud van Asseldonk
fef65ad12f Bump version to 0.9.0 2025-07-10 22:13:33 +02:00
Ruud van Asseldonk
7c759fcec9 Add property-based fuzzer for json-lines output
This checks that the number of newlines in the json-lines output equals
the number of list elements, i.e. that we don't generate spurious
newlines.
2025-07-10 21:35:35 +02:00
Ruud van Asseldonk
6788256a85 Accept a new json-lines format in the CLI
I added a std.format_json function, and that required making the width
in the pretty-printer optional. With that in place, it is now easier to
support the JSON Lines format, something I wanted to do for some time
already.

This is a first step to accept the new format in the CLI. It does not
implement the formatting itself yet.
2025-06-28 22:17:01 +02:00
Ruud van Asseldonk
2015fefbec Add std.format_json to fuzzers and grammars 2025-06-28 21:50:20 +02:00
Ruud van Asseldonk
96a92093dd Make width optional in pretty-printer
For displaying things to humans, it makes sense to have a maximum width,
but if we serialize for machines, we can just put everything on a single
line and not bother with backtracking.
2025-06-28 21:50:20 +02:00
Ruud van Asseldonk
5bcc7e74c7 Generate builtins list for the smith fuzzer
This moves one more place of duplication of builtins into
generate_keywords.py as a single source of truth, resolving
a to do in the smith fuzzer.

This does once more shuffle all of these around in the fuzzer, which
makes the existing fuzz corpus mostly meaningless. Fortunately, this
should be the last time that this happens: with the new approach we
can modify the builtins with minimal changes to the meaning of the
fuzz corpus, which is something that I wanted for a long time.
2025-03-15 19:43:13 +01:00
Ruud van Asseldonk
34e347a387 Generate fuzz dictionary from Pygments grammar
I regularly add new methods, and it's becoming tedious to have to
remember to update all the places that reference these, so let's
generate them and automate the process. For now, I'm choosing the
Pygments grammar as the source of truth, and the first target to
generate is the fuzz dictionary.
2025-03-03 22:14:27 +01:00
Ruud van Asseldonk
59be133128 Bump version to 0.8.0
I'm leaving the Zed extension pointing to the older commit of the
Tree-sitter grammar, I'll update that after this version bump. It's
a bit awkward to do it this way around, but there are circular
dependencies that can't be avoided. Maybe with an attack on SHA1 it
can be done in theory, but let's not go there.
2025-03-02 21:15:33 +01:00
Ruud van Asseldonk
f0e4cd13b5 Add Number.round method
At first I also wanted to support rounding to a negative number of
decimals (so rounding to a positive power of 10), but scope creep,
complications ... I don't need it, and we can always add that later.
2025-03-02 18:32:21 +01:00
Ruud van Asseldonk
eaeb54c424 Simplify and document fuzz_decimal fuzzer 2025-02-25 20:49:40 +01:00
Ruud van Asseldonk
27f91ac012 Remove various final references to Int 2025-02-24 21:08:49 +01:00
Ruud van Asseldonk
c7b62c9ee5 Add reminder to remove Int from fuzzer 2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
e9748822c2 Implement Decimal::checked_add
The implementation is a bit naive, but it's good enough for now.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
caa6605bf9 Allow parsing negative decimals
This makes the testing code and fuzzers a bit cleaner.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
fda5cf01c6 Extend decimal fuzzer for roundtrips, fix issue
It found one issue right away, related to using an i16::MIN exponent,
which overflows the way we parsed. But then I realized there are a few
other bugs in the number parser ... I added a marker for one and fixed
handling of the implicit exponent offsets.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
9030ffd528 Store decimal point and exponent separately
This enables us treat numbers with an exponent losslessly. We don't
conflate the decimal point with the exponent in case they get in the
way of each other.

It also greatly simplifies the formatting. We can mechanically format
the representation now, without having to use heuristics for when to
switch to scientific notation. The catch is of course that the
heuristics will need to move elsewhere. We'll have to normalize the
numbers after arithmetic operations.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
3dafed4571 Rename Num type to Number, add highlighting for it
RCL aims to be obvious to understand. Num might be cryptic for new users,
and although we also have "Int" rather than "Integer", that one is very
established, "Num" may be a bit too obscure. (We also have "String"
rather than "Str", consistency ...). It's a type that I expect has
little use for end-users, but it shows up in the negation error message,
so let's make it unambiguous and call it "Number".
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
18ba7e9176 Extend Decimal fuzzer to cover more cases 2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
94feb8cb9d Add a fuzzer to test various Decimal properties
This is only the start, but let's verify Decimal::cmp against f64::cmp.
It instantly finds an input where they disagree:

	Compare {
	    a: NormalF64(
	        -0.16406250000007813,
	    ),
	    b: NormalF64(
	        0.0,
	    ),
	}
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
2862a21512 Add surrogate pair exception to json superset fuzzer
Surrogate pairs are not supported by RCL on purpose, so when that can be
parsed by Serde but is rejected by RCL, we shouldn't fail the fuzzer on
it.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
5480b97450 Add float parsing exception to TOML fuzzer
RCL can handle larger exponents on floats, we have to admit that then.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
de496af1be Add decimal parsing exception to fuzzer
This adds back the exception that was removed by allowing float parsing
imprecision, though in a more limited form initially because it only
affected exponents.

But after running the fuzzer for a bit longer, it also affects large
integers, so we are back to the start, overflow is just an intentional
incompatibility.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
647e457d67 Allow parsing floats that overflow i64
This removes one case of incompatibility with Serde. If you write a
float literal that is too precise to be represented exactly, then we now
silently round it rather than treating it as an overflow error. I think
this is acceptable because if you are in the case where you care about
numbers to 19 significant digits then probably RCL is not the best tool
for what you are doing, but the case where we encounter some arbitrary
json that we want to query with "rcl jq" and it happens to have some
humongous float in it, that is probably more likely. Python handles
float literals in this way too so I think it's okay.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
4e3efcdedb Add fuzzer exception for large exponent numbers
The choice I went with is to have a 16-bit exponent, which gives RCL's
float/decimal type more range than a regular f64. Now the fuzzer can
generate an input with a large exponent, and RCL will happily echo it,
and it's technically syntactically valid json, but Serde rejects it with
"number out of range" (in the same way that RCL rejects some numbers as
overflow). So add an exception for this mismatch.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
d2df2dd48a Relax Serde json fuzzer slightly
RCL rejects 9223372036854775807.576460752303423487 with an overflow
error, but I think that is fine, I don't want to lose precision on
inputs.
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
65a0984c2f Add fuzzer to ensure RCL is a json superset
The one thing that prevents that right now is floats, and the fuzzer
discovered it within a few seconds:

  ╭──────╴ Opcode (hex)
  │  ╭───╴ Argument (hex)
  │  │  ╭╴ Operation, argument (decimal)
  26 03 ExprPushInput, 3
        take_str, 3   → "4e2"
  e6 01 ModeJsonSuperset, 1
  EvalJsonSuperset -->
  4e2
2025-02-24 20:42:59 +01:00
Ruud van Asseldonk
6c0734148f Add List.sort_by and Set.sort_by methods
I realized today that I want this. In particular, the API of my music
player Musium returns albums with a numeric playcount and discovery
score, and I want to sort on that. Finally that is possible now that I
am adding support for floats. But I need a way to sort on one field of
a dict! Arguably this is more important than the bare sort itself.

While I do this for lists, we can do the same for sets.
2025-02-24 19:44:04 +01:00
Ruud van Asseldonk
9dc5092279 Bump version to 0.7.0 2024-12-31 13:34:37 +01:00
Ruud van Asseldonk
2aabff47ee Dogfood-generate pyrcl Cargo.toml as well 2024-12-08 15:42:28 +01:00
Ruud van Asseldonk
aa55c4b076 Move fuzz/Cargo.toml to dogfood generated file 2024-12-08 15:42:28 +01:00
Ruud van Asseldonk
4104ba3a2f Fix typo in doc comment 2024-12-08 12:36:27 +01:00
Ruud van Asseldonk
663d954c33 Fuzz new List and Set methods
And confirm that the fuzzer works by temporarily putting panics in the
new code paths (not part of this commit).
2024-12-07 20:57:26 +01:00
Ruud van Asseldonk
2601582abe Add std.empty_set constant
It started to get annoying to have to define it myself every time, so
let's just add it properly now. This also resolves the longstanding
issue in the RCL pretty-printer that we have no good way to print the
empty set -- now we do!
2024-12-07 20:26:40 +01:00
Ruud van Asseldonk
dff50984d9 Document and highlight new List.sort method 2024-12-01 13:15:28 +01:00
Ruud van Asseldonk
1bac59668e Bump version to 0.6.0
See docs/changelog.md for a summary of the changes in this release.
2024-12-01 11:52:49 +01:00
Ruud van Asseldonk
c51b77a59e Add 'rcl re' and 'rcl rq' shorthands for -fraw
This is somewhat common, especially when used as jq replacement, so
let's add a shorthand for them.
2024-08-23 22:14:41 +02:00
Ruud van Asseldonk
05ceea94c0 Use new if-else syntax in Smith fuzzer
It means the fuzzer gets to explore less, actually, but we still have
the source-based fuzzer that will find the case where the colon is
missing, and which could hunt for non-idempotencies in the formatter and
such.
2024-07-31 21:53:58 +02:00
Ruud van Asseldonk
28d920e4ac Bump version to 0.5.0 2024-07-28 21:43:29 +02:00
Ruud van Asseldonk
12ffe62cf4 Address a few minor issues in the new build code
Caught in self-review, and I don't feel like turning them into fixups
for all of the commits that introduced these.
2024-07-27 23:03:05 +02:00