lulu 0.0.721

A mini lua runtime
Documentation
use std::cmp::min;

#[derive(Debug, Clone)]
pub struct SourceMapEntry {
  pub lulu_line: usize,
  #[allow(unused)]
  pub lulu_col: usize,
  pub lua_line: usize,
  #[allow(unused)]
  pub lua_col: usize,
}

#[allow(unused)]
pub fn generate_sourcemap(lulu_source: &str, lua_output: &str) -> Vec<SourceMapEntry> {
  let lulu_chars: Vec<(usize, usize, char)> = lulu_source
    .lines()
    .enumerate()
    .flat_map(|(line_idx, line)| {
      line
        .chars()
        .enumerate()
        .map(move |(col_idx, c)| (line_idx, col_idx, c))
    })
    .collect();

  let lua_chars: Vec<(usize, usize, char)> = lua_output
    .lines()
    .enumerate()
    .flat_map(|(line_idx, line)| {
      line
        .chars()
        .enumerate()
        .map(move |(col_idx, c)| (line_idx, col_idx, c))
    })
    .collect();

  let mut dp = vec![vec![0; lua_chars.len() + 1]; lulu_chars.len() + 1];
  for i in 1..=lulu_chars.len() {
    for j in 1..=lua_chars.len() {
      if lulu_chars[i - 1].2 == lua_chars[j - 1].2 {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]);
      }
    }
  }

  let mut sourcemap = Vec::new();
  let mut i = lulu_chars.len();
  let mut j = lua_chars.len();
  while i > 0 && j > 0 {
    if lulu_chars[i - 1].2 == lua_chars[j - 1].2 {
      sourcemap.push(SourceMapEntry {
        lulu_line: lulu_chars[i - 1].0,
        lulu_col: lulu_chars[i - 1].1,
        lua_line: lua_chars[j - 1].0,
        lua_col: lua_chars[j - 1].1,
      });
      i -= 1;
      j -= 1;
    } else if dp[i - 1][j] >= dp[i][j - 1] {
      i -= 1;
    } else {
      j -= 1;
    }
  }

  sourcemap.reverse();

  let mut filled = Vec::new();
  let mut last: Option<SourceMapEntry> = None;
  for entry in sourcemap {
    if let Some(prev) = last {
      let lua_gap = entry.lua_line.saturating_sub(prev.lua_line);
      let lulu_gap = entry.lulu_line.saturating_sub(prev.lulu_line);
      if lua_gap > 1 || lulu_gap > 1 {
        for i in 1..=lua_gap {
          filled.push(SourceMapEntry {
            lulu_line: prev.lulu_line + min(i, lulu_gap),
            lulu_col: 0,
            lua_line: prev.lua_line + i,
            lua_col: 0,
          });
        }
      }
    }
    filled.push(entry.clone());
    last = Some(entry);
  }

  filled
}

#[allow(unused)]
fn levenshtein(a: &str, b: &str) -> usize {
  let mut costs: Vec<usize> = (0..=b.len()).collect();
  for (i, ca) in a.chars().enumerate() {
    let mut last_cost = i;
    let mut new_costs = vec![i + 1];
    for (j, cb) in b.chars().enumerate() {
      let insertion = costs[j + 1] + 1;
      let deletion = new_costs[j] + 1;
      let substitution = if ca == cb { costs[j] } else { costs[j] + 1 };
      new_costs.push(*[insertion, deletion, substitution].iter().min().unwrap());
    }
    costs = new_costs;
  }
  *costs.last().unwrap()
}

#[allow(unused)]
pub fn lookup_lua_to_lulu(
  lua_line: usize,
  lua_col: usize,
  sourcemap: &[SourceMapEntry],
) -> Option<(usize, usize)> {
  sourcemap
    .iter()
    .filter(|entry| entry.lua_line == lua_line)
    .min_by_key(|entry| (entry.lua_col as isize - lua_col as isize).abs())
    .map(|entry| (entry.lulu_line, entry.lulu_col))
}

#[allow(unused)]
pub fn lookup_lulu_to_lua(
  lulu_line: usize,
  lulu_col: usize,
  sourcemap: &[SourceMapEntry],
) -> Option<(usize, usize)> {
  sourcemap
    .iter()
    .filter(|entry| entry.lulu_line == lulu_line)
    .min_by_key(|entry| (entry.lulu_col as isize - lulu_col as isize).abs())
    .map(|entry| (entry.lua_line, entry.lua_col))
}

#[cfg(test)]
mod test {
  use super::*;
  use crate::compiler::Compiler;

  #[test]
  fn test_sourcemap() {
    let mut compiler = Compiler::new(None);

    let lulu = r#"r#"
a = 1
b = 2


c = 4


macro {
    hello($dd) { $dd }
}

local name = hello! "ddd";

func(() =>
    print('hi')
end)
"#;
    let lua = compiler.compile(lulu, None, None);

    let map = generate_sourcemap(lulu, &lua);
    for entry in map {
      println!(
        "Lua [{}:{}] -> Lulu [{}:{}]",
        entry.lua_line, entry.lua_col, entry.lulu_line, entry.lulu_col
      );
    }
  }
}