Skip to content

✨ feat(lang): add table module for extracting table structures#1222

Merged
harehare merged 8 commits intomainfrom
feat/add-table-module
Feb 6, 2026
Merged

✨ feat(lang): add table module for extracting table structures#1222
harehare merged 8 commits intomainfrom
feat/add-table-module

Conversation

@harehare
Copy link
Copy Markdown
Owner

@harehare harehare commented Feb 5, 2026

  • Add new table standard module with tables function to extract table structures from markdown nodes
  • Fix TableAlign node name from "table_header" to "table_align"
  • Add is_table_cell and rename is_table_header to is_table_align in builtin functions

- Add new `table` standard module with `tables` function to extract
  table structures from markdown nodes
- Fix TableAlign node name from "table_header" to "table_align"
- Add `is_table_cell` and rename `is_table_header` to `is_table_align`
  in builtin functions
- Add missing "section" module name mapping in resolver
Copilot AI review requested due to automatic review settings February 5, 2026 14:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds table extraction functionality to mq by introducing a new standard module and fixing an incorrect node name for table alignment nodes.

Changes:

  • Added a new table standard module with a tables function to extract structured table data from markdown nodes
  • Fixed the TableAlign node name from "table_header" to "table_align" to accurately reflect its purpose
  • Updated builtin functions to include is_table_cell and renamed is_table_header to is_table_align for consistency

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/mq-markdown/src/node.rs Corrects the node name for TableAlign from "table_header" to "table_align"
crates/mq-lang/src/module/resolver.rs Adds "section" module mapping (appears unrelated to PR purpose)
crates/mq-lang/src/module.rs Registers the new "table" standard module
crates/mq-lang/modules/table.mq Implements the tables function for extracting table structures
crates/mq-lang/builtin.mq Renames is_table_header to is_table_align and adds is_table_cell helper

"xml" => Cow::Borrowed("xml.mq"),
"toml" => Cow::Borrowed("toml.mq"),
"test" => Cow::Borrowed("test.mq"),
"section" => Cow::Borrowed("section.mq"),
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The addition of 'section' module mapping appears unrelated to this PR's purpose of adding table functionality. This change should either be removed or explained in the PR description if it's an intentional inclusion.

Suggested change
"section" => Cow::Borrowed("section.mq"),

Copilot uses AI. Check for mistakes.
| current_row = [node]
end
else:
current_row += node
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

This line should append [node] instead of node to maintain consistency with line 22 where current_row is assigned [node]. The current code attempts to concatenate a single node object instead of a list.

Suggested change
current_row += node
current_row += [node]

Copilot uses AI. Check for mistakes.
end
else:
current_row += node
elif (is_table_align(node)): current_align = node
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The current_align should be assigned as a list [node] or the entire node's alignment data, not the node itself. Based on line 29 where it's used in align: current_align, this assignment may not provide the expected structure for the table's align property.

Suggested change
elif (is_table_align(node)): current_align = node
elif (is_table_align(node)): current_align = [node]

Copilot uses AI. Check for mistakes.
| while (i < nodes_len):
let node = md_nodes[i]
| if (is_table_cell(node)):
if (node.row != current_row_index) do
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The logic assumes that node.row exists as a property on table cell nodes. This assumption should be documented in a comment explaining the expected structure of table cell nodes, or verified that this property is guaranteed to exist.

Copilot generated this review using guidance from repository custom instructions.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Feb 5, 2026

CodSpeed Performance Report

Merging this PR will not alter performance

Comparing feat/add-table-module (cd209e4) with main (45ee3f6)1

Summary

✅ 29 untouched benchmarks

Footnotes

  1. No successful run was found on main (b65dd3b) during the generation of this report, so 45ee3f6 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copilot AI review requested due to automatic review settings February 6, 2026 13:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


# Convert a table structure back into a list of markdown nodes.
def to_markdown(table):
table[:header] + table[:align] + flatten(table[:rows])
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

The table[:align] needs to be wrapped in an array. Based on line 26 where current_align = node, align is a single node, not an array. This should be table[:header] + [table[:align]] + flatten(table[:rows]) to properly concatenate the alignment node into the result.

Suggested change
table[:header] + table[:align] + flatten(table[:rows])
table[:header] + [table[:align]] + flatten(table[:rows])

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 6, 2026 14:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

else: do
var i = 0
| let cells = foreach (cell, row):
to_md_table_cell(cell, len(table[:rows]), i)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

