More to come...
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

432 lines
30KB

  1. #ifndef CHAPTER_4_SOURCE
  2. #define CHAPTER_4_SOURCE
  3. #include <stdlib.h>
  4. #include "chapter_4.h"
  5. /*
  6. 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
  7. 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
  8. 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,
  9. 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
  10. 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
  11. thing well, and having few external variables, that won't be edited outside of that file is the best in my opinion.
  12. 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
  13. 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.
  14. 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
  15. 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
  16. 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
  17. game development, and they each have their own advantages and disadvantages.
  18. @C
  19. void preview_unhighlighted_text_file (char * text_file, int x, int y) {
  20. char * text_data;
  21. text_data = file_record (text_file);
  22. for (curses_active = 1; curses_active != 0; ) {
  23. curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL);
  24. curses_render_string (text_data, COLOUR_WHITE, EFFECT_NORMAL, x, x);
  25. curses_synchronize ();
  26. }
  27. text_data = deallocate (text_data);
  28. }
  29. @
  30. 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
  31. 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
  32. 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,
  33. Ada, C++, and maybe few more programming languages.
  34. 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
  35. 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
  36. 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,
  37. '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.
  38. */
  39. static int syntax_count = 0; // Number of previously defined syntax rules.
  40. static int * syntax_enrange = NULL; // Syntax rule can start with any character from 'syntax_begin' if this value is TRUE.
  41. static int * syntax_derange = NULL; // Syntax rule can start with any character from 'syntax_end' if this value is TRUE.
  42. static char * * syntax_begin = NULL; // Strings containing valid character (sub)sequence for begining the scan.
  43. static char * * syntax_end = NULL; // Strings containing valid character (sub)sequence for ending the scan.
  44. static char * syntax_escape = NULL; // Escape sequence for the rule, useful for line-breaks in C macros and line-based languages.
  45. static int * syntax_colour = NULL; // Colour for our token, these two could be completely independent, but I like to keep them here.
  46. static int * syntax_effect = NULL; // Effect for our token.
  47. /*
  48. 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,
  49. 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,
  50. 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
  51. of 'ATEXIT_MAX', which is at least 32 by some standards (POSIX).
  52. 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
  53. 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,
  54. 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,
  55. we're just deallocating (freeing) the memory, so we don't leak it and generate Valgrind warnings.
  56. */
  57. static void syntax_delete (void) { // We want to free used resources at certain point in our program, so we can declare this function as internal with 'static' keyword.
  58. int offset;
  59. if (syntax_count == 0) { // If syntax "library" wasn't used, we don't want to deallocate memory, we just return.
  60. return;
  61. }
  62. // We could reverse-loop through this without a local variable 'offset' using this approach, but I consider this bad for readability.
  63. // --syntax_count;
  64. // do {
  65. // syntax_begin [syntax_count] = deallocate (syntax_begin [syntax_count]);
  66. // syntax_end [syntax_count] = deallocate (syntax_end [syntax_count]);
  67. // } while (--syntax_count != -1);
  68. for (offset = 0; offset < syntax_count; ++offset) {
  69. syntax_begin [offset] = deallocate (syntax_begin [offset]); // Since these two are arrays of strings, we need to deallocate, otherwise we'll leak memory.
  70. syntax_end [offset] = deallocate (syntax_end [offset]); // We're basically freeing memory one by one string, you'll see below how we allocate it.
  71. }
  72. syntax_enrange = deallocate (syntax_enrange); // And now we're deallocating the rest of arrays, so no memory is leaked.
  73. syntax_derange = deallocate (syntax_derange);
  74. syntax_begin = deallocate (syntax_begin);
  75. syntax_end = deallocate (syntax_end);
  76. syntax_escape = deallocate (syntax_escape);
  77. syntax_colour = deallocate (syntax_colour);
  78. syntax_effect = deallocate (syntax_effect);
  79. syntax_count = 0; // Lastly, I like to do this, but you don't have to. We'll use it in later chapter tho.
  80. }
  81. /*
  82. 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
  83. be used with 'syntax_select' function to make our syntax highlighting. Lets explain what those function arguments do:
  84. @C
  85. // To see how I use it, look at 'syntax_highlight_*' functions below.
  86. int syntax_define (int enrange, // Strict matching of string 'begin' in buffer range if FALSE, any character matching if TRUE.
  87. int derange, // Strict matching of string 'end' in buffer range if FALSE, and again, any character matching if TRUE.
  88. char * begin, // String of array of characters to begin matching.
  89. char * end, // String of array of characters to end matching, I don't know why I explain these...
  90. char escape, // Escape character, useful for C preprocessor.
  91. int colour, // Colour.
  92. int effect); // Effect, I hate explaining the code when the identifiers are descriptive.
  93. @
  94. */
  95. int syntax_define (int enrange, int derange, char * begin, char * end, char escape, int colour, int effect) {
  96. if (syntax_count == 0) { // If our syntax isn't active, we'll execute this only once.
  97. atexit (syntax_delete); // Mark this function to be executed at program exit point.
  98. } // It's same if we use more 'syntax_highlight_*' functions.
  99. fatal_failure (begin == NULL, "syntax_define: Begin string is null pointer."); // I don't like checking for errors, but here, voila.
  100. fatal_failure (end == NULL, "syntax_define: End string is null pointer.");
  101. ++syntax_count;
  102. syntax_enrange = reallocate (syntax_enrange, syntax_count * (int) sizeof (* syntax_enrange)); // Now, we have block of memory reallocation for syntax data:
  103. syntax_derange = reallocate (syntax_derange, syntax_count * (int) sizeof (* syntax_derange));
  104. syntax_begin = reallocate (syntax_begin, syntax_count * (int) sizeof (* syntax_begin));
  105. syntax_end = reallocate (syntax_end, syntax_count * (int) sizeof (* syntax_end));
  106. syntax_escape = reallocate (syntax_escape, syntax_count * (int) sizeof (* syntax_escape));
  107. syntax_colour = reallocate (syntax_colour, syntax_count * (int) sizeof (* syntax_colour));
  108. syntax_effect = reallocate (syntax_effect, syntax_count * (int) sizeof (* syntax_effect));
  109. syntax_enrange [syntax_count - 1] = enrange; // In order to "make space" for our actual data.
  110. syntax_derange [syntax_count - 1] = derange;
  111. syntax_escape [syntax_count - 1] = escape;
  112. syntax_colour [syntax_count - 1] = colour;
  113. syntax_effect [syntax_count - 1] = effect;
  114. syntax_begin [syntax_count - 1] = allocate ((string_length (begin) + 1) * (int) sizeof (* * syntax_begin)); // We need to allocate enough memory for our strings now.
  115. syntax_end [syntax_count - 1] = allocate ((string_length (end) + 1) * (int) sizeof (* * syntax_end)); // Notice, we won't REallocate, just allocate!
  116. string_copy (syntax_begin [syntax_count - 1], begin); // Finally, we're copying our strings into syntax data.
  117. string_copy (syntax_end [syntax_count - 1], end);
  118. return (syntax_count - 1); // We return the index, but we won't use it in this chapter.
  119. }
  120. /*
  121. This is more complex, but if you use your eyes to look, your brain to comprehend and your heart to love, I'm sure that you'll understand it. Essentially, we are at some point in
  122. our text file (we're using pointer to type 'char' to indicate where that is), we try to match characters from that point onward with our previously defined syntax rules, either
  123. strictly, but using entire string (for keywords) or any character inside it (for numbers, identifiers, etc.). If we found it, we check next character depending on 'syntax_derange'
  124. and either finish first loop, or continue. After, we need to know the length of our matched syntax rule, that's it.
  125. */
  126. int syntax_select (char * string, int * length) {
  127. int offset, select;
  128. if (syntax_count == 0) { // Don't select without rules, return!
  129. return (syntax_count);
  130. }
  131. fatal_failure (string == NULL, "syntax_select: String is null.");
  132. fatal_failure (length == NULL, "syntax_select: Length is null.");
  133. // In this first part of the function, we need to check if our syntax rule has been detected at the string offset we've provided. We're looping defined syntax rules and
  134. // choosing whether to compare any of the characters, or full string, depending on 'syntax_enrange' value which is essentially boolean, true or false, which I express with
  135. // 'int' type for "type-safety simplicity". Keep in mind that we're not returning or modifying the string we provided, so it won't be null-terminated, instead I think
  136. // it's best to modify only variable 'length', hence we check with 'string_compare_limit' function.
  137. for (select = offset = 0; select != syntax_count; ++select) { // We're looping defined syntax rules:
  138. int begin = string_length (syntax_begin [select]);
  139. if (syntax_enrange [select] == FALSE) { // Choosing the comparisson based on 'syntax_enrange':
  140. if (syntax_derange [select] == FALSE) { // Either full string, or any character in it.
  141. if (string_compare_limit (string, syntax_begin [select], begin) == TRUE) { // Limiting our string comparisson.
  142. break; // If strings are same, we exit the loop.
  143. }
  144. } else {
  145. if ((string_compare_limit (string, syntax_begin [select], begin) == TRUE) // We need to see if we found our string, and:
  146. && (character_compare_array (string [offset + begin], syntax_end [select]) == TRUE)) { // If next character, after the string is in 'syntax_end'.
  147. break;
  148. } // Otherwise, we implcitly continue the loop.
  149. }
  150. } else { // Else, we compare any character.
  151. if (character_compare_array (string [offset], syntax_begin [select]) == TRUE) { // With our obviously named function...
  152. break; // We found it, exit the loop!
  153. } // If we didn't, just continue.
  154. }
  155. } // And now we have our 'select' value.
  156. // If there was no syntax rule detected, we need to return from a function, and increment the offset by setting variable 'length' to 1. If we don't increment it, at the
  157. // first unrecognized character, our second nested-loop inside function 'program_curses_view_file' would use uninitialized or zero value, depending on how we structured
  158. // our code before that. We also return 'syntax_count' as the syntax rule index, which is invalid, and would produce Valgrind warning if we didn't handle it. In my
  159. // unimportant opinion, this if statement is the ugliest part of the function.
  160. if (select >= syntax_count) { // If we didn't found our string, return.
  161. * length = 1;
  162. return (syntax_count);
  163. }
  164. // In this second part, we have our 'select' value, index of the syntax rule, and we want to know the 'length' value, by trying to match with 'syntax_end' string. We have
  165. // to again, separate two cases for matching any character or full string, except that we use it to determine its' match-length. Important difference is also that there's
  166. // special case where we have escape character matching, and where 'syntax_end' string is empty (but not NULL), so in that case we match only one character. We could have
  167. // nested loop there, and second loop would need goto statement to exit it, so we only use one loop.
  168. for (offset = 1; string [offset - 1] != '\0'; ++offset) { // Now, offset must be 1, and we loop...
  169. int end = string_length (syntax_end [select]);
  170. if (string [offset] == syntax_escape [select]) { // Here's our escape exception.
  171. ++offset;
  172. continue;
  173. }
  174. if (syntax_derange [select] == FALSE) { // Choosing what to compare, yet again...
  175. if (string_compare_limit (& string [offset], syntax_end [select], end) == TRUE) { // Again, we're comparing full string.
  176. * length = offset + end; // We found it, yaay!
  177. break;
  178. }
  179. } else {
  180. if (syntax_end [select] [0] == CHARACTER_NULL) { // And here's our empty string exception.
  181. * length = offset; // On that case, we break from loop.
  182. break;
  183. }
  184. if (character_compare_array (string [offset], syntax_end [select]) == TRUE) { // Otherwise, we compare to see if the end is near!
  185. * length = offset;
  186. break;
  187. }
  188. } // These two loops look similar, but no!
  189. } // And now we have our 'length' value.
  190. return (select); // Lastly, return syntax rule index.
  191. }
  192. /*
  193. Imagine my shock, we can now print coloured text, without regular expressions. Nothing much, we can print it without using 'curses_*' functions, but if we want to preview large,
  194. well more than 24 line of code, we'd want to scroll it or modify it if we're making a text editor, hence, using curses is good. Lets see how our "mini-main" subprogram-like
  195. function does its' work, and how we use 'syntax_*' functions in them, and I also want to make few syntax highlighting abstractions. We can call multiple 'syntax_highlight_*'
  196. functions, but it would mix the highlighting of those languages in that case, so we use 'syntax_delete' to reset it.
  197. Before we begin (Ada pun intended, remove this in final version), I won't (re)align 'separators' and 'keywords', because they fuck-up my comments, which I never write in my
  198. "official" programs. I write comments only here, to explain stuff in more details. Have fun... Oh, and type of variable 'keywords' an array of string pointers of automatic length,
  199. which we get with "sizeof (keywords) / sizeof (keywords [0])" part, for those keywords, it would be 32UL, and we cast it to integer. I use "long" comments outside of functions,
  200. and "short" comments inside them, while aligning them to the longest line of code, or current indentation level.
  201. */
  202. void syntax_highlight_c (void) {
  203. char * separators = ".,:;<=>+*-/%!&~^?|()[]{}'\" \t\r\n";
  204. char * keywords [] = {
  205. "register", "volatile", "auto", "const", "static", "extern", "if", "else",
  206. "do", "while", "for", "continue", "switch", "case", "default", "break",
  207. "enum", "union", "struct", "typedef", "goto", "void", "return", "sizeof",
  208. "char", "short", "int", "long", "signed", "unsigned", "float", "double"
  209. };
  210. int word;
  211. if (syntax_count != 0) { // If syntax was used, free it, then we can redefine them.
  212. syntax_delete (); // This way, we won't mix syntaces if we use this multiple times.
  213. }
  214. syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD); // Below, we're simply using our 'syntax_define' function.
  215. syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD); // I really don't think I need to explain those, so...
  216. syntax_define (FALSE, FALSE, "#", "\n", '\\', COLOUR_YELLOW, EFFECT_ITALIC);
  217. syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
  218. syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
  219. for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
  220. syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
  221. }
  222. syntax_define (TRUE, FALSE, "()[]{}", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
  223. syntax_define (TRUE, FALSE, ".,:;<=>+*-/%!&~^?|", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
  224. syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
  225. syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
  226. syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
  227. syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
  228. }
  229. /*
  230. Now, why the hell am I talking about Ada programming language again? Well, you see...
  231. Ada programming language was made in 1980, it was designed around the ideas of procedural programming, strict type safety and readability. As I mentioned before, the way I write C
  232. comes from the way I write Ada (in some cases, not always, as they are quite different languages). For example, you can see that Ada has more keywords and less operators than C,
  233. and is very strict language, while C is loose. Why is that interesting? Because Ada can interface with libraries written in C, and we'll use that in later chapters. You can spot
  234. how different they are, and still, you can write your project mixing those languages. That's the true power of C.
  235. I'll very briefly comment out what we're using in Ada, so you can see and compare syntax of C and Ada, and here's an example below:
  236. @Ada
  237. with ada.text_io; -- Our <stdio.h> in Ada basically.
  238. use ada.text_io; -- With this, we're automatically enabling a namespace.
  239. procedure hello_world is -- In C, this is 'extern void main (void)' so to say...
  240. begin -- This is Ada's way of saying '{'.
  241. put_line ("Hello " & "world!"); -- We're concatenating 2 strings and printing them.
  242. end hello_world; -- And this is Ada's way of saying '}'.
  243. @
  244. */
  245. void syntax_highlight_ada (void) {
  246. char * separators = ".,:;<=>#+*-/&|()\" \t\r\n";
  247. char * keywords [] = {
  248. "abort", "else", "new", "return", "abs", "elsif", "not", "reverse",
  249. "abstract", "end", "null", "accept", "entry", "select", "access", "of",
  250. "separate", "aliased", "exit", "or", "some", "all", "others", "subtype",
  251. "and", "for", "out", "array", "function", "at", "tagged", "generic",
  252. "package", "task", "begin", "goto", "pragma", "body", "private", "then",
  253. "type", "case", "in", "constant", "until", "is", "raise", "use",
  254. "if", "declare", "range", "delay", "limited", "record", "when", "delta",
  255. "loop", "rem", "while", "digits", "renames", "with", "do", "mod",
  256. "requeue", "xor", "procedure", "protected", "interface", "synchronized", "exception", "overriding",
  257. "terminate"
  258. };
  259. int word;
  260. if (syntax_count != 0) {
  261. syntax_delete ();
  262. }
  263. syntax_define (FALSE, FALSE, "--", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
  264. syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
  265. syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
  266. for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
  267. syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
  268. }
  269. syntax_define (TRUE, FALSE, "()#", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
  270. syntax_define (TRUE, FALSE, ".,:;<=>+*-/&|'", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
  271. syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
  272. syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
  273. syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
  274. syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
  275. }
  276. void program_curses_view_file (char * text_file, int x, int y) {
  277. char * text_data; // This local variable will hold our data.
  278. int reset_x, reset_y, from, to; // Since we're using curses, we want to reset the offset.
  279. curses_configure (); // Curses configuration, aka printing ugly text.
  280. switch (file_type (text_file)) { // Depending on our file extension, we select highlighting.
  281. case FILE_TYPE_C_SOURCE:
  282. case FILE_TYPE_C_HEADER:
  283. syntax_highlight_c ();
  284. break;
  285. case FILE_TYPE_ADA_BODY:
  286. case FILE_TYPE_ADA_SPECIFICATION:
  287. syntax_highlight_ada ();
  288. break;
  289. default:
  290. break;
  291. }
  292. text_data = file_record (text_file); // And, imagine, importing our file data into a buffer!
  293. reset_x = x;
  294. reset_y = y;
  295. from = 0;
  296. to = string_length (text_data);
  297. while (curses_active == TRUE) { // We enter our main subprogram loop.
  298. int offset, select, length;
  299. curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL); // We need to clear the screen buffer before rendering.
  300. x = reset_x;
  301. y = reset_y;
  302. select = syntax_count; // I intentionally set this to an invalid value.
  303. length = 0;
  304. if (curses_character == 'w') { // WORK IN PROGRESS
  305. from -= character_count (text_data, '\0', from, to, '\n');
  306. } else if (curses_character == 's') {
  307. from += character_count (text_data, '\0', from, to, '\n') + 1;
  308. }
  309. for (offset = from; offset < to; offset += length) { // And it's time to start rendering our file.
  310. int suboffset, colour, effect;
  311. select = syntax_select (& text_data [offset], & length); // Here we're evaluating variables 'select' and 'length'.
  312. // We can do the same thing in 2 lines of code, but it's less readable in my opinion, I prefer more verbose way below...
  313. // colour = (select >= syntax_count) ? COLOUR_WHITE : syntax_colour [select];
  314. // effect = (select >= syntax_count) ? EFFECT_NORMAL : syntax_effect [select];
  315. // Or, if you find this more intuitive:
  316. // colour = (select < syntax_count) ? syntax_colour [select] : COLOUR_WHITE;
  317. // effect = (select < syntax_count) ? syntax_effect [select] : EFFECT_NORMAL;
  318. // And I don't really care for negative values, but feel free to check for them, as they aren't even returned normally.
  319. if (select >= syntax_count) { // Here, we're handling error value of 'syntax_select'.
  320. colour = COLOUR_WHITE;
  321. effect = EFFECT_NORMAL;
  322. } else {
  323. colour = syntax_colour [select];
  324. effect = syntax_effect [select];
  325. }
  326. for (suboffset = 0; suboffset < length; ++suboffset) { // Sadly, we need to render them one by one character.
  327. if (text_data [offset + suboffset] == CHARACTER_LINE_FEED) { // Rendering of blank characters isn't counted, so:
  328. x = reset_x; // If there's a new line, we need to reset 'x' value.
  329. y += 1; // And increment 'y' value.
  330. } else if (text_data [offset + suboffset] == CHARACTER_TAB_HORIZONTAL) { // If there's a tab, we offset 'x' value by normal count.
  331. x += 8; // Normal indentation is 8-characters wide.
  332. } else {
  333. curses_render_character (text_data [offset + suboffset], colour, effect, x, y); // Finally, we can render it character by character.
  334. x += 1;
  335. }
  336. }
  337. if (y >= curses_screen_height) {
  338. break;
  339. }
  340. }
  341. curses_synchronize (); // Lastly, we synchronize our terminal.
  342. }
  343. text_data = deallocate (text_data); // And deallocate the memory when we exit the subprogram.
  344. }
  345. #endif