Skip to content

Conversation

@dpgeorge
Copy link
Member

Summary

During the discussion about t-string support #17557, I thought that it might not be much effort to support nested f-strings with the current f-string parser. And it turned out relatively simple.

The way the MicroPython f-string parser works is:

  1. it extracts the f-string arguments (things in curly braces) into a temporary buffer (a vstr)
  2. once the f-string ends (reaches its closing quote) the lexer switches to tokenizing the temporary buffer
  3. once the buffer is empty it switches back to the stream.

The temporary buffer can easily hold f-strings itself (ie nested f-strings) and they can be re-parsed by the lexer using the same algorithm. The only thing stopping that from working is that the temporary buffer can't be reused for the nested f-string because it's currently being parsed.

This PR fixes that by adding a second temporary buffer, which is the "injection" buffer. That allows arbitrary number of nestings with a simple modification to the original algorithm:

  1. when an f-string is encountered the string is parsed and its arguments are extracted into fstring_args
  2. when the f-string finishes, fstring_args is inserted into the current position in inject_chrs (which is the start of that buffer if no injection is ongoing)
  3. fstring_args is now cleared and ready for any further f-strings (nested or not)
  4. the lexer switches to inject_chrs if it's not already reading from it
  5. if an f-string appeared inside the f-string then it is in inject_chrs and can be processed as before, extracting its arguments into fstring_args, which can then be inserted again into inject_chrs
  6. once inject_chrs is exhausted (meaning that all levels of f-strings have been fully processed) the lexer switched back to tokenizing the stream

Amazingly, this scheme supports arbitrary numbers of nestings of f-strings using the same quote style.

Testing

A new test is added which will run under CI.

Trade-offs and Alternatives

This adds some code size and a bit more memory usage for the lexer. In particular for a single (non-nested) f-string it now makes an extra copy of the fstring_args data, when copying it across to inject_chrs. That could possibly be optimized to reuse the same buffer (inject_chrs would steal the memory from fstring_args).

Otherwise, memory use only goes up with the complexity of nested f-strings.

Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge dpgeorge added the py-core Relates to py/ directory in source label Dec 18, 2025
@github-actions
Copy link

github-actions bot commented Dec 18, 2025

Code size report:

Reference:  esp32/machine_sdcard: Fix SDMMC slot assignment for non-default slots. [e67d4a2]
Comparison: py/lexer: Eliminate need for saved cached chars. [merge of ac7c57e]
  mpy-cross:   +32 +0.008% 
   bare-arm:    +0 +0.000% 
minimal x86:   -29 -0.015% 
   unix x64:    +0 +0.000% standard
      stm32:   +56 +0.014% PYBV10
     mimxrt:   +56 +0.015% TEENSY40
        rp2:   +72 +0.008% RPI_PICO_W
       samd:   +56 +0.021% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:   +56 +0.012% VIRT_RV32

Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge dpgeorge force-pushed the py-lexer-support-nested-fstrings branch from aba1901 to b51b102 Compare December 18, 2025 13:28
@codecov
Copy link

codecov bot commented Dec 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.38%. Comparing base (e67d4a2) to head (ac7c57e).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #18588      +/-   ##
==========================================
- Coverage   98.38%   98.38%   -0.01%     
==========================================
  Files         171      171              
  Lines       22300    22294       -6     
==========================================
- Hits        21939    21933       -6     
  Misses        361      361              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This saves about 30 bytes on ARM Cortex-M, and about 100 bytes on
x86-64.

Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge dpgeorge force-pushed the py-lexer-support-nested-fstrings branch from 6344bce to 81c841e Compare December 19, 2025 01:11
Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge dpgeorge force-pushed the py-lexer-support-nested-fstrings branch from 81c841e to ac7c57e Compare December 19, 2025 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

py-core Relates to py/ directory in source

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant