GNU   davin.50webs.com/research
Bringing to you notes for the ages

       Main Menu          Research Projects         Photo Album            Curriculum Vitae      The Greatest Artists
    Email Address       Computer Games          Web Design          Java Training Wheels      The Fly (A Story)   
  Political Activism   Scruff the Cat       My Life Story          Smoking Cessation          Other Links      
Debugging Macros     String Class I     Linked List System I Java for C Programmers Naming Convention
    String Class II         How I use m4              Strings III                 Symmetrical I/O             Linked Lists II     
Run-Time Type Info   Virtual Methods      An Array System        Science & Religion            Submodes       
  Nested Packages      Memory Leaks    Garbage Collection      Internet & Poverty      What is Knowledge?
Limits of Evolution   Emacs Additions      Function Plotter           Romantic Love        The Next Big Thing
    Science Fiction     Faster Compilation Theory of Morality         Elisp Scoping               Elisp Advice      
  S.O.G.M. Pattern       Safe Properties         School Bullying          Charisma Control          Life and Death    
     Splitting Java          Multiple Ctors       Religious Beliefs         Conversation 1           Conversation 2    
   J.T.W. Language    Emacs Additions II      Build Counter             Relation Plotter          Lisp++ Language  
  Memory Leaks II   Super Constructors CRUD Implementation Order a Website Form There Is An Afterlife
More Occam's Razor C to Java Translator Theory of Morality II


io.cc

    
//#include <stdio.h>
//#include <stdlib.h> // for EXIT_FAILURE
//#include <ctype.h>
//#include <string.h>
//#include <math.h>
#include "io.hh"

File_Writer cout(stdout);
File_Writer cerr(stderr);

///
/// TODO: String_Gulper should do
/// saw_line_end
///

