From: Thomas Walker Lynch Date: Tue, 13 Jan 2026 14:19:14 +0000 (+0000) Subject: RT code reformatting progress pt X-Git-Url: https://git.reasoningtechnology.com/?a=commitdiff_plain;h=b35139f64486553a9ff9a8b8520b30ef11cb2904;p=Epimetheus%2F.git RT code reformatting progress pt --- diff --git a/developer/document.zip b/developer/document.zip deleted file mode 100644 index 8ba6b34..0000000 Binary files a/developer/document.zip and /dev/null differ diff --git a/developer/document/Epimetheus.html b/developer/document/Epimetheus.html index cd06c98..75e3026 100644 --- a/developer/document/Epimetheus.html +++ b/developer/document/Epimetheus.html @@ -1,139 +1,139 @@ - - - Epimetheus: The Only Primitive - - - - - - - -

Given

-

The English language is a given, apparently.

-

- This discussion happens to appear in the context of a Python language - implementation, though it applies equally as well to other languages. -

- -

Symbol

-

Definition

-

- We have a symbol instance factory. Each time the make - method on the factory is called, the factory returns an instance of a - distinct symbol. -

- -

- A symbol instance may be copied as though it were an integer. Thus, a - common method for making a copy is assignment, e.g., - x = y, where y is a variable holding - a symbol instance. After execution of the assignment, x - will hold another instance of the symbol. -

- -

- Any two, or by transitivity, any number of, symbol instances can be - compared for equality using an integer equality comparison: - x == y. A comparison between symbol-holding variables - will always return True or False. -

- -

- It follows that any two symbol instances directly returned from the - factory will always equality compare to be False. -

- -

- An equivalence set of symbol instances is a functional representation of - the symbol. The name of this set is yet another instance of said symbol. - Thus, every instance of the symbol represents the symbol. -

- -

- Though symbol instances are integer-like for copy and equality compare, - they are disallowed from being used with other integer operators. - Symbols cannot be compared for greater-than or less-than; they cannot be - incremented, added, nor subtracted, etc. -

- -

Distinctness Across Contexts

-

- If a symbol persists across contexts, such as across scopes or processes, - it must remain distinct from all other symbols as it enters into those - new processes or contexts. -

- -

- In this implementation, we meet this constraint by using a global symbol - factory and by explicitly disallowing a symbol from being used outside - the process it was made in. -

- -

Object

-

Definition

-

- The term "Python object" refers to anything that can or could - appear in Python code and could be associated with a symbol. This - includes symbols themselves. -

- -

- A symbol is associated with an object using a Python dictionary. The - symbol is the key, and the object is that thing which is "looked up" - via said key. -

- -

Path

-

Individual Path

-

- In the TTCA, we provided a formal definition for a "tape." A tape has a - leftmost cell, one or more "in-between" cells, and a rightmost cell. - A "path" is a special case of a tape with additional constraints. -

- -

- Each cell of a path holds a symbol. The leftmost cell is called the - destination. The reader might have expected the path - to go from left to right, but such a definition would run into a problem, - as left-to-right traversal might not ever end. -

- -
- Think about it this way: you are sitting in a pub of an inn, and a - stranger walks in. Though you know the stranger's current destination, - you might not know where he came from. -
- -

- All cells of the path to the right of the destination cell are called - "the way." If we were to drop the leftmost cell from the tape, then we - would find "the way," if it exists, to be another tape. -

- -

Discrete Function

-

- Consider another tape where each cell holds a path. If we compare all - paths on said tape and find that no two identical "ways" lead to - different destinations, we call said tape a discrete function. -

- - - destination = f(way) - - -

- This notation is possible because the same way always leads to the given - destination. -

- - - - - + + + Epimetheus: The Only Primitive + + + + + + + +

Given

+

The English language is a given, apparently.

+

+ This discussion happens to appear in the context of a Python language + implementation, though it applies equally as well to other languages. +

+ +

Symbol

+

Definition

+

+ We have a symbol instance factory. Each time the make + method on the factory is called, the factory returns an instance of a + distinct symbol. +

+ +

+ A symbol instance may be copied as though it were an integer. Thus, a + common method for making a copy is assignment, e.g., + x = y, where y is a variable holding + a symbol instance. After execution of the assignment, x + will hold another instance of the symbol. +

+ +

+ Any two, or by transitivity, any number of, symbol instances can be + compared for equality using an integer equality comparison: + x == y. A comparison between symbol-holding variables + will always return True or False. +

+ +

+ It follows that any two symbol instances directly returned from the + factory will always equality compare to be False. +

+ +

+ An equivalence set of symbol instances is a functional representation of + the symbol. The name of this set is yet another instance of said symbol. + Thus, every instance of the symbol represents the symbol. +

+ +

+ Though symbol instances are integer-like for copy and equality compare, + they are disallowed from being used with other integer operators. + Symbols cannot be compared for greater-than or less-than; they cannot be + incremented, added, nor subtracted, etc. +

+ +

Distinctness Across Contexts

+

+ If a symbol persists across contexts, such as across scopes or processes, + it must remain distinct from all other symbols as it enters into those + new processes or contexts. +

+ +

+ In this implementation, we meet this constraint by using a global symbol + factory and by explicitly disallowing a symbol from being used outside + the process it was made in. +

+ +

Object

+

Definition

+

+ The term "Python object" refers to anything that can or could + appear in Python code and could be associated with a symbol. This + includes symbols themselves. +

+ +

+ A symbol is associated with an object using a Python dictionary. The + symbol is the key, and the object is that thing which is "looked up" + via said key. +

+ +

Path

+

Individual Path

+

+ In the TTCA, we provided a formal definition for a "tape." A tape has a + leftmost cell, one or more "in-between" cells, and a rightmost cell. + A "path" is a special case of a tape with additional constraints. +

+ +

+ Each cell of a path holds a symbol. The leftmost cell is called the + destination. The reader might have expected the path + to go from left to right, but such a definition would run into a problem, + as left-to-right traversal might not ever end. +

+ +
+ Think about it this way: you are sitting in a pub of an inn, and a + stranger walks in. Though you know the stranger's current destination, + you might not know where he came from. +
+ +

+ All cells of the path to the right of the destination cell are called + "the way." If we were to drop the leftmost cell from the tape, then we + would find "the way," if it exists, to be another tape. +

+ +

Discrete Function

+

+ Consider another tape where each cell holds a path. If we compare all + paths on said tape and find that no two identical "ways" lead to + different destinations, we call said tape a discrete function. +

+ + + destination = f(way) + + +

+ This notation is possible because the same way always leads to the given + destination. +

