pasta_lua 0.2.1

Pasta Lua - Lua integration for Pasta DSL
Documentation
//! Producer-side source-map seam for the Lua code generator (R4: feasibility).
//!
//! This module defines ONLY the producer side of the `.pasta` <-> `.lua` source-map
//! seam: the [`SourceMapSink`] trait that the [`crate::code_gen::LuaCodeGenerator`]
//! calls to record `(generated_lua_line -> .pasta span)` correspondences, plus a
//! lightweight [`PastaPos`] position type for the seam.
//!
//! # Dependency direction (CRITICAL)
//!
//! The design's dependency direction is `debug -> code_gen`: the `debug` module
//! *consumes* the span material that `code_gen` produces. Therefore the producer
//! seam lives here in `code_gen` and `code_gen` MUST NOT depend on `debug`. The
//! concrete consumer-side production map (`MapBuilderSink` -> `ChunkSourceMap` ->
//! `SourceMap`) lives in `debug/source_map.rs` (always compiled) and implements
//! [`SourceMapSink`] to build its map from the records emitted here.
//!
//! # Inert when no sink is attached
//!
//! When no sink is attached (the default for ALL production transpile callers) the
//! seam is inert and the generated bytes are unchanged (zero-cost). The consumer
//! side resolves `.pasta` <-> `.lua` correspondences bidirectionally through
//! [`SourceMap::resolve_lua_to_pasta`](crate::debug::source_map::SourceMap) and
//! `resolve_pasta_to_lua` in `debug/source_map.rs`.

use pasta_dsl::parser::Span;

/// A position in an original `.pasta` source file.
///
/// Used by the consumer side (`ChunkSourceMap` / `SourceMap` in
/// `debug/source_map.rs`) to express a resolved `.pasta` coordinate. Kept in
/// `code_gen` so the seam's vocabulary does not force `debug` types into the
/// generator (preserving the `debug -> code_gen` direction).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PastaPos {
    /// Originating `.pasta` file path (as known to the generator/loader).
    pub file: String,
    /// 1-based line number in the `.pasta` source.
    pub line: u32,
}

/// Sink that records `(generated_lua_line -> .pasta span)` correspondences.
///
/// The [`LuaCodeGenerator`](crate::code_gen::LuaCodeGenerator) calls
/// [`record`](SourceMapSink::record) for each generated Lua line whose originating
/// `.pasta` [`Span`] is known. `lua_line` is the 1-based line number of the emitted
/// output line (the generator's `out_line` value at the moment the line was written).
///
/// Implementors build whatever structure they need from the stream of records (e.g.
/// the consumer-side `MapBuilderSink` in `debug/source_map.rs`). When no sink is
/// attached (the default for ALL production transpile callers) the seam is inert and
/// the generated bytes are unchanged.
pub trait SourceMapSink {
    /// Map output `.lua` line `lua_line` (1-based) directly to `.pasta` line
    /// `pasta_line` (1-based). This is the CORE recording operation, used for
    /// per-line offset mapping (e.g. code blocks, where one source line maps to a
    /// distinct output line). All recording funnels through this method.
    fn record_line(&mut self, lua_line: u32, pasta_line: u32);

    /// Record that generated Lua line `lua_line` (1-based) originates from `span`.
    ///
    /// Sugar over [`record_line`](SourceMapSink::record_line): uses
    /// `span.start_line` as the `.pasta` line. Existing span-based callers are
    /// unchanged; the default delegates to `record_line` so any implementor only
    /// needs to provide `record_line`.
    fn record(&mut self, lua_line: u32, span: Span) {
        self.record_line(lua_line, span.start_line as u32);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Tiny test sink capturing each `(lua_line, pasta_line)` mapping it receives.
    #[derive(Default)]
    struct CapturingSink {
        records: Vec<(u32, u32)>,
    }

    impl SourceMapSink for CapturingSink {
        fn record_line(&mut self, lua_line: u32, pasta_line: u32) {
            self.records.push((lua_line, pasta_line));
        }
    }

    /// Sugar equivalence (議題3 / Requirements 1.1): `record(lua_line, span)` must
    /// produce the SAME captured mapping as a direct `record_line(lua_line, P)`
    /// when `span.start_line == P`. This is the task's completion criterion:
    /// the span-based API is sugar over the direct line API.
    #[test]
    fn record_is_sugar_for_record_line() {
        const LUA_LINE: u32 = 11;
        const PASTA_LINE: u32 = 2;

        // Direct line mapping.
        let mut direct = CapturingSink::default();
        direct.record_line(LUA_LINE, PASTA_LINE);

        // Span-based sugar: span whose start_line == PASTA_LINE.
        let mut via_span = CapturingSink::default();
        // Span::new(start_line, start_col, end_line, end_col, start_byte, end_byte)
        let span = Span::new(PASTA_LINE as usize, 3, PASTA_LINE as usize, 20, 42, 60);
        via_span.record(LUA_LINE, span);

        // Both APIs yield the identical mapping (L -> P).
        assert_eq!(
            direct.records,
            vec![(LUA_LINE, PASTA_LINE)],
            "record_line maps the output .lua line directly to the .pasta line"
        );
        assert_eq!(
            via_span.records, direct.records,
            "record(lua_line, span) must be sugar for record_line(lua_line, span.start_line)"
        );
    }

    /// Requirements 1.3: when a single `.pasta` element produces multiple `.lua`
    /// lines, ALL of them map (to the same `.pasta` line). Recording several
    /// output lines for one `.pasta` line yields a mapping for every output line.
    #[test]
    fn multiple_lua_lines_map_to_same_pasta_line() {
        const PASTA_LINE: u32 = 5;
        let mut sink = CapturingSink::default();
        sink.record_line(10, PASTA_LINE);
        sink.record_line(11, PASTA_LINE);
        sink.record_line(12, PASTA_LINE);

        assert_eq!(
            sink.records,
            vec![(10, PASTA_LINE), (11, PASTA_LINE), (12, PASTA_LINE)],
            "1.3: every .lua line produced by one .pasta element maps to that .pasta line"
        );
    }
}