move parser into add-in (loses course/questions distinction)

This commit is contained in:
Lephenixnoir 2023-08-07 14:30:58 +02:00
parent 25827479ac
commit 318522d791
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
11 changed files with 959 additions and 397 deletions

View File

@ -10,7 +10,9 @@ find_package(Gint 2.9 REQUIRED)
set(SOURCES
src/brk.c
src/document.c
src/main.c
src/parser.c
src/reader.c)
set(ASSETS
assets/uf8x9
@ -29,7 +31,6 @@ set(ASSETS
math/13_arithmetique.tex)
fxconv_declare_assets(${ASSETS} WITH_METADATA)
fxconv_declare_converters(converters.py)
add_executable(addin ${SOURCES} ${ASSETS})
target_compile_options(addin PRIVATE -Wall -Wextra -Os)

View File

@ -1,164 +0,0 @@
import fxconv
import re
import enum
def convert(input, output, params, target):
recognized = True
if params["custom-type"] == "chapter":
o = convert_chapter(input, output, params)
else:
recognized = False
if recognized:
fxconv.elf(o, output, "_" + params["name"], **target)
return 0
return 1
# Lex document files line-by-line
T = enum.Enum("T", [
"CHAPTER", "METADATA", "COURSE", "QUESTION",
"SLIDE_SEP", "LINE_TEXT", "LINE_MATH",
"END"
])
LINE_TYPE_TEXT = 0
LINE_TYPE_DISPLAY_MATH = 1
def lex(lines):
re_keyword = re.compile(r'%\s*([a-z]+):\s*(.*)')
re_slide_sep = re.compile(r'---')
re_line_math = re.compile(r'\\\[\s*(|.*\S)\s*\\\]')
for l in lines:
l = l.strip()
if l == "":
continue
m = re_keyword.fullmatch(l)
if m:
value = m[2].strip()
if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
value = value[1:-1]
if m[1] == 'chapitre':
yield (T.CHAPTER, value)
elif m[1] == 'bg':
yield (T.METADATA, ("bg", value))
elif m[1] == 'cours':
yield (T.COURSE, value)
elif m[1] == 'question':
yield (T.QUESTION, value)
else:
raise fxconv.FxconvError(f"unknown %-key '{m[1]}' ({m[2]})")
continue
m = re_slide_sep.fullmatch(l)
if m:
yield (T.SLIDE_SEP, None)
continue
m = re_line_math.fullmatch(l)
if m:
yield (T.LINE_MATH, m[1])
continue
yield (T.LINE_TEXT, l)
while True:
yield (T.END, None)
class Parser:
def __init__(self, lexer):
self.lexer = lexer
self.la = next(self.lexer)
print(self.la)
def expect(self, type):
if self.la[0] != type:
raise fxconv.FxconvError(
f"parse error, expected {type}, got {self.la[0]}")
data = self.la[1]
self.la = next(self.lexer)
print(self.la)
return data
def _chapter(self):
chapter_title = self.expect(T.CHAPTER)
bg = 0
subjects = 0
questions = 0
o_subjects = fxconv.Structure()
o_questions = fxconv.Structure()
while self.la[0] == T.METADATA:
key, value = self.expect(T.METADATA)
if key == "bg" and value.startswith("0x"):
bg = int(value, 0)
else:
raise fxconv.FxconvError(f"invalid metadata '{key}: {value}'")
while self.la[0] == T.COURSE:
title = self.expect(T.COURSE)
subjects += 1
o_subjects += self._slides(title)
while self.la[0] == T.QUESTION:
title = self.expect(T.QUESTION)
questions += 1
o_questions += self._slides(title)
self.expect(T.END)
o = fxconv.Structure()
o += fxconv.string(chapter_title)
o += fxconv.u32(bg)
o += fxconv.u32(subjects)
o += fxconv.ptr(o_subjects)
o += fxconv.u32(questions)
o += fxconv.ptr(o_questions)
return o
def _slides(self, title):
slides = 0
o_slides = fxconv.Structure()
while True:
slides += 1
o_slides += self._slide()
print("got a slide")
print(o_slides.inner)
if self.la[0] != T.SLIDE_SEP:
break
self.expect(T.SLIDE_SEP)
o = fxconv.Structure()
o += fxconv.string(title)
o += fxconv.u32(slides)
o += fxconv.ptr(o_slides)
return o
def _slide(self):
lines = 0
o_lines = fxconv.Structure()
while self.la[0] in [T.LINE_TEXT, T.LINE_MATH]:
type = self.la[0]
text = self.expect(type)
lines += 1
o_lines += fxconv.u32({
T.LINE_TEXT: LINE_TYPE_TEXT,
T.LINE_MATH: LINE_TYPE_DISPLAY_MATH,
}[type])
o_lines += fxconv.string(text)
o = fxconv.Structure()
o += fxconv.u32(lines)
o += fxconv.ptr(o_lines)
return o
def convert_chapter(input, output, params):
with open(input, "r") as fp:
text = fp.read()
l = lex(text.splitlines())
p = Parser(l)
return p._chapter()

View File

@ -11,8 +11,6 @@ Elle est définie par son premier terme $u_p$ et le réel r qui est appelé "rai
\[ u_n = u_p + (n - p) × r \]
---
La somme $(S_n)$ de $(u_n)$ est :
\[ S_n = (n-p+1) \frac{u_n + u_p}{2} \]
@ -27,8 +25,6 @@ q s'appelle la raison. Quand le premier terme est $v_p$, la suite est
\[ v_n = v_p × q^{n-p} \]
---
La somme $(S_n)$ de $(v_n)$ est :
\[ S_n = v_p × \frac{1 - q^{n-p+1}}{1-q} \]
@ -47,8 +43,6 @@ Quand $u_n > 0$, on peut aussi étudier $u_{n+1} / u_n$.
- $(u_n)$ est croissante si $u_{n+1} / u_n ≥ 1$
- $(u_n)$ est décroissante si $u_{n+1} / u_n ≤ 1$
---
Si $(u_n)$ est de la forme $u_n = f(n)$, il faut étudier les variations de f.
Si $u_{n+1} = f(u_n)$, il faut faire un raisonnement par récurrence.
@ -85,8 +79,6 @@ Lorsque $(u_n)$ est divergente :
- Si elle est minorée, elle est convergente
- Sinon, elle tend vers -∞.
---
Si $(u_n)$ est croissante et tend vers l, alors elle est majorée par l.
Si $(u_n)$ est décroissante et tend vers l, alors est elle minorée par l.

View File

@ -1,3 +1,3 @@
*.tex:
name_regex: (.*)\.tex math_\1
custom-type: chapter
name_regex: (.*)\.tex file_\1_tex
type: binary

156
src/document.c Normal file
View File

@ -0,0 +1,156 @@
#include <stdlib.h>
#include <string.h>
#include "document.h"
static struct element *mkelement(void)
{
return calloc(1, sizeof (struct element));
}
static struct element *mkelement_with_text(char const *str, int len)
{
struct element *e = mkelement();
if(!e)
return NULL;
e->str = (len >= 0) ? strndup(str, len) : strdup(str);
if(!e->str) {
free(e);
return NULL;
}
return e;
}
struct element *element_new_inline_text(char const *str, int len)
{
struct element *e = mkelement_with_text(str, len);
if(e)
e->type = ELEMENT_INLINE_TEXT;
return e;
}
struct element *element_new_inline_math(char const *str, int len)
{
struct element *e = mkelement_with_text(str, len);
if(e)
e->type = ELEMENT_INLINE_MATH;
return e;
}
struct element *element_new_new_paragraph(void)
{
struct element *e = mkelement();
if(e)
e->type = ELEMENT_NEW_PARAGRAPH;
return e;
}
struct element *element_new_block_math(char const *str, int len)
{
struct element *e = mkelement_with_text(str, len);
if(e)
e->type = ELEMENT_BLOCK_MATH;
return e;
}
void element_free(struct element *e)
{
free(e->str);
free(e);
}
struct page *page_new(void)
{
return calloc(1, sizeof(struct page));
}
void page_set_title(struct page *page, char const *title, int len)
{
free(page->title);
page->title = NULL;
if(title)
page->title = (len >= 0) ? strndup(title, len) : strdup(title);
}
void page_set_category(struct page *page, char const *ctgy, int len)
{
free(page->category);
page->category = NULL;
if(ctgy)
page->category = (len >= 0) ? strndup(ctgy, len) : strdup(ctgy);
}
void page_add_element(struct page *page, struct element *element)
{
int new_count = page->element_count + 1;
struct element **new_buf =
realloc(page->elements, new_count * sizeof *new_buf);
if(!new_buf)
return;
new_buf[new_count - 1] = element;
page->elements = new_buf;
page->element_count = new_count;
}
void page_free(struct page *page)
{
if(!page)
return;
free(page->title);
free(page->category);
for(int i = 0; i < page->element_count; i++)
element_free(page->elements[i]);
free(page->elements);
free(page);
}
struct document *document_new(void)
{
return calloc(1, sizeof(struct document));
}
void document_set_title(struct document *doc, char const *title, int len)
{
free(doc->title);
doc->title = NULL;
if(title)
doc->title = (len >= 0) ? strndup(title, len) : strdup(title);
}
void document_set_symbol(struct document *doc, char const *symbol, int len)
{
free(doc->symbol);
doc->symbol = NULL;
if(symbol)
doc->symbol = (len >= 0) ? strndup(symbol, len) : strdup(symbol);
}
void document_set_bg(struct document *doc, int bg)
{
doc->bgcolor = bg;
}
void document_add_page(struct document *doc, struct page *page)
{
int new_count = doc->page_count + 1;
struct page **new_buf = realloc(doc->pages, new_count * sizeof *new_buf);
if(!new_buf)
return;
new_buf[new_count - 1] = page;
doc->pages = new_buf;
doc->page_count = new_count;
}
void document_free(struct document *doc)
{
if(!doc)
return;
free(doc->title);
free(doc->symbol);
for(int i = 0; i < doc->page_count; i++)
page_free(doc->pages[i]);
free(doc->pages);
free(doc);
}

75
src/document.h Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include <stddef.h>
/* High-level document for the purpose of this application; it is a list of
pages (single-page documents flowing top to bottom) with categories. */
struct document {
char *title;
char *symbol;
int bgcolor;
/* All pages are owned by the document. */
int page_count;
struct page **pages;
};
/* Linear, mostly unstructured sequence of elements. Some elements are inline,
some are blocks, some are inline blocks, resulting in varied layouts. This
intentionally has no nested structure because I don't have time for
something complicated right now. Morally, it could be more complicated. */
struct page {
char *title;
char *category;
/* Element vector owned by the page. */
int element_count;
struct element **elements;
};
/* page element, a single inline or block object in the page. */
enum {
ELEMENT_INLINE_TEXT, /* Inline text */
ELEMENT_INLINE_MATH, /* Inline math */
ELEMENT_NEW_PARAGRAPH, /* Paragraph delimited */
ELEMENT_BLOCK_MATH, /* Display math */
};
struct element {
int type;
/* Optional NUL-terminated string, owned by the element. */
char *str;
};
//======= API for manipulating documents =======//
/* Make new elements */
struct element *element_new_inline_text(char const *str, int len);
struct element *element_new_inline_math(char const *str, int len);
struct element *element_new_new_paragraph(void);
struct element *element_new_block_math(char const *str, int len);
/* Free an element */
void element_free(struct element *element);
/* Make an empty page, set its metadata, and free it (with its elements). */
struct page *page_new(void);
void page_set_title(struct page *page, char const *title, int len);
void page_set_category(struct page *page, char const *category, int len);
void page_free(struct page *page);
/* Add elements to a page. */
void page_add_element(struct page *page, struct element *element);
/* Make an empty document, set its metadata, free it (pages and their elements
are freed as well). */
struct document *document_new(void);
void document_set_title(struct document *doc, char const *title, int len);
void document_set_symbol(struct document *doc, char const *symbol, int len);
void document_set_bg(struct document *doc, int bg);
void document_free(struct document *doc);
/* Add pages to the document. */
void document_add_page(struct document *doc, struct page *page);
/* Parser a document from a text file. */
struct document *document_parse(char const *str, int len);

View File

@ -1,59 +1,61 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <TeX/TeX.h>
#include "mathsts.h"
#include "document.h"
#include "reader.h"
#define ENUM_CHAPTERS(X) \
X(math_01_suites) \
X(math_02_complexes) \
X(math_03_limites) \
X(math_04_derivation) \
X(math_05_exponentielle) \
X(math_06_logarithme) \
X(math_07_trigo) \
X(math_08_integration) \
X(math_09_geometrie) \
X(math_10_probabilites) \
X(math_11_loisprobas) \
X(math_12_stats) \
X(math_13_arithmetique) \
#define ENUM_FILES(X) \
X(file_01_suites_tex) \
X(file_02_complexes_tex) \
X(file_03_limites_tex) \
X(file_04_derivation_tex) \
X(file_05_exponentielle_tex) \
X(file_06_logarithme_tex) \
X(file_07_trigo_tex) \
X(file_08_integration_tex) \
X(file_09_geometrie_tex) \
X(file_10_probabilites_tex) \
X(file_11_loisprobas_tex) \
X(file_12_stats_tex) \
X(file_13_arithmetique_tex) \
#define EXTERN_DECLARE(NAME) extern struct chapter const NAME;
ENUM_CHAPTERS(EXTERN_DECLARE)
#define EXTERN_DECLARE(NAME) \
extern char const NAME[]; \
extern int const NAME ## _size;
ENUM_FILES(EXTERN_DECLARE)
#define INITIALIZE(NAME) &NAME,
static struct chapter const *chapters[] = { ENUM_CHAPTERS(INITIALIZE) };
struct source {
char const *str;
int len;
};
#define INITIALIZE(NAME) { NAME, (int)&NAME ## _size },
static struct source const files[] = { ENUM_FILES(INITIALIZE) };
#define COUNT(NAME) +1
static int file_count = 0 ENUM_FILES(COUNT);
// TODO: Incorporate chapter symbols into TeX files
static char const *chapter_symbols[13] = {
"Un", "Z", "lim", "f'", "e ", "ln", "cos", "∫dx",
"u", "P(A)", "λ", "In", "[k]",
"Un", "Z", "lim", "f'", "e^x", "ln", "cos", "∫dx",
"\\vec{u}", "P(A)", "λ", "In", "[k]",
};
static void render_chapter_tile(int chapter, int where, bool selected)
static void render_document_tile(struct document const *doc, int symbolid,
int where, bool selected)
{
if((unsigned)chapter >= 13)
return;
int y = 87;
int middle = DWIDTH / 2 + 67 * where + 6 * ((where > 0) - (where < 0));
int r = (where == 0) ? 26 : 20;
drect(middle-r, y-r, middle+r, y+r, chapters[chapter]->bgcolor);
if(selected) {
drect_border(middle-r-2, y-r-2, middle+r+2, y+r+2, C_NONE, 1,
chapters[chapter]->bgcolor);
}
dtext_opt(middle, y, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
chapter_symbols[chapter], -1);
int color = doc ? doc->bgcolor : C_RGB(16, 16, 16);
/* Special decorations */
if(chapter == 4)
dtext_opt(middle-1, y-6, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
" x", -1);
if(chapter == 8)
dtext_opt(middle+1, y-6, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
"", -1);
drect(middle-r, y-r, middle+r, y+r, color);
if(selected)
drect_border(middle-r-2, y-r-2, middle+r+2, y+r+2, C_NONE, 1, color);
if(symbolid >= 0)
dtext_opt(middle, y, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
chapter_symbols[symbolid], -1);
}
void render_buttons(int x0, int y, int count, int selected, int color,
@ -84,7 +86,8 @@ static void textext(char const *str, int x, int y, int color)
dtext(x, y, color, str);
}
static int move_details_cursor(struct chapter const *ch, int c_details,
#if 0
static int move_details_cursor(struct chapter const *ch, int c_page,
int new_details)
{
int row = new_details & 1;
@ -92,10 +95,11 @@ static int move_details_cursor(struct chapter const *ch, int c_details,
int max = row ? ch->questions_size : ch->subjects_size;
if(max == 0)
return c_details;
return c_page;
col = (col < 0) ? 0 : (col >= max) ? max - 1 : col;
return 2 * col + row;
}
#endif
int main(void)
{
@ -107,14 +111,22 @@ int main(void)
TeX_intf_size(texsize);
TeX_intf_text(textext);
// TODO: Also load files from storage memory
struct document *documents[13] = { 0 };
int document_count = 13;
for(int i = 0; i < file_count; i++) {
documents[i] = document_parse(files[i].str, files[i].len);
}
/* Cursor for selecting stuff in the menu */
enum { C_CHAPTER, C_DETAILS } c_mode = C_CHAPTER;
int c_chapter = 0;
int c_details = 0;
enum { C_DOCUMENT, C_PAGE } c_mode = C_DOCUMENT;
int c_document = 0;
int c_page = 0;
/* Main menu loop */
while(1) {
struct chapter const *ch = chapters[c_chapter];
struct document const *doc = documents[c_document];
dclear(C_WHITE);
drect(4, 4, DWIDTH-1-4, 44, C_BLACK);
@ -124,75 +136,97 @@ int main(void)
/* Tiles */
for(int d = -2; d <= +2; d++) {
render_chapter_tile(c_chapter + d, d, !d && c_mode == C_CHAPTER);
if(c_document + d < 0 || c_document + d >= document_count)
continue;
render_document_tile(documents[c_document + d],
(c_document + d <= 13) ? c_document + d : -1,
d, !d && c_mode == C_DOCUMENT);
}
if(c_chapter > 2) {
if(c_document > 2) {
dtext_opt(20, 87, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
"<", -1);
}
if(c_chapter < 10) {
if(c_document < document_count - 3) {
dtext_opt(DWIDTH-20, 87, C_BLACK, C_NONE, DTEXT_CENTER,
DTEXT_MIDDLE, ">", -1);
}
/* Chapter preview */
dtext_opt(DWIDTH/2, 128, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
chapters[c_chapter]->title, -1);
if(doc) {
dtext_opt(DWIDTH/2, 128, C_BLACK, C_NONE, DTEXT_CENTER,
DTEXT_MIDDLE, doc->title, -1);
}
#if 0
dtext_opt(100, 160, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE,
"Cours:", -1);
render_buttons(110, 160, ch->subjects_size,
(c_mode == C_DETAILS && !(c_details & 1)) ? c_details / 2 : -1,
(c_mode == C_PAGE && !(c_page & 1)) ? c_page / 2 : -1,
C_BLACK, 14);
dtext_opt(100, 176, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE,
"Questions:", -1);
render_buttons(110, 176, ch->questions_size,
(c_mode == C_DETAILS && (c_details & 1)) ? c_details / 2 : -1,
(c_mode == C_PAGE && (c_page & 1)) ? c_page / 2 : -1,
C_BLACK, 14);
#endif
dtext_opt(100, 160, C_BLACK, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE,
"Pages:", -1);
render_buttons(110, 160, doc ? doc->page_count : 0,
(c_mode == C_PAGE) ? c_page : -1, C_BLACK, 14);
if(c_mode == C_DETAILS) {
char const *title = (c_details & 1)
? ch->questions[c_details / 2].title
: ch->subjects[c_details / 2].title;
if(c_mode == C_PAGE && doc && doc->pages[c_page]->title) {
dtext_opt(26, 200, C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_MIDDLE,
title, -1);
doc->pages[c_page]->title, -1);
}
dupdate();
int key = getkey_opt(GETKEY_DEFAULT & ~GETKEY_MOD_SHIFT, NULL).key;
if(key == KEY_LEFT && c_mode == C_CHAPTER && c_chapter-1 >= 0) {
c_chapter--;
c_details = 0;
if(key == KEY_LEFT && c_mode == C_DOCUMENT && c_document-1 >= 0) {
c_document--;
c_page = 0;
}
if(key == KEY_RIGHT && c_mode == C_CHAPTER && c_chapter+1 < 13) {
c_chapter++;
c_details = 0;
if(key == KEY_RIGHT && c_mode == C_DOCUMENT
&& c_document+1 < document_count) {
c_document++;
c_page = 0;
}
if(key == KEY_LEFT && c_mode == C_DETAILS)
c_details = move_details_cursor(ch, c_details, c_details - 2);
if(key == KEY_RIGHT && c_mode == C_DETAILS)
c_details = move_details_cursor(ch, c_details, c_details + 2);
if(key == KEY_UP && c_mode == C_DETAILS)
c_details = move_details_cursor(ch, c_details, c_details & -2);
if(key == KEY_DOWN && c_mode == C_DETAILS)
c_details = move_details_cursor(ch, c_details, c_details | 1);
#if 0
if(key == KEY_LEFT && c_mode == C_PAGE)
c_page = move_details_cursor(ch, c_page, c_page - 2);
if(key == KEY_RIGHT && c_mode == C_PAGE)
c_page = move_details_cursor(ch, c_page, c_page + 2);
if(key == KEY_UP && c_mode == C_PAGE)
c_page = move_details_cursor(ch, c_page, c_page & -2);
if(key == KEY_DOWN && c_mode == C_PAGE)
c_page = move_details_cursor(ch, c_page, c_page | 1);
#endif
if(key == KEY_LEFT && c_mode == C_PAGE && c_page > 0)
c_page--;
if(key == KEY_RIGHT && c_mode == C_PAGE && c_page+1 < doc->page_count)
c_page++;
if(key == KEY_EXIT && c_mode == C_DETAILS)
c_mode = C_CHAPTER;
if(key == KEY_EXIT && c_mode == C_PAGE)
c_mode = C_DOCUMENT;
if(key == KEY_SHIFT || key == KEY_EXE) {
if(c_mode == C_CHAPTER) {
if(c_mode == C_DOCUMENT) {
if(doc->page_count > 0) {
c_mode = C_PAGE;
c_page = 0;
}
#if 0
if(ch->subjects_size > 0) {
c_mode = C_DETAILS;
c_details = 0;
c_mode = C_PAGE;
c_page = 0;
}
else if(ch->questions_size > 0) {
c_mode = C_DETAILS;
c_details = 1;
c_mode = C_PAGE;
c_page = 1;
}
#endif
}
else {
read_chapter(ch, c_details & 1, c_details / 2);
read_document(doc, c_page);
}
}
}

View File

@ -1,45 +0,0 @@
#pragma once
/* "Slides" structure, represents a document as a set of slides containing text
and TeX formulas. */
struct slides {
char const *title;
int size;
struct slide *slides;
};
struct slide {
int size;
struct line *lines;
};
enum {
LINE_TYPE_TEXT,
LINE_TYPE_DISPLAY_MATH,
};
struct line {
int type;
char const *source;
};
/* Chapter structure:
- Slides for each course subject
- Slides for each typical question */
struct chapter {
char const *title;
int bgcolor;
int subjects_size;
struct slides *subjects;
int questions_size;
struct slides *questions;
};
/* Start reading specified chapter, starting at the provided category (0 for
subjects, 1 for questions) at position index (0 index < subjects_size or
0 index < questions_size). */
void read_chapter(struct chapter const *chapter, int category, int index);
/* Render slide buttons */
void render_buttons(int x, int y, int count, int selected, int color,
int spacing);

528
src/parser.c Normal file
View File

@ -0,0 +1,528 @@
/* Lexer and parser for TeX-like files for courses, questions, etc. */
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "document.h"
/* Token types. */
enum {
T_END = 0, /* EOF */
T_METADATA, /* Metadata line "% <name>: <value>" */
T_PARAGRAPH, /* Paragraph separator (double newline) */
T_TEXT, /* Plain text */
T_INLINEMATH, /* Inline math $...$ */
T_DISPLAYMATH, /* Display math \[ ... \] */
T_ERROR, /* Lexing error indicator */
};
struct token {
int type; /* A T_* enumerated token type */
char const *str1; /* Pointer to substring of input 1 */
int len1; /* Length of substring 1 */
char const *str2; /* Pointer to substring of input 2 */
int len2; /* Length of substring 2 */
};
struct lexer {
char const *str; /* Input text */
int len; /* Length of input string */
int offset; /* Current offset */
bool start_of_line; /* Whether we're at the start of a line */
};
static void lexer_init(struct lexer *lexer, char const *str, int len)
{
lexer->str = str;
lexer->len = len;
lexer->offset = 0;
lexer->start_of_line = true;
}
static void lexer_deinit(struct lexer *lexer)
{
memset(lexer, 0, sizeof *lexer);
}
/* Skip spaces, returns true if `end` is reached. */
static bool skip_spaces(char const **str, char const *end)
{
while(*str < end && (**str == ' ' || **str == '\t'))
(*str)++;
return (*str >= end);
}
/* ^%\s*([a-z]+):\s*(.*)$
If the value (second group) is surrounded with '"', take them out. */
static bool _lex_metadata(struct lexer *lexer, struct token *token)
{
char const *str = lexer->str + lexer->offset;
char const *end = lexer->str + lexer->len;
if(str > end - 1 || !lexer->start_of_line || *str != '%')
return false;
str++;
if(skip_spaces(&str, end))
return false;
char const *name = str;
while(str < end && islower(*str))
str++;
if(str >= end || str == name)
return false;
int name_len = str - name;
if(skip_spaces(&str, end))
return false;
if(*str++ != ':')
return false;
if(skip_spaces(&str, end))
return false;
char const *arg = str;
while(str < end && *str != '\n')
str++;
int arg_len = str - arg;
if(arg_len >= 2 && arg[0] == '"' && arg[arg_len-1] == '"') {
arg++;
arg_len -= 2;
}
lexer->offset = (str + (*str == '\n')) - lexer->str;
lexer->start_of_line = (*str == '\n');
token->type = T_METADATA;
token->str1 = name;
token->len1 = name_len;
token->str2 = arg;
token->len2 = arg_len;
return true;
}
/* \[\s*(.*+)\s*\] */
static bool _lex_displaymath(struct lexer *lexer, struct token *token)
{
char const *str = lexer->str + lexer->offset;
char const *end = lexer->str + lexer->len;
if(!lexer->start_of_line || skip_spaces(&str, end))
return false;
if(str > end - 2 || str[0] != '\\' || str[1] != '[')
return false;
str += 2;
if(skip_spaces(&str, end))
return false;
char const *formula = str;
while(str <= end - 2 && (str[0] != '\\' || str[1] != ']'))
str++;
if(str > end - 2 || str[0] != '\\' || str[1] != ']')
return false;
/* Trim spaces on the right */
int formula_len = str - formula;
while(formula_len > 0 && (formula[formula_len - 1] == ' ' ||
formula[formula_len - 1] == '\t'))
formula_len--;
str += 2;
skip_spaces(&str, end);
bool end_of_line = (str < end && *str == '\n');
lexer->offset = (str + end_of_line) - lexer->str;
lexer->start_of_line = end_of_line;
token->type = T_DISPLAYMATH;
token->str1 = formula;
token->len1 = formula_len;
return true;
}
/* \n+, emits a T_PARAGRAPH for \n{2,} but always skips, even one */
static bool _lex_par(struct lexer *lexer, struct token *token)
{
char const *str = lexer->str + lexer->offset;
char const *end = lexer->str + lexer->len;
if(str > end - 1 || *str != '\n')
return false;
/* Count newlines */
int newline_count = 0;
while(str < end && *str == '\n')
str++, newline_count++;
lexer->offset = str - lexer->str;
lexer->start_of_line = true;
if(newline_count < 2)
return false;
token->type = T_PARAGRAPH;
return true;
}
/* $\s*(.*+)\s*$ */
static bool _lex_inlinemath(struct lexer *lexer, struct token *token)
{
char const *str = lexer->str + lexer->offset;
char const *end = lexer->str + lexer->len;
if(str > end - 1 || *str != '$')
return false;
str++;
char const *formula = str;
while(str <= end - 1 && *str != '$')
str++;
if(str > end - 1 || str == formula)
return false;
int formula_len = str - formula;
/* Trim spaces on both sides */
while(formula_len > 0 && formula[0] == ' ') {
formula++;
formula_len--;
}
while(formula_len > 0 && formula[formula_len-1] == ' ')
formula_len--;
lexer->offset = (str + 1) - lexer->str;
lexer->start_of_line = false;
token->type = T_INLINEMATH;
token->str1 = formula;
token->len1 = formula_len;
return true;
}
static bool _lex_text(struct lexer *lexer, struct token *token)
{
char const *str = lexer->str + lexer->offset;
char const *end = lexer->str + lexer->len;
char const *text = str;
while(str <= end - 1 && *str != '\n' && *str != '$')
str++;
int text_len = str - text;
if(text_len == 0)
return false;
lexer->offset = str - lexer->str;
lexer->start_of_line = false;
token->type = T_TEXT;
token->str1 = text;
token->len1 = text_len;
return true;
}
static void _lex(struct lexer *lexer, struct token *token)
{
memset(token, 0, sizeof *token);
/* Has the side-effect of skipping incoming newlines even when no match */
if(_lex_par(lexer, token))
return;
if(lexer->offset >= lexer->len) {
token->type = T_END;
return;
}
if(_lex_metadata(lexer, token))
return;
if(_lex_displaymath(lexer, token))
return;
if(_lex_inlinemath(lexer, token))
return;
if(_lex_text(lexer, token))
return;
/* Default to an error on one character */
lexer->start_of_line = (lexer->str[lexer->offset] == '\n');
lexer->offset++;
token->type = T_ERROR;
token->str1 = lexer->str + lexer->offset - 1;
token->len1 = 1;
}
#if 0
#include <stdio.h>
#include <stdlib.h>
static void dump_tokens(char const *str, int len)
{
struct lexer l;
lexer_init(&l, str, len);
char const *T_names[] = {
"T_END", "T_METADATA", "T_PARAGRAPH", "T_TEXT", "T_INLINEMATH",
"T_DISPLAYMATH", "T_ERROR",
};
int T_names_size = sizeof T_names / sizeof T_names[0];
struct token t;
do {
_lex(&l, &t);
if(t.type >= 0 && t.type < T_names_size)
printf("%s", T_names[t.type]);
else
printf("<%d>", t.type);
if(t.str1)
printf(": `%.*s`", t.len1, t.str1);
if(t.str2)
printf(", `%.*s`", t.len2, t.str2);
printf("\n");
}
while(t.type != T_END);
lexer_deinit(&l);
}
int main(int argc, char **argv)
{
FILE *fp = fopen(argv[1], "r");
if(!fp) {
perror("fopen");
return 1;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *str = malloc(len);
fread(str, 1, len, fp);
fclose(fp);
dump_tokens(str, len);
free(str);
return 0;
}
#endif
struct parser {
struct lexer *lexer;
/* Look-ahead token */
struct token la;
};
static void parser_init(struct parser *p, struct lexer *l)
{
p->lexer = l;
_lex(l, &p->la);
}
static void parser_deinit(struct parser *p)
{
lexer_deinit(p->lexer);
}
static struct token feed(struct parser *p)
{
struct token t = p->la;
_lex(p->lexer, &p->la);
return t;
}
static bool is_metadata(struct token const *t, char const *kind)
{
return t->type == T_METADATA
&& t->len1 == (int)strlen(kind)
&& !strncmp(t->str1, kind, t->len1);
}
static bool accept(struct parser *p, int type, struct token *t)
{
if(p->la.type == type) {
*t = feed(p);
return true;
}
return false;
}
static bool accept_metadata(struct parser *p, char const *kind,
struct token *t)
{
if(is_metadata(&p->la, kind)) {
*t = feed(p);
return true;
}
return false;
}
static bool is_inline(struct token const *t)
{
return (t->type == T_TEXT || t->type == T_INLINEMATH);
}
struct page *_parse_page(struct parser *p)
{
struct page *page = page_new();
if(!page)
return NULL;
/* Find page title */
struct token t;
if(accept_metadata(p, "cours", &t)) {
page_set_title(page, t.str2, t.len2);
page_set_category(page, "Cours", -1);
}
else if(accept_metadata(p, "question", &t)) {
page_set_title(page, t.str2, t.len2);
page_set_category(page, "Questions", -1);
}
else goto error;
/* Now accept any number of elements */
int last_type_was_inline = false;
while(1) {
struct token t;
if(accept(p, T_TEXT, &t)) {
struct element *e = element_new_inline_text(t.str1, t.len1);
page_add_element(page, e);
}
else if(accept(p, T_INLINEMATH, &t)) {
struct element *e = element_new_inline_math(t.str1, t.len1);
page_add_element(page, e);
}
else if(accept(p, T_DISPLAYMATH, &t)) {
struct element *e = element_new_block_math(t.str1, t.len1);
page_add_element(page, e);
}
else if(accept(p, T_PARAGRAPH, &t)) {
if(last_type_was_inline && is_inline(&p->la)) {
struct element *e = element_new_new_paragraph();
page_add_element(page, e);
}
}
else break;
last_type_was_inline = is_inline(&t);
}
return page;
error:
page_free(page);
return NULL;
}
struct document *document_parse(char const *str, int len)
{
struct lexer l;
lexer_init(&l, str, len);
struct parser p;
parser_init(&p, &l);
struct document *doc = document_new();
while(p.la.type != T_END) {
struct token t;
if(accept_metadata(&p,"chapitre", &t)) {
document_set_title(doc, t.str2, t.len2);
continue;
}
if(accept_metadata(&p,"symbole", &t)) {
document_set_symbol(doc, t.str2, t.len2);
continue;
}
if(accept_metadata(&p,"bg", &t)) {
// TODO: Not very clean (can overflow, in principle)
document_set_bg(doc, strtol(t.str2, NULL, 0));
continue;
}
if(is_metadata(&p.la, "cours") || is_metadata(&p.la, "question")) {
struct page *page = _parse_page(&p);
if(page)
document_add_page(doc, page);
continue;
}
goto error;
}
parser_deinit(&p);
return doc;
error:
document_free(doc);
lexer_deinit(&l);
return NULL;
}
#if 0
#include <stdio.h>
#include <stdlib.h>
static void dump_document(struct document const *doc)
{
if(!doc) {
printf("(null)\n");
return;
}
printf("document:\n");
printf(" title: `%s`\n", doc->title);
printf(" symbol: `%s`\n", doc->symbol);
printf(" bgcolor: 0x%04x\n", doc->bgcolor);
printf(" pages:\n");
for(int i = 0; i < doc->page_count; i++) {
struct page *page = doc->pages[i];
printf(" - title: `%s`\n", page->title);
printf(" category: `%s`\n", page->category);
printf(" elements:\n");
for(int j = 0; j < page->element_count; j++) {
struct element *e = page->elements[j];
printf(" - ");
if(e->type == ELEMENT_INLINE_TEXT)
printf("INLINE_TEXT: `%s`\n", e->str);
else if(e->type == ELEMENT_INLINE_MATH)
printf("INLINE_MATH: `%s`\n", e->str);
else if(e->type == ELEMENT_BLOCK_MATH)
printf("BLOCK_MATH: `%s`\n", e->str);
else if(e->type == ELEMENT_NEW_PARAGRAPH)
printf("NEW_PARAGRAPH\n");
}
}
}
int main(int argc, char **argv)
{
FILE *fp = fopen(argv[1], "r");
if(!fp) {
perror("fopen");
return 1;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *str = malloc(len);
fread(str, 1, len, fp);
fclose(fp);
struct document *doc = document_parse(str, len);
dump_document(doc);
document_free(doc);
free(str);
return 0;
}
#endif

View File

@ -4,7 +4,8 @@
#include <TeX/TeX.h>
#include <TeX/env.h>
#include <string.h>
#include "mathsts.h"
#include "reader.h"
#include "document.h"
#include "brk.h"
static bool debug = false;
@ -38,20 +39,6 @@ static void fkey_button(int position, char const *text, int bg)
dtext(x + ((w - width) >> 1), y + 3, C_WHITE, text);
}
static char const *find_dollar(char const *str, char const **brk, bool skip_it)
{
/* Find the first unescaped '$' */
while(*str) {
if(*str == '$') {
*brk = str + (skip_it == true);
return str;
}
str = str + 1 + (str[0] == '\\' && str[1] != 0);
}
*brk = str;
return str;
}
static int rtl_w = -1;
static struct TeX_Env *rtl_formulas[32] = { NULL };
@ -81,43 +68,63 @@ static void rtl_rbf(int x, int y, unsigned int id)
TeX_draw(rtl_formulas[id], x, y, C_BLACK);
}
static int render_text_line(int x, int y, int w, char const *source,
static int render_page(int x, int y, int w, struct page const *page,
font_t const *font, int wrap_mode)
{
char const *next;
int formula_count = 0;
rtl_w = w;
struct brk_state *brk = brk_new(x, y, rtl_lwf, rtl_rtf, rtl_rbf);
/* Each loop iteration is a either a text segment or a formula */
while(*source) {
/* Formula */
if(*source == '$') {
source++;
char const *end = find_dollar(source, &next, true);
if(formula_count < 32) {
char *formula = strndup(source, end-source);
struct TeX_Env *env = TeX_parse(formula, false);
free(formula);
rtl_formulas[formula_count] = env;
// TODO: Adjustment between text baseline and formula baseline
// hardcoded here
/* Keep one pixel of padding on the right for punctuation */
brk_layout_block(brk, formula_count, env->width+1, env->height,
env->line + 5);
formula_count++;
}
source = next;
for(int i = 0; i < page->element_count; i++) {
struct element const *e = page->elements[i];
if(e->type == ELEMENT_INLINE_MATH && formula_count < 32) {
struct TeX_Env *env = TeX_parse(e->str, false);
rtl_formulas[formula_count] = env;
// TODO: Adjustment between text baseline and formula baseline
// hardcoded here
/* Keep one pixel of padding on the right for punctuation */
brk_layout_block(brk, formula_count, env->width+1, env->height,
env->line + 5);
formula_count++;
}
/* Text */
else {
char const *end = find_dollar(source, &next, false);
brk_layout_text(brk, source, end-source, font, wrap_mode, 0);
source = next;
else if(e->type == ELEMENT_INLINE_TEXT) {
brk_layout_text(brk, e->str, -1, font, wrap_mode, 0);
}
else if(e->type == ELEMENT_BLOCK_MATH && formula_count < 32) {
struct TeX_Env *env = TeX_parse(e->str, true);
rtl_formulas[formula_count] = env;
// TODO: Center these
brk_layout_newline(brk);
brk_layout_block(brk, formula_count, env->width, env->height,
env->line);
brk_layout_newline(brk);
formula_count++;
}
}
#if 0
else if(line->type == LINE_TYPE_DISPLAY_MATH) {
struct TeX_Env *env = TeX_parse(line->source, true);
if(!env) { ... }
int cx = x + w / 2;
int ex = cx - env->width / 2;
int ey = y + 2;
if(debug) {
drect(ex, ey, ex+env->width-1, ey+env->height-1,
C_RGB(23, 23, 23));
dline(ex, ey+env->line, ex+env->width-1, ey+env->line,
C_RED);
}
TeX_draw(env, cx - env->width / 2, y+2, C_BLACK);
TeX_free(env);
y += env->height + 5;
}
#endif
brk_layout_finish(brk);
int h = brk_get_h(brk);
@ -131,56 +138,6 @@ static int render_text_line(int x, int y, int w, char const *source,
return h;
}
static int render(int x, int y0, int w, int h, struct slides const *slides,
int scroll)
{
int y = y0 - scroll;
(void)h;
for(int i = 0; i < slides->size; i++) {
struct slide const *slide = &slides->slides[i];
for(int j = 0; j < slide->size; j++) {
struct line const *line = &slide->lines[j];
if(line->type == LINE_TYPE_TEXT) {
int h = render_text_line(x+4, y, w-8, line->source, NULL,
BRK_WRAP_WORD);
y += h;
}
else if(line->type == LINE_TYPE_DISPLAY_MATH) {
struct TeX_Env *env = TeX_parse(line->source, true);
int cx = x + w / 2;
if(env) {
int ex = cx - env->width / 2;
int ey = y + 2;
if(debug) {
drect(ex, ey, ex+env->width-1, ey+env->height-1,
C_RGB(23, 23, 23));
dline(ex, ey+env->line, ex+env->width-1, ey+env->line,
C_RED);
}
TeX_draw(env, cx - env->width / 2, y+2, C_BLACK);
TeX_free(env);
y += env->height + 5;
}
else {
dtext_opt(cx, y, C_RED, C_NONE, DTEXT_CENTER, DTEXT_TOP,
"[formula parse error]", -1);
y += 13;
}
}
else {
dprint(x+20, y, C_BLACK, "(%d) \"%s\"", line->type,
line->source);
y += 13;
}
}
}
return y - (y0 - scroll);
}
static void scrollbar(int x, int y, int scroll, int content_height,
int display_height)
{
@ -193,18 +150,21 @@ static void scrollbar(int x, int y, int scroll, int content_height,
drect(x, y+top, x+1, y+top+height-1, C_BLACK);
}
void read_chapter(struct chapter const *chapter, int category, int index)
void read_document(struct document const *doc, int page_index)
{
int scroll = 0;
int maxscroll = 0;
while(1) {
struct page const *page = doc->pages[page_index];
dclear(C_WHITE);
drect(4, 4, DWIDTH-1-4, 44, chapter->bgcolor);
drect(4, 4, DWIDTH-1-4, 44, doc->bgcolor);
drect_border(5, 5, DWIDTH-1-5, 43, C_NONE, 1, C_WHITE);
dtext(12, 12, C_WHITE, chapter->title);
dtext(12, 12, C_WHITE, doc->title);
int spacing = 9;
#if 0
int total = chapter->subjects_size + chapter->questions_size;
render_small_buttons(DWIDTH - 18 - spacing * total, 16,
chapter->subjects_size, chapter->questions_size,
@ -216,6 +176,12 @@ void read_chapter(struct chapter const *chapter, int category, int index)
category ? chapter->questions_size : chapter->subjects_size,
category ? chapter->questions[index].title
: chapter->subjects[index].title);
#endif
render_small_buttons(DWIDTH - 18 - spacing * doc->page_count, 16,
doc->page_count, 0, 0, page_index, C_WHITE,
spacing);
dprint(12, 27, C_WHITE,
"%d/%d : %s", page_index + 1, doc->page_count, page->title);
if(debug) {
kmalloc_arena_t *arena = kmalloc_get_arena("_uram");
@ -224,12 +190,16 @@ void read_chapter(struct chapter const *chapter, int category, int index)
DTEXT_TOP, "heap: %d", stats->free_memory);
}
#if 0
bool can_go_left =
(index > 0) || (category > 0 && chapter->subjects_size > 0);
bool can_go_right =
(category == 1 && index < chapter->questions_size - 1)
|| (category == 0 && (index < chapter->questions_size - 1
|| chapter->questions_size > 0));
#endif
bool can_go_left = (page_index > 0);
bool can_go_right = (page_index < doc->page_count - 1);
fkey_button(5, "PRÉC.", can_go_left ? C_BLACK : C_RGB(16, 16, 16));
fkey_button(6, "SUIV.", can_go_right ? C_BLACK : C_RGB(16, 16, 16));
@ -243,10 +213,8 @@ void read_chapter(struct chapter const *chapter, int category, int index)
struct dwindow old_w = dwindow_set(w);
int dh = w.bottom - w.top - 5;
int ch = render(w.left+3, w.top+3, w.right-w.left-5, dh,
category ? &chapter->questions[index]
: &chapter->subjects[index],
scroll);
int ch = render_page(w.left + 3, w.top + 3 - scroll,
w.right - w.left - 5, page, NULL, BRK_WRAP_WORD);
scrollbar(w.right-2, w.top+3, scroll, ch, dh);
maxscroll = (ch >= dh) ? ch - dh : 0;
@ -258,6 +226,7 @@ void read_chapter(struct chapter const *chapter, int category, int index)
if(key == KEY_EXIT)
break;
#if 0
if(key == KEY_F5 && can_go_left) {
index--;
if(index < 0) {
@ -274,6 +243,15 @@ void read_chapter(struct chapter const *chapter, int category, int index)
}
scroll = 0;
}
#endif
if(key == KEY_F5 && can_go_left) {
page_index--;
scroll = 0;
}
if(key == KEY_F6 && can_go_right) {
page_index++;
scroll = 0;
}
if(key == KEY_OPTN)
debug = !debug;

7
src/reader.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include "document.h"
/* Start reading specified document, starting at the provided page (in bounds
0 page_index < doc->page_count). */
void read_document(struct document const *document, int page_index);