// string CLASS: void string::worker_mutator_clone() { //cout << "*** inside worker_mutator_clone\n"; //PRINT(ptr->ref_count); if (ptr->ref_count > 1) { //cout << "*** worker_mutator_clone: cloning string\n"; ptr->ref_count--; ASSERT(ptr->ref_count >= 1); String_Internal* old_ptr = ptr; ptr = (String_Internal*)malloc(sizeof(String_Internal) + old_ptr->maxlength); ptr->init(old_ptr->maxlength); ptr->length = my_strncpy(ptr->array,old_ptr->array,old_ptr->length+1); ASSERT(ptr->length <= ptr->maxlength); ASSERT(ptr->array[ptr->length] == '\0'); } }
// STANDARD FOUR BASIC OPERATIONS: string::string() { ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH); ptr->init(String_Internal::MIN_LENGTH); } string::string(const string& s) { worker_new_up(s); ptr = s.ptr; } string& string::operator = (const string& s) { worker_new_up(s); // protect against s = s worker_current_down(); ptr = s.ptr; return *this; } string::~string() { worker_current_down(); }
// CONSTRUCTORS: string::string(char ch) { ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH); ptr->init(String_Internal::MIN_LENGTH); string_buffer result; result << ch; *this = result; } string::string(int i) { //cout << "** BEGIN string(int)\n"; //PRINT(i); ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH); ptr->init(String_Internal::MIN_LENGTH); string_buffer result; result << i; *this = result; //cout << "** END string(int)\n"; } string::string(double d) { ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH); ptr->init(String_Internal::MIN_LENGTH); string_buffer result; result << d; *this = result; } string::string(const char* s) { int len = strlen(s); int newlen = len + String_Internal::MIN_LENGTH;//max(len * 2, String_Internal::MIN_LENGTH); ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen); ptr->init(newlen); ptr->length = my_strncpy(ptr->array,s,len+1); ASSERT(ptr->array[ptr->length] == '\0'); ASSERT(ptr->length <= ptr->maxlength); }
string& string::operator = (const char* s) { worker_current_down(); int len = strlen(s); int newlen = len + String_Internal::MIN_LENGTH;//max(len * 2, String_Internal::MIN_LENGTH); ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen); ptr->init(newlen); ptr->length = my_strncpy(ptr->array,s,len+1); ASSERT(ptr->length <= ptr->maxlength); ASSERT(ptr->array[ptr->length] == '\0'); return *this; } string& string::operator += (const string& s) { int len1 = ptr->length; int len2 = s.ptr->length; if (len1 + len2 <= ptr->maxlength) { worker_mutator_clone(); ptr->length += my_strncpy(ptr->array + ptr->length, s.ptr->array,len2+1); ASSERT(ptr->length <= ptr->maxlength); ASSERT(ptr->array[ptr->length] == '\0'); } else { string_buffer sb; sb << ptr->array; sb << s.ptr->array; *this = sb; } return *this; } string& string::operator += (const char* s) { int len1 = ptr->length; int len2 = strlen(s); if (len1 + len2 <= ptr->maxlength) { worker_mutator_clone(); ptr->length += my_strncpy(ptr->array + ptr->length, s, len2+1); ASSERT(ptr->length <= ptr->maxlength); ASSERT(ptr->array[ptr->length] == '\0'); } else { string_buffer sb; sb << ptr->array; sb << s; *this = sb; } return *this; } // string::operator const char*() const // { // return ptr->array; // } char* string::char_star(int mem_size, char* s) const { my_strncpy(s,ptr->array,min(mem_size,ptr->length+1)); return s; }
char string::get_char_at(int index) const { // if (index < 0 || index >= ptr->length) { // std::cout << "** assertion failed" << std::endl; // } ASSERT(0 <= index); ASSERT(index < ptr->length); return ptr->array[index]; } int string::get_length() const { return ptr->length; }
int my_strncpy(char* to, const char* from, int mem_size) { if (from == null) { // case of null creates an empty string... // Better than crashing... *to = '\0'; return 0; } int i = 0; int max = mem_size-1; // extra 1 for the '\0' while ((*(to++) = *(from++))) { if (++i >= max) { *to = '\0'; return max; } } return i; } // inline char debug_hex_value(int i) // { // char result; // if (i <= 9) { // result = '0' + (char)i; // } // else { // result = 'a' + (char)(i-10); // } // return result; // } // void string::debug_print() // { // // for (int i=0; i< 16; i++) { // // cout << "(" << i << " " << debug_hex_value(i) << ") "; // // } // // cout << "\n"; // std::cout << "("; // //PRINT(ptr->length); // for (int i=0; i<ptr->maxlength; i++) { // unsigned char ch = ptr->array[i]; // string s = string() + '\'' + (char)ch + '\''; // if (ch == '\0') { // s = "'\\0'"; // } // else if (ch >= 128 || ch < ' ') { // s = (string() + "0x" + // debug_hex_value(ch >> 4) + // debug_hex_value(ch & 0xf)); // } // if (i < ptr->length) { // std::cout << "(" << i << " " << s.const_char_star() << ") "; // } // else { // std::cout << "[" << i << " " << s.const_char_star() << "] "; // } // } // std::cout << ")\n"; // }
// string EXTRA FUNCTIONS: string substring(const string& s, int start, int stop) { int len = s.get_length(); if (start < 0) { start += len; } if (stop <= 0) { stop += len; } // start = start % len; // stop = stop % len; // if (start < 0) { // start += len; // } // if (stop < 0) { // stop += len; // } //PRINT(start); //PRINT(stop); ASSERT(start >= 0); ASSERT(start < len); ASSERT(stop >= 0); ASSERT(stop <= len); string_buffer answer; for (int i=start; i<stop; i++) { answer << s.get_char_at(i); } // for (int i=start; i != stop; i=((i+1) % len)) { // answer << s.get_char_at(i); // } return answer; } string quoted(const string& s) { int len = s.get_length(); string_buffer result; result << '"'; for (int i=0; i<len; i++) { char ch = s.get_char_at(i); if (ch == '"') { result << '\\' << '"'; } else if (ch == '\\') { result << '\\' << '\\'; } else if (ch == '\t') { result << '\\' << 't'; } else if (ch == '\n') { result << '\\' << 'n'; } else if (ch == '\r') { result << '\\' << 'r'; } else { result << ch; } } result << '"'; return result; } string reversed(const string& s) { string_buffer result; for (int i=s.get_length()-1; i>=0; i--) { result << s.get_char_at(i); } return result; } string capitalised(const string& s) { bool last_was_space = true; string_buffer result; int len = s.get_length(); for (int i=0; i<len; i++) { char ch = s.get_char_at(i); if (last_was_space) { ch = toupper(ch); } if (ch == ' ') { last_was_space = true; } else { last_was_space = false; } result << ch; } return result; } int index_of(const string& s, const string& key) { int slen = s.get_length(); int klen = key.get_length(); for (int i=0; i<slen; i++) { bool found = true; for (int j=0; (i+j < slen) && (j<klen); j++) { if (key.get_char_at(j) != s.get_char_at(i+j)) { found = false; break; } } if (found) { return i; } } return -1; }
// STRING_BUFFER FUNCTIONS: void string_buffer::set_char_at(int i, char ch) { worker_mutator_clone(); ASSERT(i < ptr->length); ASSERT(i >= 0); ptr->array[i] = ch; } string_buffer& string_buffer::operator << (char ch) { assert(ch != 0); this->worker_mutator_clone(); if (this->ptr->length < this->ptr->maxlength) { this->ptr->array[this->ptr->length++] = ch; this->ptr->array[this->ptr->length] = '\0'; } else { String_Internal* old_ptr = this->ptr; int len = old_ptr->length; int newlen = 2 * len; this->ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen); this->ptr->init(newlen); my_strncpy(this->ptr->array,old_ptr->array,len+1); this->ptr->length = len; ASSERT(this->ptr->array[this->ptr->length] == '\0'); this->ptr->array[this->ptr->length++] = ch; this->ptr->array[this->ptr->length] = '\0'; free(old_ptr); //cout << "**1 Extending array from " << len << " to " << newlen << endl; } return *this; } string_buffer& string_buffer::operator << (int i) { this->worker_mutator_clone(); char tmp[100]; sprintf(tmp,"%d",i); *this << tmp; return *this; } string_buffer& string_buffer::operator << (double d) { this->worker_mutator_clone(); char tmp[100]; sprintf(tmp,"%g",d); *this << tmp; return *this; } string_buffer& string_buffer::operator << (const char* s) { // while (*s != '\0') { // sb << *s; // s++; // } // return sb; //cout << "*** BEGIN operator << (string_buffer&,const char*)\n"; bool dynamically_allocated_s = false; int len = strlen(s); if (len == 0) { return *this; } if (this->ptr->array <= s && s <= this->ptr->array + len) { /// /// Handles SPECIAL case like sb << sb /// char* accumulator = new char[len+1]; strcpy(accumulator, s); s = accumulator; dynamically_allocated_s = true; } // BUGGER! this BLOODY WORKS! //PRINT(this->ptr->array[1000]); this->worker_mutator_clone(); while (*s != '\0') { if (this->ptr->length < this->ptr->maxlength) { //cout << "*** copying from *s='" << *s // << "' to this->ptr->array[" << this->ptr->length << "]\n"; this->ptr->array[this->ptr->length++] = *(s++); } else { //cout << "*** resizing array from this->ptr->length=" << this->ptr->length // << " newlen=" << (this->ptr->length * 2) << '\n'; this->ptr->array[this->ptr->length] = '\0'; String_Internal* old_ptr = this->ptr; int len = old_ptr->length; int newlen = 2 * len; this->ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen); // init not called here, so all fields must be set to non-garbage // values this->ptr->ref_count = 1; this->ptr->length = len; this->ptr->maxlength = newlen; my_strncpy(this->ptr->array,old_ptr->array,len+1); ASSERT(this->ptr->array[this->ptr->length] == '\0'); //PRINT(len); //PRINT(this->ptr->array); //PRINT(old_ptr->array); /// /// mutator clone allows us to free old_ptr /// free(old_ptr); //cout << "**2 Extending array from " << len << " to " << newlen << endl; } } this->ptr->array[this->ptr->length] = '\0'; if (dynamically_allocated_s) { delete s; } //cout << "*** END operator << (string_buffer&,const char*)\n"; return *this; }
// FILE_GULPER CLASS: File_Gulper::File_Gulper(string file_name) : Gulper(file_name) { const char* file_name_const_char_star = file_name.const_char_star(); this->f = fopen(file_name_const_char_star,"rb"); ASSERT_INFO(this->f != null, "Error opening file \"" + file_name + "\" for reading"); //if (this->f == null) { // #ifdef ALLEGRO_MESSAGE_AND_EXIT // ALLEGRO_MESSAGE_AND_EXIT(("*** File Error!\n\n" // PLEASE_EMAIL_DAVIN // "*** LOCATION: (%s:%d)\n\n" // "*** TEST: Unable to open file \"%s\" for reading\n\n", // __FILE__,__LINE__, // file_name_const_char_star)); // #else /* !ALLEGRO_MESSAGE_AND_EXIT */ // printf("*** Unable to open file \"%s\" for reading\n", file_name_const_char_star); // exit(EXIT_FAILURE); // #endif /* ALLEGRO_MESSAGE_AND_EXIT */ //} this->saw_line_end = false; gulp_char(); } void File_Gulper::gulp_char() { current_char = fgetc(f); if (saw_line_end) { saw_line_end = false; file_line++; } if (current_char == '\n') { saw_line_end = true; } }
// STRING_GULPER CLASS: String_Gulper::String_Gulper(string s) : Gulper("<string>") { this->internal = s; this->index = -1; this->saw_line_end = false; gulp_char(); } void String_Gulper::gulp_char() { index++; if (index >= internal.get_length()) { index = internal.get_length(); current_char = EOF; return; } current_char = internal.get_char_at(index); if (saw_line_end) { saw_line_end = false; file_line++; } if (current_char == '\n') { saw_line_end = true; } }
// READER_CORE CLASS: string Reader_Core::scan_identifier() { //ASSERT(isalpha(gulper_get_char())); string_buffer s; //s << '<'; s << (char)(gulper->get_current_char()); gulper->gulp_char(); for (;;) { int ch = gulper->get_current_char(); if (ch == EOF || ch == ';' || ch == '"' || isspace(ch) || ch == '(' || ch == ')' || ch == '\'') { return s; } s << (char)ch; gulper->gulp_char(); } return s; } string Reader_Core::scan_quoted_string() { ASSERT(gulper->get_current_char() == '"'); gulper->gulp_char(); string_buffer s; while (gulper->get_current_char() != '"') { char duplicate = gulper->get_current_char(); if (gulper->get_current_char() == '\\') { gulper->gulp_char(); switch (gulper->get_current_char()) { case 'n': duplicate = '\n'; break; case 't': duplicate = '\t'; break; case '\\': duplicate = '\\'; break; case '\"': duplicate = '\"'; break; case EOF: break; default: error_and_halt(string() + "Unrecognised escape sequence: \\" + (char)gulper->get_current_char()); } } if (gulper->get_current_char() == EOF) { error_and_halt(string() + "End of file before end of string: " + quoted(s)); break; } s << duplicate; gulper->gulp_char(); } ASSERT(gulper->get_current_char() == '"'); gulper->gulp_char(); return s; } void Reader_Core::skip_whitespace() { // cout << "--- beginning of skip_whitespace ---\n"; bool found_whites = true; do { found_whites = false; if (isspace(gulper->get_current_char())) { found_whites = true; while (isspace(gulper->get_current_char())) { gulper->gulp_char(); } } if (gulper->get_current_char() == ';') { found_whites = true; int count=0; for (;;) { if (gulper->get_current_char() == '\n' || gulper->get_current_char() == EOF) break; gulper->gulp_char(); ASSERT(++count < 1000); } } } while (found_whites); // cout << "--- end of skip_whitespace ---\n"; } class Io_Worker { public: static bool is_hex_digit(char ch) { ch = tolower(ch); return ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')); } static int hex_digit_value(char ch) { ch = tolower(ch); if (ch >= '0' && ch <= '9') { return ch - '0'; } else if (ch >= 'a' && ch <= 'f') { return ch - 'a' + 10; } else { ASSERT(false); } } }; int Reader_Core::scan_int() { // cout << "--- beginning of scan_int ---\n"; ASSERT(gulper->get_current_char() != '-'); int answer = 0; if (gulper->get_current_char() == '0') { gulper->gulp_char(); if (tolower(gulper->get_current_char()) == 'x') { gulper->gulp_char(); while (Io_Worker::is_hex_digit(gulper->get_current_char())) { answer = answer * 16 + Io_Worker::hex_digit_value(gulper->get_current_char()); gulper->gulp_char(); } return answer; } } while (gulper->get_current_char() == '0') { gulper->gulp_char(); } while (isdigit(gulper->get_current_char())) { answer = answer * 10 + (gulper->get_current_char() - '0'); gulper->gulp_char(); } // cout << "--- end of scan_int ---\n"; return answer; } double Reader_Core::scan_fraction() { double answer = 0; for (int k=10; isdigit(gulper->get_current_char()); k*=10) { answer = answer + (gulper->get_current_char() - '0') / ((double)k); gulper->gulp_char(); } return answer; } void Reader_Core::next_token() { // cout << "--- beginning of next_token ---\n"; skip_whitespace(); // cout << "Deciding on gulper->get_current_char() = " << gulper->get_current_char() // << " ASCII=" << (char)gulper->get_current_char() << " \n"; bool minus_flag = false; while (gulper->get_current_char() == '-') { gulper->gulp_char(); minus_flag = !minus_flag; } if (isdigit(gulper->get_current_char())) { // cout << "next_token: is a digit!\n"; last_int = scan_int(); if (minus_flag) { last_int = (-last_int); } if (gulper->get_current_char() == '.') { /* scan the fraction */ gulper->gulp_char(); last_double = last_int + (minus_flag? -1:1) * scan_fraction(); if (tolower(gulper->get_current_char()) == 'e') { /* scan the exponent */ gulper->gulp_char(); ASSERT(gulper->get_current_char() == '+' || gulper->get_current_char() == '-'); bool exponent_negative; if (gulper->get_current_char() == '-') { exponent_negative = true; } else { exponent_negative = false; } gulper->gulp_char(); int exponent = scan_int(); if (exponent_negative) { exponent = (-exponent); } //const double ln10 = log((float)10); last_double = last_double * pow(10, exponent); } current_type = Type_Double; } else { current_type = Type_Int; } } else if (gulper->get_current_char() == '"') { current_type = Type_String; last_string = scan_quoted_string(); } else if ((gulper->get_current_char() == '(') || (gulper->get_current_char() == ')') || (gulper->get_current_char() == '\'')) { current_type = Type_Char; last_char = gulper->get_current_char(); gulper->gulp_char(); } else if (gulper->get_current_char() == EOF) { current_type = Type_Eof; } else { // NO OTHER POSSIBILTIES: current_type = Type_Ident; last_ident = scan_identifier(); } // cout << "--- end of next_token ---\n"; } void Reader_Core::error_and_halt(string msg) { (void)msg; ERROR(string() + "Parse error reading file (" + get_file_name() + ":" + get_file_line() + ")" + "\n\n Error = " + msg + "\n\n"); } string Reader_Core::debug_say_symbol() { //cout << "*** debug_say_symbol = "; string_buffer s; switch (current_type) { case Type_Char: s << "(char, '" << last_char << "' (" << (int)last_char << "))"; break; case Type_Int: s << "(int, " << last_int << ")"; break; case Type_Double: s << "(double, " << last_double << ")"; break; case Type_Ident: s << "(identifier, " << last_ident << ")"; break; case Type_String: s << "(string, " << quoted(last_string) << ")"; break; case Type_Eof: s << "(eof, gulper->get_current_char() = " << gulper->get_current_char() << ", type = " << current_type << ")"; break; default: s << "(UNKNOWN! gulper->get_current_char() = " << gulper->get_current_char() << ", type = " << current_type << ")"; } //s << "\n"; return s; } void Reader_Core::wrong_type(string tname) { cout << "Coding error in Reader_Core call: tried to get_" << tname << ", but gulper->get_current_char() token = " << debug_say_symbol() << "\n"; exit(EXIT_FAILURE); }
// READER CLASS: Reader& operator >> (Reader& r, const char& expected_char) { if ((expected_char != '(') && (expected_char != ')') && (expected_char != '\'')) { r.error_and_halt(string() + "One of '(',')','\\'' must be passed to\n" + "operator >> (Reader& r, const char& expected_char)\n" + "expected_char='" + expected_char + "'"); } if (!r.currently_char()) { r.error_and_halt(string() + "A char '" + expected_char + "' was expected but looking at " + r.debug_say_symbol()); } if (r.peek_char() != expected_char) { r.error_and_halt(string() + "Expecting char '" + expected_char + "' but looking at char '" + r.peek_char() + '\''); } r.next_token(); return r; } Reader& operator >> (Reader& r, const char* expected_string) { const char* ptr = expected_string; bool found_space = false; bool found_illegal = false; string_buffer found_illegal_char; while ((*ptr) != '\0') { if (isspace(*ptr)) { found_space = true; break; } else if (*ptr == '(') { found_illegal = true; found_illegal_char << "'('"; break; } else if (*ptr == ')') { found_illegal = true; found_illegal_char << "')'"; break; } else if (*ptr == '\'') { found_illegal = true; found_illegal_char << "'\\''"; break; } ptr++; } if (found_space) { r.error_and_halt(string() + "Whitespace found in string: " + quoted(expected_string)); } if (found_illegal) { r.error_and_halt(string() + "Illegal character " + found_illegal_char + " found in string: " + quoted(expected_string)); } if (!r.currently_identifier()) { r.error_and_halt(string() + "An identifier \"" + expected_string + "\" was expected but looking at " + r.debug_say_symbol()); } if (!r.peek_identifier().equals(expected_string)) { r.error_and_halt(string() + "Expecting identifier " + quoted(expected_string) + " but looking at identifier " + quoted(r.peek_identifier())); } r.next_token(); return r; } Reader& operator >> (Reader& r, char& ch) { if (!r.currently_char()) { r.error_and_halt("A char was expected but looking at " + r.debug_say_symbol()); } ch = r.peek_char(); r.next_token(); return r; } Reader& operator >> (Reader& r, int& i) { if (!r.currently_int()) { r.error_and_halt("An int was expected but looking at " + r.debug_say_symbol()); } i = r.peek_int(); r.next_token(); return r; } Reader& operator >> (Reader& r, double& d) { if (!r.currently_double()) { r.error_and_halt("A double was expected but looking at " + r.debug_say_symbol()); } d = r.peek_double(); r.next_token(); return r; } Reader& operator >> (Reader& r, string& s) { if (!r.currently_identifier()) { r.error_and_halt("An identifier was expected but looking at " + r.debug_say_symbol()); } s = r.peek_identifier(); r.next_token(); return r; } string Reader::gulp_quoted_string() { ASSERT(currently_string()); string s = peek_string(); next_token(); return s; }
Back
| Main Menu | Research Projects | Photo Album | Curriculum Vitae | The Greatest Artists |
| Email Address | Computer Games | Web Design | Java Training Wheels | The Fly (A Story) |
| Political Activism | Scruff the Cat | My Life Story | Smoking Cessation | Other Links |
| Debugging Macros | String Class I | Linked List System I | Java for C Programmers | Naming Convention |
| String Class II | How I use m4 | Strings III | Symmetrical I/O | Linked Lists II |
| Run-Time Type Info | Virtual Methods | An Array System | Science & Religion | Submodes |
| Nested Packages | Memory Leaks | Garbage Collection | Internet & Poverty | What is Knowledge? |
| Limits of Evolution | Emacs Additions | Function Plotter | Romantic Love | The Next Big Thing |
| Science Fiction | Faster Compilation | Theory of Morality | Elisp Scoping | Elisp Advice |
| S.O.G.M. Pattern | Safe Properties | School Bullying | Charisma Control | Life and Death |
| Splitting Java | Multiple Ctors | Religious Beliefs | Conversation 1 | Conversation 2 |
| J.T.W. Language | Emacs Additions II | Build Counter | Relation Plotter | Lisp++ Language |
| Memory Leaks II | Super Constructors | CRUD Implementation | Order a Website Form | There Is An Afterlife |
| More Occam's Razor | C to Java Translator | Theory of Morality II
Last modified: Sun Sep 25 16:09:08 NZDT 2016
Best viewed at 800x600 or above resolution.
© Copyright 1999-2016 Davin Pearson.
Please report any broken links to