瀏覽代碼

Explained parts of chapter 4...

master
父節點
當前提交
715063ebc6
共有 1 個檔案被更改,包括 66 行新增32 行删除
  1. +66
    -32
      chapter/chapter_4.c

+ 66
- 32
chapter/chapter_4.c 查看文件

@@ -15,14 +15,16 @@ It is distributed in the hope that it will be useful or harmful, it really depen
Of course, we could just write something like 'preview_unhighlighted_text_file' function (name is obviously a joke), but this would stylize (apply colour and effect character
attributes) to our entire text file. When we're writing programs, syntax highlighting makes a lot of difference to readability, the same way the code formatting does, and initial
program design structure. If you use a lot of external variables everywhere, the entire programs starts to be messy or difficult to maintain, write and debug (or both). However,
if you use a lot of variables, and you pass each of them separately into functions, or if you have one huge monolithic structure (this time literal 'struct), you aren't doing
if you use a lot of variables, and you pass each of them separately into functions, or if you have one huge monolithic structure (this time literal 'struct'), you aren't doing
much better, except the compiler will have easier time to optimize your code, even tho that kind of code becomes pain to write. So, having few external functions, that do one
thing well, and having few external variables, that won't be edited outside of that file is the best in my opinion.

Your function calls won't be long, if you don't want to make those external ("global") variables visible to some other file, just move them to C source file instead of C header
file, and redeclare them as 'static', making them internal variables. Keep in mind, you have to use brain more in that case, and think about what you're modifying, where and why.
Well, if you want to write programs, and not to think about them, just close this and read Jin Ping Mei instead or something. There's no cheatsheet for making good programs, you
choose your constraints, your program design structure, and you start working on it.
choose your constraints, your program design structure, and you start working on it. Sometimes you'll have to choose between performance, maintainability, simplicity or low memory
usage, and even if you are smart and manage to get three of them to work out in your project, fourth won't. I can't teach you how to choose, maybe you want to learn embedded or
game development, and they each have their own advantages and disadvantages.

@C
void preview_unhighlighted_text_file (char * text_file, int x, int y) {
@@ -41,10 +43,15 @@ void preview_unhighlighted_text_file (char * text_file, int x, int y) {
}
@

So, lets write very basic C programming language syntax highlighting, explain how can we easily do it in little more than 100 lines of (scarily verbose and nicely aligned) code
So, lets write very basic C programming language syntax highlighting, explain how can we easily do it in little more than 150 lines of (scarily verbose and nicely aligned) code
and why we don't need regular expressions for it. You can use these 'syntax_*' functions to tokenize some source code, highlight the syntax of it or something else that I didn't
even think about if you're creative. Of course, we can use it to highlight syntax of some other programming language, not only C, and we'll use it later to highlight assembly,
Ada, C++, and maybe few more programming languages.

Note that regular expressions are way more powerful way of achieving the same results, and doing even more things like replacing some parts of the strings. This is simple solution
for simple problem. We'll define some internal variables below, functions 'syntax_delete' (that'll be called automatically when we exit the program), 'syntax_define' to make rules
about our character and string matching and 'syntax_select' to process our text file (which is just an array of character, also known as, low and behold, a string). Last function,
'syntax_select', will return index of the syntax rule that matches to our offset in string and store size of the match in 'length' variable, we'll look into it.
*/

static int syntax_count = 0; // Number of previously defined syntax rules.
@@ -57,10 +64,22 @@ static char * syntax_escape = NULL; // Escape sequence for the rule, usefu
static int * syntax_colour = NULL; // Colour for our token, these two could be completely independent, but I like to keep them here.
static int * syntax_effect = NULL; // Effect for our token.

/*
Lets go in more details about how this function works. Standard library function 'atexit' will take as an argument function pointer of form 'extern void name (void)' that will,
imagine my shock, be executed at the exit point of our little program. We can make mistakes using it, if we don't think while we write our programs, the error will be obvious,
memory will be leaked or double-freed, Valgrind will detect it, we'd fix it. Also keep in mind, you can't have too much functions executed at the end, you can check for the value
of 'ATEXIT_MAX', which is at least 32 by some standards (POSIX).

So the goal is, we think a bit more when we structure our program, and we don't worry about if we forgot to deinitialize something. We'll reuse this in chapter five, but use
contra-approach, where we want to explicitly deinitialize syntax. If you take a look in the next function, 'syntax_define', you'll see that we'll use 'atexit' function only once,
when 'syntax_active' is FALSE, we'll change it to true, so 'atexit' won't be executed every time we call 'syntax_define', which is good. Lastly, in the 'syntax_delete' function,
we're just deallocating (freeing) the memory, so we don't leak it and generate Valgrind warnings.
*/

static void syntax_delete (void) {
int offset;

if (syntax_active == FALSE) {
if (syntax_active == FALSE) { // If 'syntax' subprogram wasn't active, we don't want to deallocate memory, we just return.
return;
}

@@ -72,11 +91,11 @@ static void syntax_delete (void) {
// } while (--syntax_count != -1);

for (offset = 0; offset < syntax_count; ++offset) {
syntax_begin [offset] = deallocate (syntax_begin [offset]);
syntax_end [offset] = deallocate (syntax_end [offset]);
syntax_begin [offset] = deallocate (syntax_begin [offset]); // Since these two are arrays of strings, we need to deallocate, otherwise we'll leak memory.
syntax_end [offset] = deallocate (syntax_end [offset]); // We're basically freeing memory one by one string, you'll see below how we allocate it.
}

syntax_enrange = deallocate (syntax_enrange);
syntax_enrange = deallocate (syntax_enrange); // And now we're deallocating the rest of arrays, so no memory is leaked.
syntax_derange = deallocate (syntax_derange);
syntax_begin = deallocate (syntax_begin);
syntax_end = deallocate (syntax_end);
@@ -84,23 +103,38 @@ static void syntax_delete (void) {
syntax_colour = deallocate (syntax_colour);
syntax_effect = deallocate (syntax_effect);

syntax_active = FALSE;
syntax_active = FALSE; // Lastly, I like to do this, but you don't have to. We'll use it in chapter five tho.
syntax_count = 0;
}

/*
In 'syntax_define' function we're reallocating (enlarging) memory, effectively adding a new element into our arrays, and assigning or copying data to them. These syntax rules will
be used with 'syntax_select' function to make our syntax highlighting. Lets explain what those function arguments do:

@C
static int syntax_define (int enrange, // Strict matching of string 'begin' in buffer range if FALSE, any character matching if TRUE.
int derange, // Strict matching of string 'end' in buffer range if FALSE, and again, any character matching if TRUE.
char * begin, // String of array of characters to begin matching.
char * end, // String of array of characters to end matching, I don't know why I explain these...
char escape, // Escape character, useful for C preprocessor.
int colour, // Colour.
int effect); // Effect, I hate explaining the code when the identifiers are descriptive.
@
*/

static int syntax_define (int enrange, int derange, char * begin, char * end, char escape, int colour, int effect) {
if (syntax_active == FALSE) {
syntax_active = TRUE;
if (syntax_active == FALSE) { // If our syntax data isn't active, we'll execute this once.
syntax_active = TRUE; // Now we set it to active state, and:

atexit (syntax_delete);
atexit (syntax_delete); // Mark this function to be executed at program exit point.
}

fatal_failure (begin == NULL, "syntax_define: Begin string is null pointer.");
fatal_failure (begin == NULL, "syntax_define: Begin string is null pointer."); // I don't like checking for errors, but here, voila.
fatal_failure (end == NULL, "syntax_define: End string is null pointer.");

++syntax_count;

syntax_enrange = reallocate (syntax_enrange, syntax_count * (int) sizeof (* syntax_enrange));
syntax_enrange = reallocate (syntax_enrange, syntax_count * (int) sizeof (* syntax_enrange)); // Now, we have block of memory reallocation for syntax data:
syntax_derange = reallocate (syntax_derange, syntax_count * (int) sizeof (* syntax_derange));
syntax_begin = reallocate (syntax_begin, syntax_count * (int) sizeof (* syntax_begin));
syntax_end = reallocate (syntax_end, syntax_count * (int) sizeof (* syntax_end));
@@ -108,21 +142,25 @@ static int syntax_define (int enrange, int derange, char * begin, char * end, ch
syntax_colour = reallocate (syntax_colour, syntax_count * (int) sizeof (* syntax_colour));
syntax_effect = reallocate (syntax_effect, syntax_count * (int) sizeof (* syntax_effect));

syntax_enrange [syntax_count - 1] = enrange;
syntax_enrange [syntax_count - 1] = enrange; // In order to "make space" for our actual data.
syntax_derange [syntax_count - 1] = derange;
syntax_escape [syntax_count - 1] = escape;
syntax_colour [syntax_count - 1] = colour;
syntax_effect [syntax_count - 1] = effect;

syntax_begin [syntax_count - 1] = allocate ((string_length (begin) + 1) * (int) sizeof (* * syntax_begin));
syntax_end [syntax_count - 1] = allocate ((string_length (end) + 1) * (int) sizeof (* * syntax_end));
syntax_begin [syntax_count - 1] = allocate ((string_length (begin) + 1) * (int) sizeof (* * syntax_begin)); // We need to allocate enough memory for our strings now.
syntax_end [syntax_count - 1] = allocate ((string_length (end) + 1) * (int) sizeof (* * syntax_end)); // Notice, we won't REallocate, just allocate!

string_copy (syntax_begin [syntax_count - 1], begin);
string_copy (syntax_begin [syntax_count - 1], begin); // Finally, we're copying our strings into syntax data.
string_copy (syntax_end [syntax_count - 1], end);

return (syntax_count - 1);
return (syntax_count - 1); // We return the index, but we won't use it in this chapter.
}

/*
TODO: WE LEFT OF HERE...
*/

static int syntax_select (char * string, int * length) {
int offset, subset, select;

@@ -185,7 +223,7 @@ static int syntax_select (char * string, int * length) {
return (select);
}

static void syntax_highlight_c (void) {
void preview_c_file (char * text_file, int width, int height, int x, int y) {
char * separators = ".,:;<=>+*-/%!&~^?|()[]{}'\" \t\r\n";

char * keywords [] = {
@@ -195,7 +233,12 @@ static void syntax_highlight_c (void) {
"char", "short", "int", "long", "signed", "unsigned", "float", "double"
};

int word;
char * text_data;

int word, reset_x, reset_y;

(void) width;
(void) height;

syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD);
syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
@@ -214,21 +257,12 @@ static void syntax_highlight_c (void) {
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
}

void preview_c_file (char * text_file, int width, int height, int x, int y) {
char * text_data;

int reset_x = x;
int reset_y = y;

(void) width;
(void) height;

syntax_highlight_c ();

text_data = file_record (text_file);

reset_x = x;
reset_y = y;

for (curses_active = 1; curses_active != 0; ) {
int offset, select, length;



Loading…
取消
儲存