+ + + +
+ diff --git a/developer/document/style/RT_code.js b/developer/document/style/RT_code.js index e5c8266..6c6e232 100644 --- a/developer/document/style/RT_code.js +++ b/developer/document/style/RT_code.js @@ -1,6 +1,6 @@ /* - Processes tags using StyleRT utilities. - Instrumented with token-based debugging. + Processes tags. + Uses the central config or CSS variables from the theme. */ function RT_code() { const RT = window.StyleRT; @@ -9,30 +9,20 @@ function RT_code() { debug.log('RT_code', 'Starting render cycle.'); - // 1. Fetch Theme - const theme = RT.active_theme ? RT.active_theme() : {}; - const accent = theme.accent || 'gold'; - - // 2. Physics const metrics = U.measure_ink_ratio('monospace'); - document.querySelectorAll('rt-code').forEach((el, index) => { + // Scoped Selector: find code blocks anywhere, but styling relies on inheritance + document.querySelectorAll('rt-code').forEach((el) => { el.style.fontFamily = 'monospace'; - // Analysis - const is_block = U.is_block_content(el); - const parentStyle = window.getComputedStyle(el.parentElement); - const parentColor = parentStyle.color; + // Check context for accent color (CSS Variable fallback) + const computed = window.getComputedStyle(el); + const accent = computed.getPropertyValue('--rt-accent').trim() || 'gold'; - // Physics + const is_block = U.is_block_content(el); + const parentColor = computed.color; const is_text_light = U.is_color_light(parentColor); - if (index === 0) { - // Always log the first one if token is active, for sanity check - debug.log('RT_code', `Sample #0: Parent Color "${parentColor}" -> Detect Light? ${is_text_light}`); - } - - // Visuals const alpha = is_block ? 0.08 : 0.15; const overlay = is_text_light ? `rgba(255,255,255,${alpha})` : `rgba(0,0,0,${alpha})`; const text_color = is_text_light ? '#ffffff' : '#000000'; @@ -42,17 +32,18 @@ function RT_code() { if (is_block) { el.style.display = 'block'; el.style.whiteSpace = 'pre'; - el.style.fontSize = (parseFloat(parentStyle.fontSize) * metrics.ratio * 0.95) + 'px'; + el.style.fontSize = (parseFloat(computed.fontSize) * metrics.ratio * 0.95) + 'px'; el.style.padding = '1.2rem'; el.style.margin = '1.5rem 0'; el.style.borderLeft = `4px solid ${accent}`; el.style.color = 'inherit'; } else { el.style.display = 'inline'; - const exactPx = parseFloat(parentStyle.fontSize) * metrics.ratio * 1.0; + const exactPx = parseFloat(computed.fontSize) * metrics.ratio * 1.0; el.style.fontSize = exactPx + 'px'; el.style.padding = '0.1rem 0.35rem'; el.style.borderRadius = '3px'; + // Vertical align fix for inline code const offsetPx = metrics.baseline_diff * (exactPx / 100); el.style.verticalAlign = offsetPx + 'px'; el.style.color = text_color; diff --git a/developer/document/style/article.js b/developer/document/style/article.js new file mode 100644 index 0000000..36e07b6 --- /dev/null +++ b/developer/document/style/article.js @@ -0,0 +1,112 @@ +/* + Generic Article Typography. + Applies container settings and injects scoped CSS for standard HTML tags + (H1-H6, P, Blockquote) within the article. +*/ +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.article = function() { + RT.config = RT.config || {}; + + // Default Configuration + RT.config.article = { + font_family: '"Noto Sans JP", sans-serif' + ,line_height: "1.75" // Increased slightly for readability + ,font_size: "16px" + ,max_width: "800px" + ,margin: "0 auto" + }; + + const conf = RT.config.article; + const article_seq = document.querySelectorAll("RT-article"); + const theme = (RT.config.theme) ? RT.config.theme : { accent: 'gray', text: 'black' }; + + // HURDLE + if (RT.debug) RT.debug.log('selector', `RT.article found ${article_seq.length} elements.`); + if(article_seq.length === 0) { + if(RT.debug) RT.debug.error('selector', 'CRITICAL: No tags found.'); + return; + } + + // 1. Apply Container Styles + article_seq.forEach( (article) =>{ + const style = article.style; + style.display = "block"; + style.fontFamily = conf.font_family; + style.fontSize = conf.font_size; + style.lineHeight = conf.line_height; + style.maxWidth = conf.max_width; + style.margin = conf.margin; + style.backgroundColor = "transparent"; + + // Ensure the accent color is available as a variable here too + style.setProperty("--rt-accent", theme.accent); + }); + + // 2. Inject Child Typography (The missing piece) + const style_id = 'rt-article-typography'; + if (!document.getElementById(style_id)) { + const style_el = document.createElement('style'); + style_el.id = style_id; + + // We explicitly scope these to rt-article to avoid breaking the rest of the page + style_el.textContent = ` + rt-article h1 { + font-size: 2.2em; + font-weight: 700; + margin-top: 1.5em; + margin-bottom: 0.5em; + border-bottom: 2px solid var(--rt-accent); + padding-bottom: 0.2em; + line-height: 1.2; + } + + rt-article h2 { + font-size: 1.7em; + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + color: var(--rt-accent); + line-height: 1.3; + } + + rt-article h3 { font-size: 1.4em; margin-top: 1.2em; margin-bottom: 0.5em; } + + rt-article p { + margin-bottom: 1.2em; + text-align: justify; + hyphens: auto; + } + + rt-article blockquote { + border-left: 4px solid var(--rt-accent); + margin: 1.5em 0; + padding: 0.5em 0 0.5em 1.2em; + font-style: italic; + background: rgba(125,125,125, 0.05); + } + + rt-article ul, rt-article ol { + margin-bottom: 1.2em; + padding-left: 2em; + } + + rt-article li { + margin-bottom: 0.3em; + } + + /* Links inside articles */ + rt-article a { + color: var(--rt-accent); + text-decoration: none; + border-bottom: 1px dotted var(--rt-accent); + } + rt-article a:hover { + border-bottom: 1px solid var(--rt-accent); + } + `; + document.head.appendChild(style_el); + } + }; +})(); diff --git a/developer/document/style/article_generic.js b/developer/document/style/article_generic.js deleted file mode 100644 index 92a8418..0000000 --- a/developer/document/style/article_generic.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - Sets basic document theme colors and dimensions. - Groups parameters into semantic dictionaries (layout, typography). -*/ -window.StyleRT = window.StyleRT || {}; - -window.StyleRT.article_generic = function() { - const RT = window.StyleRT; - const theme = RT.active_theme ? RT.active_theme() : {}; - - // 1. Theme and Global Flow - const body = document.body; - body.style.backgroundColor = theme.background || 'hsl(0, 0%, 0%)'; - body.style.color = theme.foreground || 'hsl(42, 100%, 80%)'; - body.style.fontFamily = '"Noto Sans JP", sans-serif'; - body.style.display = 'block'; - body.style.margin = '0'; - body.style.padding = '0'; - - // 2. Settings: Layout Group - RT.layout = { - page_height: 1056, - page_width: '50rem', - page_margin: '4rem auto 6rem auto', - page_padding: '3rem' - }; - - // 3. Settings: Typography Group - RT.typography = { - orphans: 4, - widows: 4, - h1_align: 'center' - }; - - // 4. Apply Dimensions and Alignment - body.style.maxWidth = RT.layout.page_width; - body.style.margin = '0 auto'; - - const h1s = document.querySelectorAll('h1'); - h1s.forEach(h => { - h.style.textAlign = RT.typography.h1_align; - h.style.width = '100%'; - }); -}; diff --git a/developer/document/style/do_style.js b/developer/document/style/do_style.js deleted file mode 100644 index 72de517..0000000 --- a/developer/document/style/do_style.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - Master Loader & Orchestrator for StyleRT. - Loads utility.js first to ensure infrastructure (RT.debug) exists. -*/ - -window.StyleRT = window.StyleRT || {}; - -window.StyleRT.do_style = function() { - const RT = window.StyleRT; - - const modules = [ - 'style/theme_RT.js', - 'style/RT_term.js', - 'style/RT_math.js', - 'style/RT_code.js', - 'style/article_generic.js', - 'style/RT_TOC.js', - 'style/paginate.js', - 'style/paginate_by_element.js', - // 'style/page.js', - // 'style/page_css.js', - 'style/page_css_pn.js', - 'style/body_visibility_visible.js' - ]; - - // 1. Bootloader: Get the utility/logger in place first - const utility = document.createElement('script'); - utility.src = 'style/utility.js'; - - utility.onload = () => { - // Infrastructure ready; begin module sequence - load_next(0); - }; - - utility.onerror = () => { - console.error("StyleRT: Critical failure - utility.js missing."); - }; - - document.head.appendChild(utility); - - // 2. The Chain Loader - const load_next = (index) => { - if (index >= modules.length) { - run_style(); - return; - } - - const src = modules[index]; - - // Accessing the property live so it doesn't matter if it was set late - if (RT.debug) RT.debug.log('style', `Loading: ${src}`); - - const script = document.createElement('script'); - script.src = src; - script.onload = () => load_next(index + 1); - script.onerror = () => { - console.error(`StyleRT: Failed load on ${src}`); - load_next(index + 1); - }; - document.head.appendChild(script); - }; - - // 3. Phase 1: Semantics - const run_style = () => { - RT.debug.log('style', 'Starting Phase 1: Setup & Semantics'); - - if(RT.article_generic) RT.article_generic(); - if(RT.RT_term) RT.RT_term(); - if(RT.RT_math) RT.RT_math(); - if(RT.RT_code) RT.RT_code(); - - // Hand off to MathJax task queue - if (window.MathJax && MathJax.Hub && MathJax.Hub.Queue) { - RT.debug.log('style', 'MathJax detected. Queueing layout tasks...'); - MathJax.Hub.Queue(["Typeset", MathJax.Hub], continue_style); - } else { - continue_style(); - } - }; - - // 4. Phase 2: Layout - const continue_style = () => { - RT.debug.log('style', 'Starting Phase 2: Layout & Reveal'); - - if(RT.RT_TOC) RT.RT_TOC(); - if(RT.paginate_by_element) RT.paginate_by_element(); - if(RT.page) RT.page(); - if(RT.body_visibility_visible) RT.body_visibility_visible(); - - RT.debug.log('style', 'Style execution complete.'); - }; -}; diff --git a/developer/document/style/page.js b/developer/document/style/page.js deleted file mode 100644 index 91f6ff1..0000000 --- a/developer/document/style/page.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - Defines the appearance of the container via a CSS block. - Uses high-contrast offsets to ensure the drop shadow is visible. -*/ -window.StyleRT = window.StyleRT || {}; - -window.StyleRT.page = function() { - const RT = window.StyleRT; - const style_id = 'rt-page-styles'; - - if (!document.getElementById(style_id)) { - const style_el = document.createElement('style'); - style_el.id = style_id; - style_el.textContent = ` - html, body { - background-color: #1a1a1a !important; - margin: 0; - padding: 0; - } - - rt-page { - display: block; - max-width: 50rem; - width: 100%; - - /* Increased vertical margin to prevent shadow clipping */ - margin: 4rem auto 6rem auto; - padding: 3rem; - box-sizing: border-box; - background-color: hsl(0, 0%, 0%); - border: 1px solid hsl(42, 100%, 50%); - - /* 1. The Offset Drop Shadow (Bottom-Right) - 2. The Ambient Glow (Centered) - */ - box-shadow: 15px 15px 30px rgba(0, 0, 0, 0.9), - 0 0 15px hsl(42, 100%, 15%); - - height: auto; - - /* Ensure the shadow isn't cut off by the container's edges */ - overflow: visible; - } - `; - document.head.appendChild(style_el); - } - - if (RT.debug) RT.debug.log('style', 'CSS block updated with 15px shadow offset.'); -}; diff --git a/developer/document/style/page_css.js b/developer/document/style/page_css.js deleted file mode 100644 index 015fdfe..0000000 --- a/developer/document/style/page_css.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - Defines the appearance of the container. - Enforces a fixed height to create a uniform book-like appearance. -*/ -window.StyleRT = window.StyleRT || {}; - -window.StyleRT.page = function() { - const RT = window.StyleRT; - const style_id = 'rt-page-styles'; - - // Use the established page height or a standard 11-inch default - const fixed_height = RT.page_height ? `${RT.page_height}px` : '1056px'; - - if (!document.getElementById(style_id)) { - const style_el = document.createElement('style'); - style_el.id = style_id; - style_el.textContent = ` - html, body { - background-color: #1a1a1a !important; - margin: 0; - padding: 0; - } - - rt-page { - display: block; - max-width: 50rem; - width: 100%; - margin: 4rem auto 6rem auto; - padding: 3rem; - box-sizing: border-box; - background-color: hsl(0, 0%, 0%); - border: 1px solid hsl(42, 100%, 50%); - - /* Fixed Height Enforcement */ - height: ${fixed_height}; - overflow: visible; - - /* The Gold Leaf Glow */ - filter: drop-shadow(8px 12px 40px hsl(44, 96%, 47%)); - } - `; - document.head.appendChild(style_el); - } - - if (RT.debug) RT.debug.log('style', `Fixed height of ${fixed_height} applied to all pages.`); -}; diff --git a/developer/document/style/page_css_pn.js b/developer/document/style/page_css_pn.js index 3e5dc08..938da34 100644 --- a/developer/document/style/page_css_pn.js +++ b/developer/document/style/page_css_pn.js @@ -1,47 +1,61 @@ /* Defines the appearance of the container. - Uses grouped settings from RT.layout and RT.typography. */ window.StyleRT = window.StyleRT || {}; window.StyleRT.page = function() { const RT = window.StyleRT; - const style_id = 'rt-page-styles'; - // Safety fallbacks in case article_generic failed or hasn't run - const layout = RT.layout || { page_height: 1056, page_margin: '4rem auto' }; - const typo = RT.typography || { orphans: 4, widows: 4 }; + // Fallback accent + const theme_accent = (RT.config && RT.config.theme) ? RT.config.theme.accent : "hsl(42, 100%, 50%)"; + + RT.config = RT.config || {}; + + // Fix: Define defaults independently + const defaults = { + width: "100%" + ,height: "1056px" + ,padding: "3rem" + ,margin: "4rem auto" + ,border_color: theme_accent + ,shadow: "drop-shadow(8px 12px 40px hsl(44, 96%, 47%))" + }; + + // Fix: MERGE defaults into existing config. + // This allows overrides but prevents empty objects from causing "undefined" CSS. + RT.config.page = Object.assign({}, defaults, RT.config.page || {}); + const conf = RT.config.page; + const style_id = 'rt-page-styles'; + if (!document.getElementById(style_id)) { const style_el = document.createElement('style'); style_el.id = style_id; style_el.textContent = ` - body { + rt-article { counter-reset: rt-page-counter; } rt-page { display: block; - max-width: ${layout.page_width || '50rem'}; - width: 100%; - margin: ${layout.page_margin}; - padding: ${layout.page_padding || '3rem'}; + width: ${conf.width}; + height: ${conf.height}; + margin: ${conf.margin}; + padding: ${conf.padding}; box-sizing: border-box; - background-color: hsl(0, 0%, 0%); - border: 1px solid hsl(42, 100%, 50%); - height: ${layout.page_height}px; + + background-color: black; + + /* These should now be populated correctly */ + border: 1px solid ${conf.border_color}; position: relative; - filter: drop-shadow(8px 12px 40px hsl(44, 96%, 47%)); + filter: ${conf.shadow}; + counter-increment: rt-page-counter; + overflow: hidden; } - rt-page p { - orphans: ${typo.orphans}; - widows: ${typo.widows}; - } - - /* Brightened Page Counter */ rt-page::after { content: "Page " counter(rt-page-counter); position: absolute; @@ -50,7 +64,7 @@ window.StyleRT.page = function() { font-family: "Noto Sans JP", sans-serif; font-size: 0.9rem; font-weight: bold; - color: hsl(42, 100%, 75%); /* Much brighter, high-contrast gold */ + color: ${conf.border_color}; } `; document.head.appendChild(style_el); diff --git a/developer/document/style/paginate_by_element.js b/developer/document/style/paginate_by_element.js index 997383b..e085148 100644 --- a/developer/document/style/paginate_by_element.js +++ b/developer/document/style/paginate_by_element.js @@ -1,63 +1,72 @@ /* Layout Paginator: paginate_by_element - Measures heights and groups elements. - Logic: Headings must stay with the element immediately following them. */ window.StyleRT = window.StyleRT || {}; window.StyleRT.paginate_by_element = function() { const RT = window.StyleRT; - const body = document.body; - const limit = RT.layout.page_height || 1000; - - const elements = Array.from(body.children).filter(el => - el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE' && el.tagName !== 'RT-PAGE' - ); - - const pages = []; - let current_batch = []; - let current_h = 0; - - const get_el_height = (el) => { - const rect = el.getBoundingClientRect(); - const style = window.getComputedStyle(el); - const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom); - return rect.height + margin; - }; - - for (let i = 0; i < elements.length; i++) { - const el = elements[i]; - const h = get_el_height(el); - const is_heading = /H[1-6]/.test(el.tagName); - - // Lookahead: If this is a heading, check the next element too - let total_required_h = h; - if (is_heading && i + 1 < elements.length) { - total_required_h += get_el_height(elements[i + 1]); - } + + // Fix: Read safely without overwriting the config namespace + const page_conf = (RT.config && RT.config.page) ? RT.config.page : {}; + const page_height_limit = page_conf.height_limit || 1000; + + const article_seq = document.querySelectorAll("RT-article"); + + // HURDLE: Error if no articles found to paginate + if(article_seq.length === 0) { + RT.debug.error('pagination', 'No elements found. Pagination aborted.'); + return; + } + + article_seq.forEach( (article) => { + const raw_elements = Array.from(article.children).filter(el => + !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName) + ); + + if(raw_elements.length === 0) return; + + const pages = []; + let current_batch = []; + let current_h = 0; - // If the element (or heading + next) exceeds limit, start new page - if (current_h + total_required_h > limit && current_batch.length > 0) { - pages.push(current_batch); - current_batch = []; - current_h = 0; + const get_el_height = (el) => { + const rect = el.getBoundingClientRect(); + const style = window.getComputedStyle(el); + const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom); + return (rect.height || 0) + (margin || 0); + }; + + for (let i = 0; i < raw_elements.length; i++) { + const el = raw_elements[i]; + const h = get_el_height(el); + const is_heading = /^H[1-6]/.test(el.tagName); + + let total_required_h = h; + if (is_heading && i + 1 < raw_elements.length) { + total_required_h += get_el_height(raw_elements[i + 1]); + } + + if (current_h + total_required_h > page_height_limit && current_batch.length > 0) { + pages.push(current_batch); + current_batch = []; + current_h = 0; + } + + current_batch.push(el); + current_h += h; } - current_batch.push(el); - current_h += h; - } + if (current_batch.length > 0) pages.push(current_batch); - if (current_batch.length > 0) pages.push(current_batch); + article.innerHTML = ''; + + pages.forEach( (list, index) => { + const page_el = document.createElement('rt-page'); + page_el.id = `page-${index+1}`; + list.forEach(item => page_el.appendChild(item)); + article.appendChild(page_el); + }); - // Rebuild DOM - const assets = Array.from(document.querySelectorAll('script, style')); - body.innerHTML = ''; - pages.forEach(list => { - const page_el = document.createElement('rt-page'); - list.forEach(item => page_el.appendChild(item)); - body.appendChild(page_el); + if (RT.debug) RT.debug.log('pagination', `Article paginated into ${pages.length} pages.`); }); - assets.forEach(a => body.appendChild(a)); - - if (RT.debug) RT.debug.log('layout', `Pagination complete: ${pages.length} pages.`); }; diff --git a/developer/document/style/style_orchestrator.js b/developer/document/style/style_orchestrator.js new file mode 100644 index 0000000..5239707 --- /dev/null +++ b/developer/document/style/style_orchestrator.js @@ -0,0 +1,88 @@ +/* + Master Loader & Orchestrator for StyleRT. + Renamed from do_style.js +*/ + +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.style_orchestrator = function() { + const RT = window.StyleRT; + + const modules = [ + // Theme & Semantics + 'style/theme.js', + 'style/RT_term.js', + 'style/RT_math.js', + 'style/RT_code.js', + 'style/article.js', // Renamed from article_generic + 'style/RT_TOC.js', + + // Layout & Pagination + 'style/paginate_by_element.js', + 'style/page_css_pn.js', + + // Visibility + 'style/body_visibility_visible.js' + ]; + + // 1. Bootloader + const utility = document.createElement('script'); + utility.src = 'style/utility.js'; + + utility.onload = () => { load_next(0); }; + utility.onerror = () => { console.error("StyleRT: Critical failure - utility.js missing."); }; + document.head.appendChild(utility); + + // 2. The Chain Loader + const load_next = (index) => { + if (index >= modules.length) { + run_style(); + return; + } + const src = modules[index]; + if (RT.debug) RT.debug.log('style', `Loading: ${src}`); + + const script = document.createElement('script'); + script.src = src; + script.onload = () => load_next(index + 1); + script.onerror = () => { + console.error(`StyleRT: Failed load on ${src}`); + load_next(index + 1); + }; + document.head.appendChild(script); + }; + + // 3. Phase 1: Semantics + const run_style = () => { + RT.debug.log('style', 'Starting Phase 1: Setup & Semantics'); + + // Naming Convention: RT. + if(RT.theme) RT.theme(); // Was theme + if(RT.article) RT.article(); // Was article_generic + if(RT.RT_term) RT.RT_term(); + if(RT.RT_math) RT.RT_math(); + if(RT.RT_code) RT.RT_code(); + + if (window.MathJax && MathJax.Hub && MathJax.Hub.Queue) { + RT.debug.log('style', 'MathJax detected. Queueing layout tasks...'); + MathJax.Hub.Queue(["Typeset", MathJax.Hub], continue_style); + } else { + continue_style(); + } + }; + + // 4. Phase 2: Layout + const continue_style = () => { + RT.debug.log('style', 'Starting Phase 2: Layout & Reveal'); + + // Debug: Dump the config to see what values we are using + if(RT.debug) RT.debug.log('config', JSON.stringify(RT.config || {}, null, 2)); + + if(RT.RT_TOC) RT.RT_TOC(); + if(RT.paginate_by_element) RT.paginate_by_element(); + if(RT.page) RT.page(); // Defined in page_css_pn.js + if(RT.body_visibility_visible) RT.body_visibility_visible(); + + RT.debug.log('style', 'Style execution complete.'); + }; +}; diff --git a/developer/document/style/test_0.c b/developer/document/style/test_0.c new file mode 100644 index 0000000..1c7cadc --- /dev/null +++ b/developer/document/style/test_0.c @@ -0,0 +1,8 @@ +// test_0_in.c +void func(int a,int b){ + if(check(a,b)){ + for(int i=0; i<10; i++){ + if(i==0) return; + } + } +} diff --git a/developer/document/style/theme.js b/developer/document/style/theme.js new file mode 100644 index 0000000..1302951 --- /dev/null +++ b/developer/document/style/theme.js @@ -0,0 +1,46 @@ +/* + Global Theme Definition. + Applies colors and variables to the document Root and Body. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme = function(){ + RT.config = RT.config || {}; + + // Define the Palette + RT.config.theme = { + background: "#1a1a1a" + ,text: "#f0f0f0" + ,accent: "#ffcc00" + ,code_bg: "#2d2d2d" + }; + + const palette = RT.config.theme; + const html = document.documentElement; + const body = document.body; + + if(RT.debug) RT.debug.log('style' ,'Applying Global Theme to Body/HTML.'); + + // 1. Reset the "White Border" (Browser Defaults) + // Browsers often add an 8px margin to body. We must zero this out. + html.style.margin = "0"; + html.style.padding = "0"; + body.style.margin = "0"; + body.style.padding = "0"; + + // Ensure the background covers the whole viewport, even if content is short + body.style.minHeight = "100vh"; + + // 2. Apply Palette + // We paint both HTML and BODY to ensure over-scroll areas match. + html.style.backgroundColor = palette.background; + body.style.backgroundColor = palette.background; + body.style.color = palette.text; + + // 3. Set Global CSS Variables + // These will now be available anywhere in the document. + body.style.setProperty("--rt-accent" ,palette.accent); + body.style.setProperty("--rt-code-bg" ,palette.code_bg); + }; +} )(); diff --git a/developer/document/style/theme_RT.js b/developer/document/style/theme_RT.js deleted file mode 100644 index 63fcec5..0000000 --- a/developer/document/style/theme_RT.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - Provides the color palette and layout layout for the RT theme. - Registers itself as the active system theme upon load. -*/ -function theme_RT(){ - return { - name: 'theme_RT', // Identity - - // Palette - background: 'hsl(0, 0%, 0%)', - foreground: 'hsl(42, 100%, 80%)', - accent: 'hsl(42, 100%, 50%)', - faded: 'hsl(42, 100%, 20%)', - highlight: 'hsl(42, 100%, 90%)', - }; -} - -window.StyleRT = window.StyleRT || {}; -window.StyleRT.theme_RT = theme_RT; - -// Generic Interface: Set this as the current active theme -window.StyleRT.active_theme = theme_RT; diff --git a/developer/document/style/utility.js b/developer/document/style/utility.js index 337bdcc..ae64e97 100644 --- a/developer/document/style/utility.js +++ b/developer/document/style/utility.js @@ -1,17 +1,16 @@ /* General utilities for the StyleRT library. - Includes: - 1. Token-based Debugging System - 2. Physics (Ink metrics, Color analysis) - 3. Text Analysis */ window.StyleRT = window.StyleRT || {}; // --- DEBUG SYSTEM --- window.StyleRT.debug = { - // Add tokens here to enable specific logs: 'RT_code', 'layout', 'style', 'layout' - active_tokens: new Set(['style' ,'layout' ,'pagination']), + // ENABLE 'selector', 'config', and 'error' so we aren't flying blind! + active_tokens: new Set([ + 'style', 'layout', 'pagination', + 'selector', 'config', 'error' + ]), log: function(token, message) { if (this.active_tokens.has(token)) { @@ -25,14 +24,17 @@ window.StyleRT.debug = { } }, - // Helper to enable/disable on the fly from console + // New: Always log errors regardless of token, but tag them + error: function(token, message) { + console.error(`[StyleRT:${token}] CRITICAL:`, message); + }, + enable: function(token) { this.active_tokens.add(token); console.log(`Enabled: ${token}`); }, disable: function(token) { this.active_tokens.delete(token); console.log(`Disabled: ${token}`); } }; // --- UTILITIES --- window.StyleRT.utility = { - // --- FONT PHYSICS --- measure_ink_ratio: function(target_font, ref_font = null) { const debug = window.StyleRT.debug; @@ -59,7 +61,7 @@ window.StyleRT.utility = { const target_m = get_metrics(target_font); const ratio = ref_m.ascent / target_m.ascent; - debug.log('layout', `Ink Ratio calculated: ${ratio.toFixed(3)}`); + // debug.log('layout', `Ink Ratio calculated: ${ratio.toFixed(3)}`); return { ratio: ratio, @@ -76,16 +78,14 @@ window.StyleRT.utility = { const numbers = color_string.match(/\d+/g); if (numbers && numbers.length >= 3) { const lightness = parseInt(numbers[2]); - const is_light = lightness > 50; - debug.log('color_layout', `HSL ${color_string} -> Lightness ${lightness}% -> ${is_light ? 'LIGHT' : 'DARK'}`); - return is_light; + return lightness > 50; } } // 2. RGB Check const rgb = color_string.match(/\d+/g); if (!rgb) { - debug.warn('color_layout', `Failed to parse color: "${color_string}". Defaulting to Light.`); + // debug.warn('color_layout', `Failed to parse color: "${color_string}". Defaulting to Light.`); return true; } @@ -93,13 +93,9 @@ window.StyleRT.utility = { const g = parseInt(rgb[1]); const b = parseInt(rgb[2]); const luma = (r * 299 + g * 587 + b * 114) / 1000; - const is_light = luma > 128; - - debug.log('color_layout', `RGB (${r},${g},${b}) -> Luma ${luma.toFixed(1)} -> ${is_light ? 'LIGHT' : 'DARK'}`); - return is_light; + return luma > 128; }, - // --- TEXT ANALYSIS --- is_block_content: function(element) { return element.textContent.trim().includes('\n'); } diff --git a/developer/document/test.html b/developer/document/test.html index d25effb..cb87322 100644 --- a/developer/document/test.html +++ b/developer/document/test.html @@ -88,9 +88,9 @@ x = y return hypergraph.lookup(way) - + diff --git a/developer/document/theory.html b/developer/document/theory.html deleted file mode 100644 index d2a2c54..0000000 --- a/developer/document/theory.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - Epimetheus: Tagged Math Paradigm - - - - - - - -

Given

- - The English language is a given, apparently. - - This discussion happens to appear in the context of a Python language implementation, though it applies equally as well to other languages. - -

Symbol

- -

Definition

- - We have a symbol instance factory. Each time the `make` method on the factory is called, the factory returns an instance of a distinct symbol. - - A symbol instance may be copied as through it were an integer. Thus, a common method for making a copy is assignment, e.g. 'x = y', where y is a variable holding a symbol instance, and after execution of the assignment, x will hold another instance of the symbol. - - Any two, or by transitivity, any number of, symbol instances can be compared for equality using an integer equality comparison, e.g. 'x == y'. A comparison between symbol holding variables will always return `True` or `False`. - - It follows that any two symbol instances directly returned from the factory will always equality compare to be `False`. - - An equivalence set of symbol instances is a functional representation of the symbol. The name of this set is yet another instance of said symbol. Thus ever instance of the symbol represents the symbol. - - Though symbol instances are integer like for copy and equality compare, they are disallowed from being used with other integer operators. As examples, symbols can not be compared for greater-than or less-than, they can not be incremented, nor added, nor subtracted, etc. - - The only allowed operators for symbol instances are integer equality compare, integer not-equal compare, and integer copy. - -

Distinctness Across Contexts

- - If a symbol persist across contexts, such as across scopes, or across processes, it must remain distinct from all other symbols as it enters into those new processes or contexts. - - There are many possible schemes or constraint sets for meeting this requirement. - - In this implementation we meet this constraint by using a global symbol factory, and by explicitly disallowing a symbol from being used outside the process it was made in. - -

Named Symbols

- - It is common for a programmer to make a dictionary that has one-to-one association of a symbol and a Python string object, i.e. a `string`, as programmers like to be able to refer to each symbol by its 'name'. - -

Object

- -

Definition

- - The term 'Python object' is anything that can appear in Python code, or could appear in Python code, and could be associated with a symbol. - - A symbol is associated with an object using a Python dictionary. The symbol is the key, and the object is that thing which is 'looked up' via that key. - -

Path

- -

Individual Path

- - In the TTCA we provided formal definition for a 'tape'. A tape has a leftmost cell, one or more in between cells, and a rightmost cell. For a one cell tape, the one cell is both the leftmost cell and the rightmost cell. A zero cell tape either does not exist, or it is a higher order 'placeholder' concept. - - A 'path' is special case of a 'tape', as defined in the TTCA, with some additional constraints. - - Each cell of a path holds a symbol. - - The leftmost cell is called the 'destination'. The reader might have expected the path to go from left to right on the tape, in the same order that a person reads the words on a printed page, but such a definition would run into a problem, as left to right traversal of a tape might not ever end. Think about it this way, you are sitting in a pub of an inn, and a stranger walks in. Though you know the stranger's current destination, you might now know where he came from. - - All the cells of the path to the right of the destination cell, are called 'the way'. If we were to drop the leftmost cell from the tape, then we would find 'the way' if it exists, to be another tape. - -

Discrete Function

- - Consider another tape where each cell holds a path. - - If when we compare all the paths on said tape, and find that no two identical ways lead to different destinations, we can call said tape a 'discrete function'. It is 'discrete' because of the type of math we are doing here, with symbols and tape cells. It is a 'function' because a given way never has two destinations. - - Topologically it gives us a satisfying feeling that a given 'way' never leads to different destinations. If it were otherwise we would feel to be in a sci-fi movie. - - Note, however, there can exist more than one way to reach the same destination, though there might not be. Again, this fits our intuition of real paths. - - Because functions embody our intuition about the space we live in, they occur often when doing real world modeling. There is also something about this quality that makes discrete functions useful in computer science. - - A common notation for a function is: - ``` - destination = f(way) - ``` - - This notation is possible because the same way always leads to the given destination. - - - - - - - - - - - - - - - - - - - diff --git a/shared/authored/doc.txt b/shared/authored/doc.txt new file mode 100644 index 0000000..fc0ea23 --- /dev/null +++ b/shared/authored/doc.txt @@ -0,0 +1,7 @@ +The "General Observations" ApproachInstead of strict rule compliance, we can code these three Typesetting Heuristics: + +The "Hollow Shell" (Multi-Level)If a pair of parentheses contains structure (other parens/braces), it is a "Hollow Shell." It should have air inside.if(check()) $\rightarrow$ if( check() )((a)) $\rightarrow$ ( (a) ) + +The "Shrink Wrap" (Single-Level)If a pair of parentheses contains only "atoms" (text, numbers, commas) and no structure, it should "shrink wrap" the content tightly.func( a, b ) $\rightarrow$ func(a ,b)( 1 + 2 ) $\rightarrow$ (1 + 2) + +The "Stack Separation" (Widows/Orphans)When we see a pile-up of closing delimiters at the start of a line (widows) or opening delimiters (orphans), we assume they represent different logical layers collapsing. We visually separate the "outer" layer from the "inner" pile.}) $\rightarrow$ } ) (Step out of the block, then close the parent).)) $\rightarrow$ ) ) (Close the child, then close the parent). diff --git a/shared/authored/rt_fmt.py b/shared/authored/rt_fmt.py new file mode 100755 index 0000000..124eeb0 --- /dev/null +++ b/shared/authored/rt_fmt.py @@ -0,0 +1,925 @@ +#!/usr/bin/env python3 +import sys +import re + +# --- RT Code Formatting Rules --- +# [Rules 1-6 unchanged] +# 7. Vertical Lists: +# - Trailing commas (end of line) are migrated to the start of the next line. +# - Vertical commas get a space before them (`,item`). + +# --- Globals --- +RT_DEBUG = True +MAX_LOOPS = 100 + +# --- Data Structures --- +class Token: + def __init__(self, type, value): + self.type = type + self.value = value + + def __repr__(self): + return f"<{self.type}:'{repr(self.value)}'>" + +# Types +T_NEWLINE = 'NEWLINE' +T_INDENT = 'INDENT' +T_BLANK = 'BLANK' +T_OPEN = 'OPEN' # ( { [ +T_CLOSE = 'CLOSE' # ) } ] +T_COMMA = 'COMMA' # , +T_STUFF = 'STUFF' # Code + +# --- Tokenizer --- +def tokenize(text): + tokens = [] + placeholders = {} + def mask_match(match): + key = f"__RT_MASK_{len(placeholders)}__" + placeholders[key] = match.group(0) + return key + + masked_text = re.sub(r'("|l\').*?(\1)|//.*|#.*', mask_match, text, flags=re.MULTILINE) + + i = 0 + length = len(masked_text) + at_line_start = True + + while i < length: + char = masked_text[i] + + if char == '\n': + tokens.append(Token(T_NEWLINE, char)) + at_line_start = True + i += 1 + continue + + if char.isspace(): + start = i + while i < length and masked_text[i].isspace() and masked_text[i] != '\n': + i += 1 + val = masked_text[start:i] + if at_line_start: + tokens.append(Token(T_INDENT, val)) + else: + tokens.append(Token(T_BLANK, val)) + continue + + at_line_start = False + + if char in '({[': + tokens.append(Token(T_OPEN, char)) + i += 1 + elif char in ')}]': + tokens.append(Token(T_CLOSE, char)) + i += 1 + elif char == ',': + tokens.append(Token(T_COMMA, char)) + i += 1 + else: + start = i + while i < length: + c = masked_text[i] + if c.isspace() or c in '({[]}),\n': + break + i += 1 + val = masked_text[start:i] + for k,v in placeholders.items(): + if k in val: val = val.replace(k,v) + tokens.append(Token(T_STUFF, val)) + + return tokens + +def detokenize(tokens): + return "".join(t.value for t in tokens) + +# --- Transformers (Actions) --- +def fmt_tok(t): + return f"{t.type}('{t.value}')" + +def log_action(name, idx, tokens): + if RT_DEBUG: + prev_s = fmt_tok(tokens[idx-1]) if idx > 0 else "START" + curr_s = fmt_tok(tokens[idx]) + next_s = fmt_tok(tokens[idx+1]) if idx+1 < len(tokens) else "END" + print(f"[ACTION] {name} at {idx}: {prev_s} << {curr_s} >> {next_s}") + +def action_remove_next(tokens, idx): + if idx + 1 < len(tokens): + log_action("REMOVE NEXT", idx, tokens) + del tokens[idx+1] + return True + return False + +def action_remove_prev(tokens, idx): + if idx > 0: + log_action("REMOVE PREV", idx, tokens) + del tokens[idx-1] + return True + return False + +def action_insert_space_next(tokens, idx): + log_action("INSERT SPACE NEXT", idx, tokens) + tokens.insert(idx + 1, Token(T_BLANK, " ")) + return True + +def action_insert_space_prev(tokens, idx): + log_action("INSERT SPACE PREV", idx, tokens) + tokens.insert(idx, Token(T_BLANK, " ")) + return True + +def action_fix_excess_space_next(tokens, idx): + if idx + 1 < len(tokens) and tokens[idx+1].type == T_BLANK: + if tokens[idx+1].value != " ": + log_action("FIX EXCESS SPACE NEXT", idx, tokens) + tokens[idx+1].value = " " + return True + return False + +def action_fix_excess_space_prev(tokens, idx): + if idx > 0 and tokens[idx-1].type == T_BLANK: + if tokens[idx-1].value != " ": + log_action("FIX EXCESS SPACE PREV", idx, tokens) + tokens[idx-1].value = " " + return True + return False + +# --- Recognizers (Predicates) --- +def is_open(t): return t.type == T_OPEN +def is_close(t): return t.type == T_CLOSE +def is_blank(t): return t.type == T_BLANK +def is_stuff(t): return t.type == T_STUFF +def is_comma(t): return t.type == T_COMMA + +def log_pred(name, idx, result, reason=""): + if RT_DEBUG: + r_str = "YES" if result else "NO " + print(f"[PRED] {name}({idx}) -> {r_str} ({reason})") + +def analyze_line_structure(tokens, idx): + line_start = idx + while line_start > 0 and tokens[line_start].type != T_NEWLINE: + line_start -= 1 + if tokens[line_start].type == T_NEWLINE: line_start += 1 + + line_end = idx + while line_end < len(tokens) and tokens[line_end].type != T_NEWLINE: + line_end += 1 + + stack = [] + pairs = {} + orphans = [] + + for i in range(line_start, line_end): + t = tokens[i] + if is_open(t): + stack.append(i) + elif is_close(t): + if stack: + s = stack.pop() + pairs[s] = i + else: + orphans.append(i) + widows = stack + return pairs, widows, orphans, line_start, line_end + +def is_perimeter_start(tokens, idx): + if not is_open(tokens[idx]): return False + pairs, widows, orphans, l_start, l_end = analyze_line_structure(tokens, idx) + if idx in pairs: + closer_idx = pairs[idx] + for s, e in pairs.items(): + if s < idx and e > closer_idx: return False + has_structure = False + for k in range(idx + 1, closer_idx): + if tokens[k].type in (T_OPEN, T_CLOSE): + has_structure = True + break + return has_structure + elif idx in widows: + if widows[0] != idx: return False + has_structure = False + for k in range(idx + 1, l_end): + if tokens[k].type in (T_OPEN, T_CLOSE): + has_structure = True + break + if has_structure: + log_pred("is_perimeter_start", idx, True, "First Widow with Structure") + return True + return False + +def is_perimeter_end(tokens, idx): + if not is_close(tokens[idx]): return False + pairs, widows, orphans, l_start, l_end = analyze_line_structure(tokens, idx) + reverse_pairs = {v: k for k, v in pairs.items()} + if idx in reverse_pairs: + opener_idx = reverse_pairs[idx] + for s, e in pairs.items(): + if s < opener_idx and e > idx: return False + has_structure = False + for k in range(opener_idx + 1, idx): + if tokens[k].type in (T_OPEN, T_CLOSE): + has_structure = True + break + return has_structure + elif idx in orphans: + if orphans[-1] != idx: return False + return True + return False + +def has_exact_following_space(tokens, idx): + if idx + 1 < len(tokens) and is_blank(tokens[idx+1]): + return tokens[idx+1].value == " " + return False + +def has_exact_preceding_space(tokens, idx): + if idx > 0 and is_blank(tokens[idx-1]): + return tokens[idx-1].value == " " + return False + +def has_any_following_space(tokens, idx): + return idx + 1 < len(tokens) and is_blank(tokens[idx+1]) + +def has_any_preceding_space(tokens, idx): + return idx > 0 and is_blank(tokens[idx-1]) + +def is_adjacent_closer(tokens, idx): + if not is_close(tokens[idx]): return False + if idx > 0 and is_close(tokens[idx-1]): return True + return False + +def is_brace_after_paren(tokens, idx): + if tokens[idx].value != '{': return False + prev_idx = idx - 1 + if prev_idx < 0: return False + if is_blank(tokens[prev_idx]): prev_idx -= 1 + if prev_idx >= 0 and tokens[prev_idx].value == ')': return True + return False + +def is_identifier(token): + if token.type != T_STUFF or not token.value: return False + c = token.value[0] + return c.isalpha() or c == '_' + +# --- Enclosure Fixers --- +def fix_perimeter_open(tokens, idx): + if is_perimeter_start(tokens, idx): + if not has_any_following_space(tokens, idx): + return action_insert_space_next(tokens, idx) + elif not has_exact_following_space(tokens, idx): + return action_fix_excess_space_next(tokens, idx) + return False + +def fix_perimeter_close(tokens, idx): + if is_perimeter_end(tokens, idx): + if not has_any_preceding_space(tokens, idx): + return action_insert_space_prev(tokens, idx) + elif not has_exact_preceding_space(tokens, idx): + return action_fix_excess_space_prev(tokens, idx) + return False + +def fix_interior_open(tokens, idx): + if is_open(tokens[idx]) and not is_perimeter_start(tokens, idx): + if has_any_following_space(tokens, idx): + return action_remove_next(tokens, idx) + return False + +def fix_interior_close(tokens, idx): + if is_close(tokens[idx]) and not is_perimeter_end(tokens, idx): + if has_any_preceding_space(tokens, idx): + prev_idx = idx - 1 + if prev_idx >= 0 and tokens[prev_idx].type == T_INDENT: return False + if idx > 1 and is_close(tokens[idx-2]): return False + return action_remove_prev(tokens, idx) + return False + +def fix_bookend_stack(tokens, idx): + if is_adjacent_closer(tokens, idx): + if not has_any_preceding_space(tokens, idx): + return action_insert_space_prev(tokens, idx) + return False + +def fix_brace_after_paren(tokens, idx): + if is_brace_after_paren(tokens, idx): + if has_any_preceding_space(tokens, idx): + return action_remove_prev(tokens, idx) + return False + +def fix_close_paren_followed_by_alpha(tokens, idx): + if is_close(tokens[idx]) and idx+1 < len(tokens): + nxt = tokens[idx+1] + if is_identifier(nxt): + if not has_any_following_space(tokens, idx): + return action_insert_space_next(tokens, idx) + return False + +# --- Phase 1: Enclosure Loop --- +def format_enclosure_structure(tokens): + fixers = [ + fix_bookend_stack, + fix_brace_after_paren, + fix_perimeter_open, + fix_perimeter_close, + fix_interior_open, + fix_interior_close, + fix_close_paren_followed_by_alpha + ] + i = 0 + while i < len(tokens): + stable = False + loops = 0 + while not stable: + if loops > MAX_LOOPS: break + loops += 1 + action_taken = False + for func in fixers: + if func(tokens, i): + action_taken = True + break + if not action_taken: stable = True + i += 1 + return tokens + +# --- Phase 2: Trailing Comma Migration --- +def pass_migrate_trailing_commas(tokens): + """ + Moves trailing commas to the start of the next line. + Rule: ITEM, [BLANK] NEWLINE [INDENT] [BLANK] ITEM -> ITEM NEWLINE [INDENT] [BLANK] , ITEM + Also cleans up blank lines created by removing the comma. + """ + i = 0 + while i < len(tokens): + t = tokens[i] + + if t.type == T_COMMA: + # 1. Check Context: Is this a trailing comma? + # Look ahead for NEWLINE (skipping blanks) + j = i + 1 + while j < len(tokens) and tokens[j].type == T_BLANK: + j += 1 + + if j < len(tokens) and tokens[j].type == T_NEWLINE: + if RT_DEBUG: print(f"[MIGRATE] Found trailing comma at {i}") + + # --- Step A: Remove Comma from Current Line --- + # Remove range [i, j) (The comma and trailing spaces) + del tokens[i:j] + + # Note: tokens[i] is now the NEWLINE. + current_newline_idx = i + + # --- Step B: Cleanup Empty Line --- + # Check if the line we just stripped is now empty (only whitespace/indent). + # Scan backwards from NEWLINE to find start of line. + start_of_line_idx = current_newline_idx + while start_of_line_idx > 0 and tokens[start_of_line_idx-1].type != T_NEWLINE: + start_of_line_idx -= 1 + + # Check contents + is_empty_line = True + for k in range(start_of_line_idx, current_newline_idx): + if tokens[k].type not in (T_INDENT, T_BLANK): + is_empty_line = False + break + + if is_empty_line and start_of_line_idx < current_newline_idx: + if RT_DEBUG: print("[MIGRATE] Collapsing empty line") + # Delete the line content + the NEWLINE at the end. + # Range: [start_of_line_idx, current_newline_idx + 1] + del tokens[start_of_line_idx : current_newline_idx + 1] + + # We need to find the *new* current_newline_idx because indices shifted. + # We are now sitting at 'start_of_line_idx', which is effectively the start of the next line. + # But we need to insert the comma AFTER the newline of the *previous* line. + # Wait, if we collapsed the line, we effectively merged the previous item up? + # No, typically migration happens in a list: + # Item A, \n Item B + # Remove comma -> Item A \n Item B. + # If Item A line was empty? " , \n" -> Remove comma -> " \n". + # Then remove line -> Item B moves up. + # The comma should go before Item B. + + # Correct target: The insertion point is at `start_of_line_idx` (which is now the start of next line). + # We want to insert ", " there? + # If we collapsed, we might have lost the newline separator entirely? + # Let's assume standard trailing comma case first: + # "Item A, \n" -> "Item A \n , Item B" + pass + + # --- Step C: Locate Insertion Point on Next Line --- + # We need to find the NEWLINE token to insert AFTER. + # If we collapsed the line, 'i' might point to start of next line (INDENT/STUFF). + # If we didn't collapse, 'tokens[i]' IS the NEWLINE. + + # Let's recover the insertion point relative to 'i'. + # We want to scan past NEWLINE, INDENT, BLANK on the *current* stream state. + + # If we didn't collapse, tokens[i] is NEWLINE. + # If we collapsed, we deleted the newline. We need to find where to put the comma. + # If we collapsed " , \n", the comma belongs to the item *after* the deleted line. + # So we insert at 'i'. + + insert_pos = i + + if not is_empty_line: + # We are at NEWLINE. Skip it. + if insert_pos < len(tokens) and tokens[insert_pos].type == T_NEWLINE: + insert_pos += 1 + + # Skip Indent and Blank on the destination line + while insert_pos < len(tokens) and tokens[insert_pos].type in (T_INDENT, T_BLANK): + insert_pos += 1 + + # --- Step D: Insert Comma --- + if RT_DEBUG: print(f"[MIGRATE] Inserting comma at {insert_pos}") + + # Insert Space + Comma (Vertical comma style: " ,Item") + # Wait, "Vertical commas get a space before them". + # So we insert " " then ",". + tokens.insert(insert_pos, Token(T_COMMA, ",")) + tokens.insert(insert_pos, Token(T_BLANK, " ")) + + # Continue scan from after insertion + i = insert_pos + 2 + continue + + i += 1 + return tokens + +# --- Phase 3: Horizontal Comma Pass --- +def format_horizontal_commas(tokens): + i = 0 + while i < len(tokens): + t = tokens[i] + if t.type == T_COMMA: + is_vertical = False + # Check if prev is NEWLINE or INDENT (implies vertical) + # OR if prev is BLANK and prev-prev is NEWLINE/INDENT? + # Migration pass ensures " \n [INDENT] [BLANK] ," + # So looking back 1 or 2 tokens is sufficient. + + lookback = i - 1 + while lookback >= 0 and tokens[lookback].type == T_BLANK: + lookback -= 1 + + if lookback >= 0 and tokens[lookback].type in (T_NEWLINE, T_INDENT): + is_vertical = True + + if not is_vertical: + if not has_any_preceding_space(tokens, i): + action_insert_space_prev(tokens, i) + i += 1 + if has_any_following_space(tokens, i): + action_remove_next(tokens, i) + i += 1 + return tokens + +# --- Driver --- +def process_file(in_fp, out_fp): + with open(in_fp, 'r') as f: + content = f.read() + + tokens = tokenize(content) + + if RT_DEBUG: + print("--- DEBUG: Token Stream (Initial) ---") + for idx, t in enumerate(tokens): print(f"{idx}: {t}") + + # Phase 1: Walls + tokens = format_enclosure_structure(tokens) + + # Phase 2: Migration + tokens = pass_migrate_trailing_commas(tokens) + + # Phase 3: Furniture + tokens = format_horizontal_commas(tokens) + + with open(out_fp, 'w') as f: + f.write(detokenize(tokens)) + +def CLI(): + global RT_DEBUG + args = sys.argv[1:] + if "-d" in args: + RT_DEBUG = True + args.remove("-d") + if len(args) < 1: + print("Usage: rt_fmt [-d] [out_file]") + sys.exit(1) + in_fp = args[0] + out_fp = args[1] if len(args) > 1 else in_fp + process_file(in_fp, out_fp) + if RT_DEBUG: print(f"Formatted: {in_fp} -> {out_fp}") + +if __name__ == "__main__": CLI() + + + + +#!/usr/bin/env python3 +import sys +import re + +# --- RT Code Formatting Rules --- +# [Rules 1-6 unchanged...] + +# --- Globals --- +RT_DEBUG = False + +# --- Data Structures --- +class Token: + def __init__(self, type, value): + self.type = type + self.value = value + + def __repr__(self): + return f"<{self.type}:'{repr(self.value)}'>" + +# Types +T_NEWLINE = 'NEWLINE' +T_INDENT = 'INDENT' +T_BLANK = 'BLANK' +T_OPEN = 'OPEN' # ( { [ +T_CLOSE = 'CLOSE' # ) } ] +T_COMMA = 'COMMA' # , +T_STUFF = 'STUFF' # Code + +# --- Tokenizer --- +def tokenize(text): + tokens = [] + placeholders = {} + def mask_match(match): + key = f"__RT_MASK_{len(placeholders)}__" + placeholders[key] = match.group(0) + return key + + masked_text = re.sub(r'("|l\').*?(\1)|//.*|#.*', mask_match, text, flags=re.MULTILINE) + + i = 0 + length = len(masked_text) + at_line_start = True + + while i < length: + char = masked_text[i] + + if char == '\n': + tokens.append(Token(T_NEWLINE, char)) + at_line_start = True + i += 1 + continue + + if char.isspace(): + start = i + while i < length and masked_text[i].isspace() and masked_text[i] != '\n': + i += 1 + val = masked_text[start:i] + if at_line_start: + tokens.append(Token(T_INDENT, val)) + else: + tokens.append(Token(T_BLANK, val)) + continue + + at_line_start = False + + if char in '({[': + tokens.append(Token(T_OPEN, char)) + i += 1 + elif char in ')}]': + tokens.append(Token(T_CLOSE, char)) + i += 1 + elif char == ',': + tokens.append(Token(T_COMMA, char)) + i += 1 + else: + start = i + while i < length: + c = masked_text[i] + if c.isspace() or c in '({[]}),\n': + break + i += 1 + val = masked_text[start:i] + for k,v in placeholders.items(): + if k in val: val = val.replace(k,v) + tokens.append(Token(T_STUFF, val)) + + return tokens + +def detokenize(tokens): + return "".join(t.value for t in tokens) + +# --- Transformers (Actions) --- +def fmt_tok(t): + return f"{t.type}('{t.value}')" + +def log_action(name, idx, tokens): + if RT_DEBUG: + prev_s = fmt_tok(tokens[idx-1]) if idx > 0 else "START" + curr_s = fmt_tok(tokens[idx]) + next_s = fmt_tok(tokens[idx+1]) if idx+1 < len(tokens) else "END" + print(f"[ACTION] {name} at {idx}: {prev_s} << {curr_s} >> {next_s}") + +def action_remove_next(tokens, idx): + if idx + 1 < len(tokens): + log_action("REMOVE NEXT", idx, tokens) + del tokens[idx+1] + return True + return False + +def action_remove_prev(tokens, idx): + if idx > 0: + log_action("REMOVE PREV", idx, tokens) + del tokens[idx-1] + return True + return False + +def action_insert_space_next(tokens, idx): + log_action("INSERT SPACE NEXT", idx, tokens) + tokens.insert(idx + 1, Token(T_BLANK, " ")) + return True + +def action_insert_space_prev(tokens, idx): + log_action("INSERT SPACE PREV", idx, tokens) + tokens.insert(idx, Token(T_BLANK, " ")) + return True + +def action_fix_excess_space_next(tokens, idx): + if idx + 1 < len(tokens) and tokens[idx+1].type == T_BLANK: + if tokens[idx+1].value != " ": + log_action("FIX EXCESS SPACE NEXT", idx, tokens) + tokens[idx+1].value = " " + return True + return False + +def action_fix_excess_space_prev(tokens, idx): + if idx > 0 and tokens[idx-1].type == T_BLANK: + if tokens[idx-1].value != " ": + log_action("FIX EXCESS SPACE PREV", idx, tokens) + tokens[idx-1].value = " " + return True + return False + +# --- Recognizers (Predicates) --- +def is_open(t): return t.type == T_OPEN +def is_close(t): return t.type == T_CLOSE +def is_blank(t): return t.type == T_BLANK +def is_stuff(t): return t.type == T_STUFF +def is_comma(t): return t.type == T_COMMA + +def log_pred(name, idx, result, reason=""): + if RT_DEBUG: + r_str = "YES" if result else "NO " + print(f"[PRED] {name}({idx}) -> {r_str} ({reason})") + +def analyze_line_structure(tokens, idx): + line_start = idx + while line_start > 0 and tokens[line_start].type != T_NEWLINE: + line_start -= 1 + if tokens[line_start].type == T_NEWLINE: line_start += 1 + + line_end = idx + while line_end < len(tokens) and tokens[line_end].type != T_NEWLINE: + line_end += 1 + + stack = [] + pairs = {} + orphans = [] + + for i in range(line_start, line_end): + t = tokens[i] + if is_open(t): + stack.append(i) + elif is_close(t): + if stack: + s = stack.pop() + pairs[s] = i + else: + orphans.append(i) + widows = stack + return pairs, widows, orphans, line_start, line_end + +def is_perimeter_start(tokens, idx): + if not is_open(tokens[idx]): return False + pairs, widows, orphans, l_start, l_end = analyze_line_structure(tokens, idx) + if idx in pairs: + closer_idx = pairs[idx] + for s, e in pairs.items(): + if s < idx and e > closer_idx: return False + has_structure = False + for k in range(idx + 1, closer_idx): + if tokens[k].type in (T_OPEN, T_CLOSE): + has_structure = True + break + return has_structure + elif idx in widows: + if widows[0] != idx: return False + has_structure = False + for k in range(idx + 1, l_end): + if tokens[k].type in (T_OPEN, T_CLOSE): + has_structure = True + break + if has_structure: + log_pred("is_perimeter_start", idx, True, "First Widow with Structure") + return True + return False + +def is_perimeter_end(tokens, idx): + if not is_close(tokens[idx]): return False + pairs, widows, orphans, l_start, l_end = analyze_line_structure(tokens, idx) + reverse_pairs = {v: k for k, v in pairs.items()} + if idx in reverse_pairs: + opener_idx = reverse_pairs[idx] + for s, e in pairs.items(): + if s < opener_idx and e > idx: return False + has_structure = False + for k in range(opener_idx + 1, idx): + if tokens[k].type in (T_OPEN, T_CLOSE): + has_structure = True + break + return has_structure + elif idx in orphans: + if orphans[-1] != idx: return False + return True + return False + +def has_exact_following_space(tokens, idx): + if idx + 1 < len(tokens) and is_blank(tokens[idx+1]): + return tokens[idx+1].value == " " + return False + +def has_exact_preceding_space(tokens, idx): + if idx > 0 and is_blank(tokens[idx-1]): + return tokens[idx-1].value == " " + return False + +def has_any_following_space(tokens, idx): + return idx + 1 < len(tokens) and is_blank(tokens[idx+1]) + +def has_any_preceding_space(tokens, idx): + return idx > 0 and is_blank(tokens[idx-1]) + +def is_adjacent_closer(tokens, idx): + if not is_close(tokens[idx]): return False + if idx > 0 and is_close(tokens[idx-1]): return True + return False + +def is_brace_after_paren(tokens, idx): + if tokens[idx].value != '{': return False + prev_idx = idx - 1 + if prev_idx < 0: return False + if is_blank(tokens[prev_idx]): prev_idx -= 1 + if prev_idx >= 0 and tokens[prev_idx].value == ')': return True + return False + +def is_identifier(token): + if token.type != T_STUFF or not token.value: return False + c = token.value[0] + return c.isalpha() or c == '_' + +# --- Enclosure Fixers --- +def fix_perimeter_open(tokens, idx): + if is_perimeter_start(tokens, idx): + if not has_any_following_space(tokens, idx): + return action_insert_space_next(tokens, idx) + elif not has_exact_following_space(tokens, idx): + return action_fix_excess_space_next(tokens, idx) + return False + +def fix_perimeter_close(tokens, idx): + if is_perimeter_end(tokens, idx): + if not has_any_preceding_space(tokens, idx): + return action_insert_space_prev(tokens, idx) + elif not has_exact_preceding_space(tokens, idx): + return action_fix_excess_space_prev(tokens, idx) + return False + +def fix_interior_open(tokens, idx): + if is_open(tokens[idx]) and not is_perimeter_start(tokens, idx): + if has_any_following_space(tokens, idx): + return action_remove_next(tokens, idx) + return False + +def fix_interior_close(tokens, idx): + if is_close(tokens[idx]) and not is_perimeter_end(tokens, idx): + if has_any_preceding_space(tokens, idx): + prev_idx = idx - 1 + if prev_idx >= 0 and tokens[prev_idx].type == T_INDENT: return False + if idx > 1 and is_close(tokens[idx-2]): return False + return action_remove_prev(tokens, idx) + return False + +def fix_bookend_stack(tokens, idx): + if is_adjacent_closer(tokens, idx): + if not has_any_preceding_space(tokens, idx): + return action_insert_space_prev(tokens, idx) + return False + +def fix_brace_after_paren(tokens, idx): + if is_brace_after_paren(tokens, idx): + if has_any_preceding_space(tokens, idx): + return action_remove_prev(tokens, idx) + return False + +def fix_close_paren_followed_by_alpha(tokens, idx): + if is_close(tokens[idx]) and idx+1 < len(tokens): + nxt = tokens[idx+1] + if is_identifier(nxt): + if not has_any_following_space(tokens, idx): + return action_insert_space_next(tokens, idx) + return False + +# --- Phase 1: Enclosure Loop --- +def format_enclosure_structure(tokens): + fixers = [ + fix_bookend_stack, + fix_brace_after_paren, + fix_perimeter_open, + fix_perimeter_close, + fix_interior_open, + fix_interior_close, + fix_close_paren_followed_by_alpha + ] + i = 0 + while i < len(tokens): + stable = False + while not stable: + action_taken = False + for func in fixers: + if func(tokens, i): + action_taken = True + break + if not action_taken: stable = True + i += 1 + return tokens + +# --- Phase 2: Horizontal Comma Pass --- +def format_horizontal_commas(tokens): + """ + Sequential pass for commas. + Rule: , -> , + """ + i = 0 + while i < len(tokens): + t = tokens[i] + + # Identify Horizontal Comma + if t.type == T_COMMA: + is_vertical = False + if i > 0 and tokens[i-1].type in (T_NEWLINE, T_INDENT): + is_vertical = True + + if not is_vertical: + action_taken = False + + # 1. Ensure Space BEFORE + # If prev is NOT blank, insert one. + # If prev IS blank, ensure it is exactly " " (optional, but good for cleanliness) + if not has_any_preceding_space(tokens, i): + action_insert_space_prev(tokens, i) + i += 1 # Adjust index for inserted space + action_taken = True + + # 2. Ensure NO Space AFTER + # If next is blank, remove it. + if has_any_following_space(tokens, i): + action_remove_next(tokens, i) + # No index adjustment needed (list shrinks, next iter checks new next) + action_taken = True + + i += 1 + return tokens + +# --- Driver --- +def process_file(in_fp, out_fp): + with open(in_fp, 'r') as f: + content = f.read() + + tokens = tokenize(content) + + if RT_DEBUG: + print("--- DEBUG: Token Stream (Initial) ---") + for idx, t in enumerate(tokens): print(f"{idx}: {t}") + + # Phase 1: Walls + tokens = format_enclosure_structure(tokens) + + # Phase 2: Furniture + tokens = format_horizontal_commas(tokens) + + with open(out_fp, 'w') as f: + f.write(detokenize(tokens)) + +def CLI(): + global RT_DEBUG + args = sys.argv[1:] + if "-d" in args: + RT_DEBUG = True + args.remove("-d") + if len(args) < 1: + print("Usage: rt_fmt [-d] [out_file]") + sys.exit(1) + in_fp = args[0] + out_fp = args[1] if len(args) > 1 else in_fp + process_file(in_fp, out_fp) + if RT_DEBUG: print(f"Formatted: {in_fp} -> {out_fp}") + +if __name__ == "__main__": CLI() diff --git a/shared/authored/test.py b/shared/authored/test.py new file mode 100755 index 0000000..406a004 --- /dev/null +++ b/shared/authored/test.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import os +import sys +# Ensure we can import rt_fmt from the current directory +sys.path.append(os.getcwd()) +from rt_fmt import process_file + +def run_test(test_id ,lang_ext): + """ + Work Function: Orchestrates a single file test and reports diffs. + """ + in_fp = f"test_{test_id}_in.{lang_ext}" + exp_fp = f"test_{test_id}_expected.{lang_ext}" + out_fp = f"test_{test_id}_out.{lang_ext}" + + if not os.path.exists(in_fp): + print(f"Skipping Test {test_id}: {in_fp} not found.") + return False + + # Call the RT Formatter Work Function + # Pass distinct Input and Output paths to prevent clobbering. + process_file(in_fp, out_fp) + + # Compare results + if not os.path.exists(exp_fp): + print(f"Warning: No expectation file found for {in_fp}") + return False + + with open(exp_fp ,'r') as f_exp ,open(out_fp ,'r') as f_out: + exp_lines = f_exp.readlines() + out_lines = f_out.readlines() + + # Skip header line (Filename comments will naturally differ) + if len(exp_lines) > 0 and len(out_lines) > 0: + exp_data = exp_lines[1:] + out_data = out_lines[1:] + else: + exp_data = exp_lines + out_data = out_lines + + success_flag = (exp_data == out_data) + + if success_flag: + print(f"PASS: Test {test_id} ({lang_ext})") + else: + print(f"FAIL: Test {test_id} ({lang_ext})") + for i ,(e ,o) in enumerate(zip(exp_data ,out_data)): + if e != o: + # Use line index + 2 because we skipped line 0 (header) and are 0-indexed + print(f" Line {i+2} mismatch:") + print(f" Expected: {repr(e)}") + print(f" Result: {repr(o)}") + + return success_flag + +def CLI(): + """ + CLI Function: Runs the test suite. + """ + print("Starting RT Formatter Test Suite...") + results_seq = [ + run_test(0 ,"c") + ,run_test(1 ,"py") + ] + + total_count = len(results_seq) + pass_count = sum(1 for r in results_seq if r) + + print(f"\nSummary: {pass_count}/{total_count} tests passed.") + if pass_count < total_count: + sys.exit(1) + +if __name__ == "__main__": + CLI() diff --git a/shared/authored/test_0_expected.c b/shared/authored/test_0_expected.c new file mode 100644 index 0000000..4047d94 --- /dev/null +++ b/shared/authored/test_0_expected.c @@ -0,0 +1,12 @@ +// test_0_expected.c +void func(int a ,int b){ + if( check(a ,b) ){ + a ,b + ,c; + for(int i=0; i<10; i++){ + if(i==0) return; + } + } +} +} + diff --git a/shared/authored/test_0_in.c b/shared/authored/test_0_in.c new file mode 100644 index 0000000..b91837b --- /dev/null +++ b/shared/authored/test_0_in.c @@ -0,0 +1,10 @@ +// test_0_in.c +void func(int a,int b){ + if(check(a,b)){ + a, b, + c; + for(int i=0; i<10; i++ ){ + if(i==0) return; + } + } +} diff --git a/shared/authored/test_0_out.c b/shared/authored/test_0_out.c new file mode 100644 index 0000000..573003e --- /dev/null +++ b/shared/authored/test_0_out.c @@ -0,0 +1,10 @@ +// test_0_in.c +void func(int a ,int b){ + if( check(a ,b) ){ + a ,b , + c; + for(int i=0; i<10; i++){ + if(i==0) return; + } + } + } diff --git a/shared/authored/test_1_expected.py b/shared/authored/test_1_expected.py new file mode 100644 index 0000000..00a740e --- /dev/null +++ b/shared/authored/test_1_expected.py @@ -0,0 +1,8 @@ +# test_1_expected.py +( function(){ + items = [ + first + ,second + ,third + ] +} )(); diff --git a/shared/authored/test_1_in.py b/shared/authored/test_1_in.py new file mode 100644 index 0000000..9cbcef5 --- /dev/null +++ b/shared/authored/test_1_in.py @@ -0,0 +1,8 @@ +# test_1_in.py +(function(){ + items = [ + first + ,second + ,third + ] +})(); diff --git a/shared/authored/test_1_out.py b/shared/authored/test_1_out.py new file mode 100644 index 0000000..1a0323a --- /dev/null +++ b/shared/authored/test_1_out.py @@ -0,0 +1,8 @@ +# test_1_in.py +( function(){ + items = [ + first + ,second + ,third + ] +} )();