The row index should be len(table[:rows]) + 1 instead of len(table[:rows]) because rows are 1-indexed based on the existing row count plus the new row being added.

Suggested change
to_md_table_cell(cell, len(table[:rows]), i)
to_md_table_cell(cell, len(table[:rows]) + 1, i)

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 6, 2026 14:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

"xml" => Cow::Borrowed("xml.mq"),
"toml" => Cow::Borrowed("toml.mq"),
"test" => Cow::Borrowed("test.mq"),
"section" => Cow::Borrowed("section.mq"),
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

"section"is already a standard module and this PR introduces"table", but the resolver mapping adds "section"again instead of"table". This likely prevents import "table"from resolving totable.mq. Change this match arm to map "table"to"table.mq"(or remove it entirely if the defaultformat!("{}.mq", name)` resolution is the intended mechanism for standard modules).

Suggested change
"section" => Cow::Borrowed("section.mq"),
"table" => Cow::Borrowed("table.mq"),

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +26
current_row += node
elif (is_table_align(node)): current_align = node
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

There are two concrete issues here that will break table extraction/serialization:

  1. current_row += node likely appends a non-array value; elsewhere you use [node] when constructing/appending rows. This should be consistent (e.g., append a single-element array) so current_row remains an array of cells.
  2. current_align is initialized as [] and later set to node (a single node). But to_markdown(table) later does table[:header] + table[:align] + ..., which will fail if :align is not an array. Store current_align as an array (e.g., [node]) or adjust to_markdown to handle a single node.
Suggested change
current_row += node
elif (is_table_align(node)): current_align = node
current_row += [node]
elif (is_table_align(node)): current_align = [node]

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +34
else: do
if (!is_empty(current_rows)) do
tables += [{type: :table, align: current_align, header: current_rows[0], rows: current_rows[1:]}]
| current_align = []
| current_rows = []
| current_row = []
| current_row_index = -1
end
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

When encountering a non-table node, you finalize a table if current_rows is non-empty, but you don’t first flush current_row into current_rows. If the last parsed row is currently in current_row, it will be dropped at this boundary (you only flush current_row after the loop). Fix by appending current_row into current_rows (when non-empty) before building/pushing the table object in this branch.

Copilot uses AI. Check for mistakes.
else: do
var i = 0
| let cells = foreach (cell, row):
to_md_table_cell(cell, len(table[:rows]), i)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

Row indexing is inconsistent with how tables() builds {header: current_rows[0], rows: current_rows[1:]} (i.e., header is row 0, data rows start at 1). Using len(table[:rows]) as the new row index will collide with an existing row index when there are already N data rows (e.g., for 2 rows, len(rows)=2 but the next row index should be 3). Adjust the created cell row index to account for the header row offset.

Suggested change
to_md_table_cell(cell, len(table[:rows]), i)
to_md_table_cell(cell, len(table[:rows]) + 1, i)

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +75
def add_column(table, col):
if (len(col) != len(table[:rows]) + 1):
error("Column length does not match table row count.")
else: do
let header_cell = to_md_table_cell(col[0], 0, len(table[:header]))
| let new_header = table[:header] + [header_cell]
| var i = 0
| let new_rows = foreach (row, table[:rows]):
let cell = to_md_table_cell(col[i], i + 1, len(row))
| i += 1
| row + [cell]
end
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

The per-row column values are off by one: col[0] is used for the header (good), but the first data row also uses col[0] because the loop uses col[i] with i = 0. The first data row should use col[1], second should use col[2], etc. Start i at 1 for the loop, or index col[i + 1].

Copilot uses AI. Check for mistakes.
def test_table_add_column():
let t = first(table::tables(table_nodes))
| let t = table::add_column(t, ["Country", "Japan", "Japan"])
| assert_eq(len(t[:header]), 3)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

After adding a column, len(t[:header]) should increase by 1. With the provided table it should become 4, but the test asserts 3, which will fail (or mask a real bug if add_column is currently incorrect). Update the assertion to match the expected post-condition for add_column.

Suggested change
| assert_eq(len(t[:header]), 3)
| assert_eq(len(t[:header]), 4)

Copilot uses AI. Check for mistakes.
@harehare harehare merged commit 0150620 into main Feb 6, 2026
7 checks passed
@harehare harehare deleted the feat/add-table-module branch February 6, 2026 15:05
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