See More

// markSystem.cpp - annotates the dictionary with what words/concepts are active in the current sentence #include "common.h" #ifdef INFORMATION For every word in a sentence, the word knows it can be found somewhere in the sentence, and there is a 64-bit field of where it can be found in that sentence. The field is in a hashmap and NOT in the dictionary word, where it would take up excessive memory. Adjectives occur before nouns EXCEPT: 1. object complement (with some special verbs) 2. adjective participle (sometimes before and sometimes after) In a pattern, an author can request: 1. a simple word like bottle 2. a form of a simple word non - canonicalized like bottled or apostrophe bottle 3. a WordNet concept like bottle~1 4. a set like ~dead or : dead For #1 "bottle", the system should chase all upward all sets of the word itself, and all WordNet parents of the synset it belongs to and all sets those are in. Marking should be done for the original and canonical forms of the word. For #2 "bottled", the system should only chase the original form. For #3 "bottle~1", this means all words BELOW this in the wordnet hierarchy not including the word "bottle" itself.This, in turn, means all words below the particular synset head it corresponds to and so instead becomes a reference to the synset head : (char*)"0173335n" or some such. For #4 "~dead", this means all words encompassed by the set ~dead, not including the word ~dead. So each word in an input sentence is scanned for marking. the actual word gets to see what sets it is in directly. Thereafter the system chases up the synset hierarchy fanning out to sets marked from synset nodes. #endif #define GENERIC_MEANING 0 // not a specific meaning of the word int verbwordx = -1; int uppercaseFind = -1; // unknown static bool failFired = false; bool trustpos = false; int marklimit = 0; static int freeTriedList = 0; std::map triedData; // per volley index into heap space static STACKREF wordlist = NULL; static HEAPREF pendingConceptList = NULL; static int MarkSetPath(int depth, int exactWord, MEANING M, int start, int end, unsigned int level, int kind); // walks set hierarchy // mark debug tracing bool showMark = false; static unsigned int markLength = 0; // prevent long lines in mark listing trace #define MARK_LINE_LIMIT 80 int upperCount, lowerCount; ExternalTaggerFunction externalPostagger = NULL; char unmarked[MAX_SENTENCE_LENGTH]; // can completely disable a word from mark recognition /**************************************/ /* Word Reference in Sentence system */ /**************************************/ // Wheredata is a 64bit tried by meaning field (aligned) // + Sentence references for the word. // Each dictionary word can have up thru 63 different addressable meanings // as well as the generic word as a whole. These are the "tried" bits, used // when marking meanings to avoid redundant sweeps. // Sentence references are where the word/concept is found in the sentence and // is used by pattern matching. // Each reference is 4 bytes // byte0: start index into sentence // byte1: end index into sentence // byte2: if fundamental meaning this is where start of subject in sentence is (fundamental start/end is on the verb) // byte3: which exact dictionary index (capitalization) matched if exact match is involved // bytes4-7: int index of dictionary word bool RemoveMatchValue(WORDP D, int position) { HEAPINDEX access = GetAccess(D); if (!access) return false; bool changed = false; unsigned char* data = (unsigned char*) (Index2Heap(access) + 8); // skip over 64bit tried by meaning field unsigned char* tried = NULL; bool didmod = false; for (int i = 0; i < MAXREFSENTENCE_BYTES; i += REF_ELEMENT_SIZE) { if (data[i] == position) { didmod = true; if (!changed)// protect by moving data to new area so restoresentence is safe { HEAPINDEX newaccess = CopyWhereInSentence(access); tried = (unsigned char*)(Index2Heap(newaccess) + 8); changed = true; SetTried(D, newaccess); } memmove(tried +i, tried +i+ REF_ELEMENT_SIZE,(MAXREFSENTENCE_BYTES - i - REF_ELEMENT_SIZE)); // end will not have marker now if we deleted last reference, so insert insurance tried[MAXREFSENTENCE_BYTES - REF_ELEMENT_SIZE] = 0xff; break; } } return didmod; } static int WhereWordHitWithData(WORDP D, int start,unsigned char* data) { if (data) for (int i = 0; i < MAXREFSENTENCE_BYTES; i += REF_ELEMENT_SIZE) { if (data[i] >= start) { if (data[i] == start) return data[i + 1]; // return end of it else break; // cannot match later - end of data will be END_OF_REFERENCES (0xff) for all } } return 0; } static int WhereWordHit(WORDP D, int start) { unsigned char* data = GetWhereInSentence(D); if (data) for (int i = 0; i < MAXREFSENTENCE_BYTES; i += REF_ELEMENT_SIZE) { if (data[i] >= start) { if (data[i] == start) return data[i + 1]; // return end of it else break; // cannot match later } } return 0; } bool HasMarks(int start) { WORDP D = FindWord(wordStarts[start]); // nominal word there if (!D) return false; unsigned char* data = GetWhereInSentence(D); // has 2 hidden int fields before this point if (!data) return false; int whereHitEnd = WhereWordHitWithData(D, start, data); // word in sentence index return whereHitEnd != 0; } bool MarkWordHit(int depth, MEANING exactWord, WORDP D, int meaningIndex, int start, int end) { // keep closest to start at bottom, when run out, drop later ones if (!D || !D->word) return false; if (start > wordCount) { ReportBug((char*)"INFO: save position is too big") return false; } if (end > wordCount) end = wordCount; if (start == verbwordx && !stricmp(wordStarts[start], "verify")) return false; // been here before? unsigned char* data = GetWhereInSentence(D); // has 2 hidden int fields before this point if (!data) data = (unsigned char*)AllocateWhereInSentence(D); if (!data) return false; // allocate failure int whereHitEnd = WhereWordHitWithData(D, start,data); // word in sentence index if (*D->word != '~') // real word, not a concept { uint64 meaningBit = 1ull << meaningIndex; // convert index into bit if (whereHitEnd < end) SetTriedMeaningWithData(GENERIC_MEANING,(unsigned int*)data); uint64 triedBits = GetTriedMeaning(D); if (meaningBit & triedBits) return false; // did this meaning already SetTriedMeaningWithData(triedBits | meaningBit, (unsigned int*)data); // update more } else if (whereHitEnd >= end) return false; // no need since already covering concept in this area if (++marklimit > 5000) { if (!failFired) ReportBug("INFO: Mark limit hit") failFired = true; return false; } // diff < 0 means peering INSIDE a multiword token before last word // we label END as the word before it (so we can still see next word) and START as the actual multiword token bool added = false; for (int i = 0; i < MAXREFSENTENCE_BYTES; i += REF_ELEMENT_SIZE) { unsigned char begin = data[i]; if (begin < start) continue; // skip over it else if (begin == start) // we have already marked this somewhat { if (end > data[i+1]) added = true; // prefer the longer match } else // insert here { memmove(data+i+ REF_ELEMENT_SIZE,data+i,MAXREFSENTENCE_BYTES - i - REF_ELEMENT_SIZE); // create a hole for entry data[i] = (unsigned char)start; added = true; } if (added) { data[i + 1] = (unsigned char)(end & 0x00ff); data[i + 2] = (unsigned char) (end >> 8); // for fundamental meanings this is the 3rd field reference data[i + 3] = 0; MEANING* exact = (MEANING*)(data + i + 4); *exact = exactWord; } break; // have location } if (added) { if (*D->word == '~')// track the actual sets done matching start word location (good for verbs, not so good for nouns) { // concepts[i] and topics[i] are lists of word indices if (!(D->internalBits & TOPIC)) Add2ConceptTopicList(concepts, D, start, end, false); // DOESNT need to be be marked as concept else Add2ConceptTopicList(topics, D, start, end, false); } if ((trace & (TRACE_PREPARE | TRACE_HIERARCHY) || prepareMode == PREPARE_MODE || showMark) && (D->word[0] != '~' || !IsDigit(D->word[1]))) { markLength += D->length; if (markLength > MARK_LINE_LIMIT) { markLength = 0; Log(USERLOG,"\r\n"); Log(USERLOG,""); } while (depth-- >= 0) Log((showMark) ? ECHOUSERLOG : USERLOG, " "); char which[20]; *which = 0; which[1] = 0; if (exactWord && D->internalBits & UPPERCASE_HASH) which[0] = '^'; Log((showMark) ? ECHOUSERLOG : USERLOG, (D->internalBits & TOPIC) ? "+T%s%s " : (char*)" +%s%s", D->word, which); Log((showMark) ? ECHOUSERLOG : USERLOG," (%d-%d)\r\n", start, end); markLength = 0; } } return added; } HEAPINDEX GetAccess(WORDP D) { std::map::iterator it; it = triedData.find(D); if (it == triedData.end()) return 0; HEAPINDEX access = it->second; // heap index return access; } unsigned char* GetWhereInSentence(WORDP D) // [0] is the meanings bits, the rest are start/end/case bytes for 8 locations { if (!D) return NULL; HEAPINDEX access = GetAccess(D); return (!access) ? NULL : (unsigned char*)Index2Heap(access) + 8; // skip over 64bit tried by meaning field } HEAPINDEX CopyWhereInSentence(int oldindex) { unsigned int* olddata = (unsigned int*)Index2Heap(oldindex); // original location if (!olddata) return 0; size_t len = TRIEDDATA_WORDSIZE; // 64bit tried by meaning field (aligned) + sentencerefs (2 bytes each + a byte for uppercase index) unsigned int* data = (unsigned int*)AllocateHeap(NULL, len, 4, false); // 64 bits (2 words) + 48 bytes (12 words) = 14 words if (data) memcpy((char*)data, olddata, len * sizeof(int)); return Heap2Index((char*)data); } void ClearWhereInSentence() // erases the WHEREINSENTENCE and the TRIEDBITS { memset(concepts, 0, sizeof(unsigned int) * MAX_SENTENCE_LENGTH); memset(topics, 0, sizeof(unsigned int) * MAX_SENTENCE_LENGTH); // be able to reuse memory if (documentMode) for (std::map::iterator it = triedData.begin(); it != triedData.end(); ++it) { MEANING* data = (MEANING*)Index2Heap(it->second); *data = freeTriedList; freeTriedList = it->second; } triedData.clear(); memset(unmarked, 0, MAX_SENTENCE_LENGTH); } void ClearTriedData() // erases the WHEREINSENTENCE and the TRIEDBITS { triedData.clear(); freeTriedList = 0; } unsigned int* AllocateWhereInSentence(WORDP D) { if (documentMode && freeTriedList) // reuse memory { MEANING* d = (MEANING*)Index2Heap(freeTriedList); freeTriedList = *d; } size_t len = TRIEDDATA_WORDSIZE; // 64bit tried by meaning field (aligned) + sentencerefs (3 bytes each + a byte for uppercase index) unsigned int* data = (unsigned int*)AllocateHeap(NULL, len, 4, false); if (!data) return NULL; memset((char*)data, END_OF_REFERENCES, len * sizeof(int)); // clears sentence xref start/end bits and casing byte data[0] = 0; // clears the tried meanings list data[1] = 0; // store where in the temps data int index = Heap2Index((char*)data); // original index! triedData[D] = index; return data + 2; // analogous to GetWhereInSentence } void SetTriedMeaningWithData(uint64 bits, unsigned int* data) { *(data - 2) = (unsigned int)(bits >> 32); *(data - 1) = (unsigned int)(bits & 0xffffffff); // back up to the tried meaning area } void SetTriedMeaning(WORDP D, uint64 bits) { unsigned int* data = (unsigned int*)GetWhereInSentence(D); if (!data) { data = AllocateWhereInSentence(D); // returns past the tried bits of chunk if (!data) return; // failed to allocate } *(data - 2) = (unsigned int)(bits >> 32); *(data - 1) = (unsigned int)(bits & 0xffffffff); // back up to the tried meaning area } uint64 GetTriedMeaning(WORDP D) // which meanings have been used (up to 64) { std::map::iterator it; it = triedData.find(D); if (it == triedData.end()) return 0; unsigned int* data = (unsigned int*)Index2Heap(it->second); // original location if (!data) return 0; uint64 value = ((uint64)(data[0])) << 32; value |= (uint64)data[1]; return value; // back up to the correct meaning zone } unsigned int GetIthSpot(WORDP D, int i, int& start, int& end) { if (!D) return 0; // not in sentence unsigned char* data = GetWhereInSentence(D); if (!data) return 0; i *= REF_ELEMENT_SIZE; if (i >= MAXREFSENTENCE_BYTES) return 0; // at end start = (unsigned char) data[i]; if (start == END_OF_REFERENCES) return 0; end = (unsigned char) data[i + 1]; if (end > wordCount) { static bool did = false; if (!did) ReportBug((char*)"INFO: Getith out of range %s at %d\r\n", D->word, volleyCount); did = true; } if (data[i + 2]) end |= data[i + 2]; // fundamental Meaning extra value return start; } static unsigned char* DataIntersect(WORDP D) { char* ptr = D->word; char* at = strchr(ptr + 1, '~'); // joiner of disparate concepts, like ~pet~tasty unsigned char* data = NULL; // dont do word~1~concept or word~n~concept if (at && at[2]) // word with ~casemarking data added, has no data on its own, not trial~n or trial~1 { WORDP first = FindWord(ptr, (at - ptr)); // the first piece data = GetWhereInSentence(first); if (!data) return 0; size_t len = strlen(at); char* at1 = strchr(at + 1, '~'); // and a 3rd piece if (at1) len = at1 - at; WORDP second = FindWord(at, len); // the 2nd piece unsigned char* seconddata = GetWhereInSentence(second); if (!seconddata) return 0; // word not found so conjoin cant either unsigned char* thirddata = NULL; if (at1) { WORDP third = FindWord(at1); thirddata = GetWhereInSentence(third); if (!thirddata) return 0; // not there } unsigned char* commonData = (unsigned char*)AllocateWhereInSentence(D); if (!commonData) return 0; // allocate failure memcpy(commonData, data, TRIEDDATA_WORDSIZE * sizeof(int)); // starts with the base // keep common positions of this second word (and optionally third) with existing first for (int i = 0; i < MAXREFSENTENCE_BYTES; i += REF_ELEMENT_SIZE) // walk commondata { if (commonData[i] == END_OF_REFERENCES) break; // no more data in base bool found1 = false; bool found2 = false; for (int j = 0; j < MAXREFSENTENCE_BYTES; j += REF_ELEMENT_SIZE) { if (seconddata[j] == END_OF_REFERENCES) break; // end of this piece if (seconddata[j] == commonData[i]) // found here { found1 = true; break; } } if (thirddata) for (int j = 0; j < MAXREFSENTENCE_BYTES; j += REF_ELEMENT_SIZE) { if (thirddata[j] == END_OF_REFERENCES) break; // end of this piece if (thirddata[j] == commonData[i]) // found here { found2 = true; break; } } else found2 = true; // dont need a third if (!found1 || !found2) // our common data is not common to all { memmove(commonData + i, commonData + i + REF_ELEMENT_SIZE, (MAXREFSENTENCE_BYTES - i - REF_ELEMENT_SIZE)); i -= REF_ELEMENT_SIZE; } } if (commonData[0] == END_OF_REFERENCES) return 0; // nothing in common data = commonData; // the common data of word and concept } return data; } unsigned int GetNextSpot(WORDP D, int start, int &startPosition, int& endPosition, bool reverse, int legalgap) {// spot can be 1-31, range can be 0-7 -- 7 means its a string, set last marker back before start so can rescan // BUG - we should note if match is literal or canonical, so can handle that easily during match eg // '~shapes matches square but not squares (whereas currently literal fails because it is not ~shapes) if (!D) return 0; // not in sentence unsigned char* data = GetWhereInSentence(D); char* ptr = D->word; if (!data && D->internalBits & HAS_CASEMARKING) data = DataIntersect(D); // word with ~casemarking data added, has no data on its own, not trial~n or trial~1 - or concept intersect: ~pet~tasty if (!data) return 0; // now perform the real analysis uppercaseFind = -1; int i; startPosition = 0; for (i = 0; i < MAXREFSENTENCE_BYTES; i += REF_ELEMENT_SIZE) // each 8 byte ref is start,end,extra,unused, 4byte exact, { unsigned char at = data[i]; if (at == END_OF_REFERENCES) // end of unit marker { if (!reverse) return 0; else break; // cannot back further } unsigned char end = data[i + 1]; unsigned char fundamentalExtra = data[i + 2]; // usually 0, except for fundamental meaning matches MEANING* exact = (MEANING*)(data + i + 4); if (unmarked[at]) { ; } else if (reverse) { if (at < start) // valid. but starts far from where we are { startPosition = at; // bug fix backward gaps as well endPosition = end | (fundamentalExtra << 8); // hidden subject data for fundamental meanings uppercaseFind = (int)*exact; continue; // find the CLOSEST without going over } else if (at >= start) break; // getting worse } else if (at > start) // scanning forward { if (legalgap && (at - start) > legalgap) startPosition = 0; // too far away and optional else { startPosition = at; endPosition = end | (fundamentalExtra << 8); // hidden subject data for fundamental meanings uppercaseFind = (int)*exact; } break; } } return startPosition; // we have a closest or we dont } /**************************************/ /* End of Word Reference in Sentence system */ /**************************************/ static void TraceHierarchy(FACT* F,char* msg) { if (TraceHierarchyTest(trace)) { char* word = AllocateBuffer(); char* fact = WriteFact(F, false, word); // just so we can see it unsigned int hold = globalDepth; globalDepth = 4 + 1; if (!msg) Log(USERLOG,"%s\r\n", fact); // \r\n else Log(USERLOG,"%s (%s)\r\n", fact,msg); // \r\n globalDepth = hold; FreeBuffer(); } } static void AddPendingConcept(FACT* F, unsigned int start, unsigned int end) { pendingConceptList = AllocateHeapval(pendingConceptList, (uint64)F, start, end); TraceHierarchy(F,"delayed"); } static bool ProcessPendingFact(FACT* F, int start, int end) { WORDP O = Meaning2Word(F->object); if (WhereWordHit(O, start) >= (int)end) return true; // already marked this set // FACT wanted to map subject to concept object, but it had an exclude on a set that needed completion int depth = 4; WORDP S = Meaning2Word(F->subject); int startPosition, endPosition; if (GetNextSpot(S, start - 1, startPosition, endPosition) && startPosition == start && endPosition == end) { TraceHierarchy(F,""); return true; // not allowed to proceed } return false; // unmarked. we dont know } static void ProcessPendingConcepts() { // (~setmember exclude ~set) // has subject been marked at this position, if so, we cannot trigger concept for this position if (!pendingConceptList) return; HEAPREF startList = pendingConceptList; // F start|end HEAPREF begin = startList; bool changed = false; while (1) { if (!startList) // we will be cycling list trying to get changes until no list or no changes { if (!changed) break; // no improvement startList = begin; changed = false; } uint64 start; uint64 end; uint64* currentEntry = (uint64*) startList; uint64 Fx; startList = UnpackHeapval(startList, Fx, start,end); FACT* F = (FACT*)Fx; WORDP concept = NULL; // set we want to trigger while (F) // will be NULL if we have already finished with it { // expect exit here because writing a concept with NO members is nuts if (F->verb != Mexclude) break; // ran out of set restrictions // before we can trigger this set membership concept = Meaning2Word(F->object); if (ProcessPendingFact(F, (int)start, (int)end)) // failed { currentEntry[1] = 0; // kill fact use changed = true; } F = GetObjectNondeadNext(F); } // now flow path of this set upwards since all excludes have been considered if (F && F->verb != Mexclude) { TraceHierarchy(F,"resume"); if (MarkWordHit(4, EXACTNOTSET, concept, 0, (int)start, (int)end)) // new ref added { if (MarkSetPath(4 + 1, EXACTNOTSET, F->object, (int)start, (int)end, 4 + 1, FIXED) != -1) changed = true; // someone marked } } } // activate anything not already deactivated now while (pendingConceptList) // one entry per pending set that had excludes { bool exact = false; bool canonical = false; uint64 Fx; uint64 start; uint64 end; pendingConceptList = UnpackHeapval(pendingConceptList, Fx,start,end); if (!Fx) continue; FACT* F = (FACT*)Fx; WORDP E = (F) ? Meaning2Word(F->object) : NULL; // mark all members of the link TraceHierarchy(F,"defaulting"); if (MarkWordHit(4, EXACTNOTSET, E, 0, (int)start, (int)end)) // new ref added { MarkSetPath(4 + 1, EXACTNOTSET, F->object, (int)start, (int)end, 4 + 1, FIXED); } } } static bool IsValidStart(int start) { if (start == 1) return true; if (!(tokenControl & DO_INTERJECTION_SPLITTING)) { // if not splitting interjections into their own sentence, then could be a valid start // if all previous words are an interjection WORDP D = FindWord("~interjections"); if (!D) return false; int i = 0; while (++i= start) return false; i = end; } } return true; } return false; } MEANING EncodeConceptMember(char* word,int& flags) { char hold[MAX_WORD_SIZE]; if (*word == '~') MakeLowerCopy(hold, word); // require concept name be lower case else strcpy(hold, JoinWords(BurstWord(word, CONTRACTIONS))); char* at = hold; while ((at = strchr(at, '_'))) *at = ' '; // change _ to spaces at = hold; if (*word == '\'' && word[1] == '\'') // 2 quotes mean case sensitive user typing { flags |= RAWCASE_ONLY; at += 2; } else if (*word == '\'') // 1 quote means not canonical (allowing FIXED or RAW) { flags |= ORIGINAL_ONLY; ++at; } return MakeMeaning(StoreWord(at,AS_IS)); } static int MarkSetPath(int depth,int exactWord,MEANING M, int start, int end, unsigned int level, int kind) // walks set hierarchy {// travels up concept/class sets only, though might start out on a synset node or a regular word unsigned int flags = GETTYPERESTRICTION(M); if (!flags) flags = BASIC_POS; // what POS we allow from Meaning WORDP D = Meaning2Word(M); int index = Meaning2Index(M); // always 0 for a synset or set // check for any repeated accesses of this synset or set or word uint64 offset = 1ull << index; int result = NOPROBLEM_BIT; FACT* H = GetSubjectNondeadHead(D); // thisword/concept member y while (H) { FACT* F = H; H = GetSubjectNondeadNext(H); if (F->verb != Mmember) continue; // ~concept members and word equivalent if (trace & TRACE_HIERARCHY) TraceHierarchy(F,""); WORDP concept = Meaning2Word(F->object); if (concept->internalBits & OVERRIDE_CONCEPT) // override by ^testpattern, is this legal fact? { if (!(F->flags & OVERRIDE_MEMBER_FACT)) continue; // pretend he doesnt exist } // if subject has type restriction, it must pass unsigned int restrict = GETTYPERESTRICTION(F->subject ); if (!restrict && index) restrict = GETTYPERESTRICTION(GetMeaning(D,index)); // new (may be unneeded) // reasons we cant use this fact // for true interjection, END_ONLY can mean wordCount or next word is , or - bool block = false; if (kind != RAWCASE && F->flags & RAWCASE_ONLY) { block = true; } // incoming is not raw correctly cased words and must be else if (kind == CANONICAL && F->flags & ORIGINAL_ONLY) { block = true; } // incoming is not original words and must be else if (restrict && !(restrict & flags)) { block = true; } // type restriction in effect for this concept member else if (F->flags & (START_ONLY | END_ONLY)) { if (F->flags & START_ONLY && !IsValidStart(start)) { block = true; } // must begin the sentence else if (F->flags & END_ONLY && end != wordCount && !(F->flags & START_ONLY)) { block = true; } // must begin the sentence else if ((F->flags & (START_ONLY | END_ONLY)) == (START_ONLY | END_ONLY) && IsValidStart(start)) { if (end == wordCount) { ; } else if (*wordStarts[end + 1] == ',') { ; } else if (*wordStarts[end + 1] == '-') { ; } else block = true; } } int mindex = Meaning2Index(F->subject); // index meaning restriction (0 means all) if (!block && index == mindex) // match generic or exact subject { bool mark = true; // test for word not included in set if (index) { unsigned int pos = GETTYPERESTRICTION(GetMeaning(Meaning2Word(F->subject), index)); if (!(flags & pos)) mark = false; // we cannot be that meaning because type is wrong } if (!mark) { if (trace & TRACE_HIERARCHY) TraceHierarchy(F, ""); } else if (*concept->word == '~' && WhereWordHit(concept, start) >= end) mark = false; // already marked this set else //does set has some members it does not want { FACT* G = GetObjectNondeadHead(concept); while (G) { // all simple excludes will be first // all set excludes will be second // actual values of set will be third // User can defeat that by runtime addition of set members. Too bad for now. // so technically we could free up the HAS_EXCLUDE bit if (G->verb == Mexclude) // see if this is marked for this position, if so, DONT trigger topic { WORDP S = Meaning2Word(G->subject); // what to exclude int startPosition,endPosition; // need to test for original only as well - BUG if (GetNextSpot(S,start-1,startPosition,endPosition) && startPosition == start && endPosition == end) { if (trace & TRACE_HIERARCHY) TraceHierarchy(F,""); mark = false; break; } // if we see a concept exclude, regular ones already passed if (*S->word == '~') // concept exclusion - status unknown { AddPendingConcept(G, start, end); // must review later mark = false; // dont mark now break; // revisit all exclusions later } } else if (G->verb == Mmember) // ran out of excludes { mark = true; break; } G = GetObjectNondeadNext(G); } } if (mark) { if (MarkWordHit(depth, exactWord, concept, index,start, end)) // new ref added { if (MarkSetPath(depth+1, exactWord, F->object, start, end, level + 1, kind) != -1) result = 1; // someone marked } } } else if (!index && mindex) // we are all meanings (limited by pos use) and he is a specific meaning { WORDP J = Meaning2Word(F->subject); MEANING M1 = GetMeaning(J, mindex); unsigned int pos = GETTYPERESTRICTION(M1); if (flags & pos) // && start == end wont work if spanning multiple words revised due to "to fish" noun infinitive { if (MarkWordHit(depth, exactWord, Meaning2Word(F->object), Meaning2Index(F->object),start, end)) // new ref added { if (MarkSetPath(depth+1, exactWord, F->object, start, end, level + 1, kind) != -1) result = 1; // someone marked } } } } return result; } static void RiseUp(int depth, int exactWord,MEANING M,unsigned int start, unsigned int end,unsigned int level,int kind) // walk wordnet hierarchy above a synset node { // M is always a synset head M &= -1 ^ SYNSET_MARKER; unsigned int index = Meaning2Index(M); WORDP D = Meaning2Word(M); char word[MAX_WORD_SIZE]; sprintf(word,(char*)"%s~%u",D->word,index); // some meaning is directly referenced? MarkWordHit(depth, exactWord, FindWord(word),0,start,end); // direct reference in a pattern // now spread and rise up if (MarkSetPath(depth, exactWord,M,start,end,level,kind) == -1) return; // did the path already FACT* F = GetSubjectNondeadHead(D); while (F) { if (F->verb == Mis && (index == 0 || F->subject == M)) RiseUp(depth+1,exactWord,F->object,start,end,level+1,kind); // allowed up F = GetSubjectNondeadNext(F); } } static void MarkAllMeaningAndImplications(int depth, MEANING M, int start, int end, int kind, bool sequence,bool once) { // M is always a word or sequence from a sentence // but if uppercase, there may be multiple forms, so handle all if (!M) return; WORDP D = Meaning2Word(M); unsigned int len = D->length; unsigned int hash = (D->hash % maxHashBuckets); // mod by the size of the table int uindex = 0; // lowercase bucket WORDP X = dictionaryBase + hashbuckets[hash + 1]; // look in uppercase bucket for this word while (X != dictionaryBase) // all entries matching in upper case bucket { if (!IsValidLanguage(X)) { ; } else if (D->hash == X->hash && X->length == len && !StricmpUTF(D->word, X->word, len)) { MEANING M1 = M; MEANING windex = MakeMeaning(X); if ((M & MEANING_BASE) != windex) // alternate spelling to what was passed in { M1 = windex | (M & TYPE_RESTRICTION); // no idea what meaning, go generic } MarkMeaningAndImplications(depth, windex, M1, start, end, kind, sequence, once); } X = dictionaryBase + GETNEXTNODE(X); } } void MarkMeaningAndImplications(int depth, MEANING exactWord,MEANING M,int start, int end,int kind,bool sequence,bool once) { // M is always a word or sequence from a sentence if (!M) return; WORDP D = Meaning2Word(M); if (D->properties & NOUN_TITLE_OF_WORK && kind == CANONICAL) return; // accidental canonical match of a title. not intended // We want to avoid wandering fact relationships for meanings we have already scanned. if (!exactWord) { if (D->internalBits & UPPERCASE_HASH) MarkAllMeaningAndImplications(depth, M, start, end,kind,sequence, once); else if (kind == CANONICAL) exactWord = (MEANING)DONTUSEEXACT; // dont use concept word or lowercase word as the match } // We mark words/phrases and concepts and words/concepts implied by them. // We mark words by meaning (63) + generic. They always have a fixed size match. // We mark concepts by size match at a start position. You might match 1 word or several in a row. // For match variable retrieval we want the longest match at a position. // Because we can come in here with a general word with and without a type restriction, // we have scan out from the word because we cant mark the different ways we scanned before, int index = Meaning2Index(M); int whereHit = WhereWordHit(D, start); unsigned int restrict = GETTYPERESTRICTION(M); unsigned int size = GetMeaningCount(D); if (size == 0) { M = MakeMeaning(D); // remove restriction restrict = 0; } if (*D->word == '~') { if (whereHit >= end) return; // already have best concept storage } else { if (whereHit < end) SetTriedMeaning(D, 0); // found nothing at this index, insure nothing to start } // we mark word hit before using MarkSetPath, so that exclude is supported // words we dont know we dont bother marking if (!once || D->properties & (PART_OF_SPEECH | NOUN_TITLE_OF_WORK | NOUN_HUMAN) || D->systemFlags & PATTERN_WORD || D->internalBits & CONCEPT) { MarkWordHit(depth, exactWord, D, 0, start, end); MarkSetPath(depth + 2, exactWord, M, start, end, 0, kind); // generic membership of this word all the way to top } // we dont mark random junk discovered, only significant sequences else if (sequence) // phrase is not a pattern word, maybe it goes to some concepts { FACT* F = GetSubjectNondeadHead(D); while (F) { // mark sequence if someone cared about it if (ValidMemberFact(F)) // ~concept members and word equivalent { MarkWordHit(depth, exactWord, D, 0, start, end); // we found something to relate to, so mark us MarkSetPath(depth + 2, exactWord, M, start, end, 0, kind); // generic membership of this word all the way to top break; } F = GetSubjectNext(F); } } // check for POS restricted forms of this word char word[MAX_WORD_SIZE]; if (*D->word != '~' && !once) // words, not concepts { if (restrict & NOUN && !(posValues[start] & NOUN_INFINITIVE)) // BUG- this wont work up the ontology, only at the root of what the script requests - doesnt accept "I like to *fish" as a noun, so wont refer to the animal { sprintf(word, (char*)"%s~n", D->word); MarkWordHit(depth, exactWord, FindWord(word, 0, PRIMARY_CASE_ALLOWED), 0, start, end); // direct reference in a pattern } if ((restrict & VERB) || posValues[start] & NOUN_INFINITIVE)// accepts "I like to *swim as not a verb meaning" { sprintf(word, (char*)"%s~v", D->word); MarkWordHit(depth, exactWord, FindWord(word, 0, PRIMARY_CASE_ALLOWED), 0, start, end); // direct reference in a pattern } if (restrict & ADJECTIVE) // and adverb { sprintf(word, (char*)"%s~a", D->word); MarkWordHit(depth, exactWord, FindWord(word, 0, PRIMARY_CASE_ALLOWED), 0, start, end); // direct reference in a pattern } } // now follow out the allowed synset hierarchies if (!restrict) restrict = ESSENTIAL_FLAGS & finalPosValues[end]; // unmarked ptrs can rise all branches compatible with final values - end of a multiword (idiom or to-infintiive) is always the posvalued one if (!once && *D->word != '~') for (unsigned int k = 1; k <= size; ++k) { M = GetMeaning(D,k); // it is a flagged meaning unless it self points if (!(GETTYPERESTRICTION(M) & restrict)) continue; // cannot match type // walk the synset words and see if any want vague concept matching like dog~~ MEANING T = M; // starts with basic meaning unsigned int n = (index && (int)k != index) ? (unsigned int)80 : (unsigned int) 0; // only on this meaning or all synset meanings while (n < 50) // insure not infinite loop { WORDP X = Meaning2Word(T); unsigned int ind = Meaning2Index(T); sprintf(word,(char*)"%s~~",X->word); MarkWordHit(depth, exactWord, FindWord(word, 0, PRIMARY_CASE_ALLOWED),0,start,end); // direct reference in a pattern if (!ind) break; // has no meaning index MEANING* meanings = GetMeanings(X); if (!meanings) break; T = meanings[ind]; if (!T) break; if ((T & MEANING_BASE) == (M & MEANING_BASE)) break; // end of loop ++n; } M = (M & SYNSET_MARKER) ? MakeMeaning(D,k) : GetMaster(M); // we are the master itself or we go get the master RiseUp(depth+1, exactWord,M,start,end,0,kind); // allowed meaning pos (self ptrs need not rise up) } } static void HuntMatch(int kind, char* word,bool strict,int start, int end, unsigned int& usetrace) { WORDP D; int oldtrace = trace; // if user typed upper case specifically, trust him if (start == end && start != 1 && IsUpperCase(word[0])) { if (!IsUpperCase(word[1])) strict = true; } WORDP set[20]; int i = GetWords(word,set,strict); // words in any case and with mixed underscore and spaces while (i) { D = set[--i]; // dont redo effort if (D->internalBits & BEEN_HERE) continue; // huntmatch already covered this D->internalBits |= BEEN_HERE; wordlist = AllocateStackval(wordlist, (uint64) D,0); // markallimplied matches words with part of speech. This doesnt if (!(D->systemFlags & PATTERN_WORD) && !(D->properties & PART_OF_SPEECH) && !(D->internalBits & UPPERCASE_HASH)) // given no flag reason to use, see if concept member { FACT* F = GetSubjectHead(D); // is it a part of some concept? Or a direct wor while (F) { if (ValidMemberFact(F)) break; // legal fact for us F = GetSubjectNext(F); } if (!F) continue; } // not allowed to detect uppercase when user input // is form is conjugated (fitted != Fit) except // for Plural noun OR monitoring => Monitor as canonical if (start == end && IsUpperCase(*D->word) && kind == FIXED){ ; } else if (start == end && IsUpperCase(*D->word) && kind == CANONICAL && stricmp(D->word, wordStarts[start])) // user didnt type this, upper case should be noun singular -- bug for us if he did proper noun plural { continue; // avoiding canonical Monitor of monitoring } trace = (D->subjectHead || D->systemFlags & PATTERN_WORD || D->properties & PART_OF_SPEECH) ? usetrace : 0; // being a subject head means belongs to some set. being a marked word means used as a keyword if ((*D->word == 'I' || *D->word == 'i') && !D->word[1]) continue; // dont follow out this I or i word int refined = kind; if (kind == RAW) // promote to rawcase? { size_t len = strlen(D->word); if (!strncmp(D->word, word, len - 1)) // except for 1st letter is rest of word what user types { if (start != 1) refined = RAWCASE; // only first word of sentence may be provoked else if (!IsUpperCase(*word)) refined = RAWCASE; // lower case by user not impacted by grammar stricture else if (IsUpperCase(word[1])) refined = RAWCASE; // 2 uppercase in a row is intentional (or caps lock so we cant tell) } } MarkMeaningAndImplications(0, 0,MakeMeaning(D),start,end, refined,true); } trace = (modifiedTrace) ? modifiedTraceVal : oldtrace; } static void SetSequenceStamp() // mark words in sequence, original and canonical (but not mixed) - detects proper name potential up to 5 words - and does discontiguous phrasal verbs {// words are always fully generic, never restricted by meaning or postag // these use underscores char* fixedbuffer = AllocateStack(NULL, maxBufferSize); char* limit = GetUserVariable("$cs_sequence", false, true); int sequenceLimit = (*limit) ? atoi(limit) : SEQUENCE_LIMIT; if (!stricmp(language, "japanese")) { for (int i = 1; i <= wordCount; ++i) { strcpy(fixedbuffer, wordStarts[i]); int limit = i + sequenceLimit + 10; // japanese sentences can be long words if (limit > wordCount) limit = wordCount; for (int j = i + 1; j <= limit; ++j) { strcat(fixedbuffer, " "); strcat(fixedbuffer, wordStarts[j]); WORDP D = FindWord(fixedbuffer, 0, PRIMARY_CASE_ALLOWED); if (D) MarkMeaningAndImplications(0, 0, MakeMeaning(D), i, j, CANONICAL, true); } ReleaseStack(fixedbuffer); // short term return; } } if (parseLimited && sequenceLimit > 2) sequenceLimit = 2; char* canonbuffer = AllocateStack(NULL, maxBufferSize); char* rawbuffer = AllocateStack(NULL, maxBufferSize); // includes typos wordlist = NULL; unsigned int oldtrace = trace; unsigned int usetrace = trace; if (trace & (TRACE_HIERARCHY | TRACE_PREPARE) || prepareMode == PREPARE_MODE) { Log(USERLOG,"\r\nSequences:\r\n"); usetrace = (unsigned int) -1; if (oldtrace && !(oldtrace & TRACE_ECHO)) usetrace ^= TRACE_ECHO; } // consider all sets of up to 5-in-a-row if (endSentence > wordCount) endSentence = wordCount; for (int i = startSentence; i <= (int)endSentence; ++i) { marklimit = 0; // per word scan limit while (wordlist) { uint64 D; wordlist = UnpackHeapval(wordlist, D, discard); ((WORDP)D)->internalBits ^= BEEN_HERE; } if (!IsAlphaUTF8OrDigit(*wordStarts[i]) ) continue; // we only composite words, not punctuation or quoted stuff if (IsDate(wordStarts[i])) continue;// 1 word date, caught later // check for dates int start,end; if (DateZone(i,start,end) && i != wordCount) { int at = start - 1; *fixedbuffer = 0; while (++at <= end) { if (!stricmp(wordStarts[at],(char*)"of")) continue; // skip of strcat(fixedbuffer,wordStarts[at]); if (at != end) strcat(fixedbuffer,(char*)"_"); } StoreWord(fixedbuffer,NOUN|NOUN_PROPER_SINGULAR); MarkMeaningAndImplications(0, 0,MakeMeaning(FindWord((char*)"~dateinfo")),start,end,FIXED,true); i = end; continue; } else if ((i + 4) <= wordCount && IsDigitWord(wordStarts[i], numberStyle) && // multiword date IsDigitWord(wordStarts[i + 2], numberStyle) && IsDigitWord(wordStarts[i + 4], numberStyle)) { int val = atoi(wordStarts[i + 2]); // must be legal 1 - 31 char sep = *wordStarts[i + 1]; if (*wordStarts[i + 3] == sep && val >= 1 && val <= 31 && (sep == '-' || sep == '/' || sep == '.') ) { char word[MAX_WORD_SIZE]; strcpy(word, wordStarts[i]); // force month first strcat(word, wordStarts[i + 1]); strcat(word, wordStarts[i + 2]); strcat(word, wordStarts[i + 3]); strcat(word, wordStarts[i + 4]); WORDP D = StoreWord(word, NOUN | NOUN_PROPER_SINGULAR); MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~dateinfo")), i, i+4, FIXED, true); } } // do hyphenated forms of pairs strcpy(fixedbuffer, wordStarts[i]); strcat(fixedbuffer, "-"); strcat(fixedbuffer, wordStarts[i+1]); HuntMatch(FIXED, fixedbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i+1, usetrace); strcpy(canonbuffer, wordCanonical[i]); strcat(canonbuffer, "-"); strcat(canonbuffer, wordCanonical[i + 1]); HuntMatch(CANONICAL,canonbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i + 1, usetrace); // set base phrase strcpy(fixedbuffer,wordStarts[i]); strcpy(canonbuffer,wordCanonical[i]); *rawbuffer = 0; start = derivationIndex[i] >> 8; // from here end = derivationIndex[i] & 0x00ff; // to here for (int j = start; j <= end; ++j) { if (!derivationSentence[j]) break; // in case sentence is empty strcat(rawbuffer,derivationSentence[j]); if ( j != end && derivationSeparator[j] && !(derivationSeparator[j] == '"' || derivationSeparator[j] == '\'')) strcat(rawbuffer,"_"); } // scan interesting initial words (spaced, underscored, capitalized) but we need to recognize bots in lower case, so try all cases here as well if (!trustpos) // the base words would already be scanned and marked by pos { // test in most specific order HuntMatch(RAW, rawbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i, usetrace); // using start and end have derivation issues when multiple replaces happen HuntMatch(FIXED , fixedbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i, usetrace); HuntMatch(CANONICAL, canonbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i, usetrace); } // fan out for addon pieces int k = 0; int index = 0; while ((++k + i) <= endSentence) { strcat(rawbuffer,(char*)"_"); start = derivationIndex[i+k] >> 8; // from here end = derivationIndex[i+k] & 0x00ff; // to here for (int j = start; j <= end; ++j) { if (!derivationSentence[j]) break; // in case sentence is empty strcat(rawbuffer,derivationSentence[j]); if ( j != end && derivationSeparator[j] && !(derivationSeparator[j] == '"' || derivationSeparator[j] == '\'')) strcat(rawbuffer,"_"); } strcat(fixedbuffer, (char*)"_"); strcat(fixedbuffer, wordStarts[i + k]); strcat(canonbuffer, (char*)"_"); strcat(canonbuffer, wordCanonical[i + k]); // we composite anything, not just words, in case they made a typo HuntMatch(RAW , rawbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i + k, usetrace); HuntMatch(FIXED,fixedbuffer,(tokenControl & STRICT_CASING) ? true : false,i,i+k,usetrace); HuntMatch(CANONICAL, canonbuffer, (tokenControl & STRICT_CASING) ? true : false, i, i + k, usetrace); if (++index >= sequenceLimit) break; // up thru n additional words in a phrase } } // mark disjoint particle verbs as whole for (int i = wordCount; i >= 1; --i) { marklimit = 0; // per word scan limit if (!(posValues[i] & PARTICLE)) continue; // find the particle unsigned int at = i; while (posValues[--at] & PARTICLE){;} // back up thru contiguous particles if (posValues[at] & (VERB_BITS|NOUN_INFINITIVE|NOUN_GERUND|ADJECTIVE_PARTICIPLE)) continue; // its all contiguous char canonical[MAX_WORD_SIZE]; char original[MAX_WORD_SIZE]; *canonical = 0; *original = 0; while (at && !(posValues[--at] & (VERB_BITS|NOUN_INFINITIVE|NOUN_GERUND|ADJECTIVE_PARTICIPLE))){;} // find the verb if (!at) continue; // failed to find match "in his early work *on von neuman... strcpy(original,wordStarts[at]); strcpy(canonical, wordCanonical[at]); unsigned int end = i; i = at; // resume out loop later from here while (++at <= end) { if (posValues[at] & (VERB_BITS|PARTICLE|NOUN_INFINITIVE|NOUN_GERUND|ADJECTIVE_PARTICIPLE)) { if (*original) { strcat(original,(char*)"_"); strcat(canonical,(char*)"_"); } strcat(original,wordStarts[at]); strcat(canonical, wordCanonical[at]); } } // storeword instead of findword because we normally dont store keyword phrases in dictionary WORDP D = FindWord(original,0,LOWERCASE_LOOKUP); if (D) { trace = usetrace; // being a subject head means belongs to some set. being a marked word means used as a keyword MarkMeaningAndImplications(0, 0,MakeMeaning(D),i,i,FIXED,false); } if (stricmp(original,canonical)) // they are different { D = FindWord(canonical,0,LOWERCASE_LOOKUP); if (D) { trace = usetrace; MarkMeaningAndImplications(0, 0,MakeMeaning(D),i,i,CANONICAL,false); } } } if (trace & TRACE_FLOW || prepareMode == PREPARE_MODE) Log(USERLOG,"\r\n"); while (wordlist) { uint64 D; wordlist = UnpackHeapval(wordlist, D, discard); ((WORDP)D)->internalBits ^= BEEN_HERE; } #ifdef TREETAGGER MarkChunk(); #endif trace = (modifiedTrace) ? modifiedTraceVal : oldtrace; ReleaseStack(fixedbuffer); // short term } static void StdMark(MEANING M, unsigned int start, unsigned int end, int kind) { if (!M) return; WORDP D = Meaning2Word(M); MarkMeaningAndImplications(0,0,M,start,end,kind); // the basic word if (IsModelNumber(D->word) && kind != CANONICAL) MarkMeaningAndImplications(0,0, MakeMeaning(StoreWord("~modelnumber")), start, end, FIXED); if (D->systemFlags & TIMEWORD && !(D->properties & PREPOSITION)) MarkMeaningAndImplications(0, 0,MakeMeaning(Dtime),start,end,FIXED); } static STACKREF BuildConceptList(int field,int verb) { STACKREF reflist = NULL; if (field == 0) { // command sentence with implied "you" as subject if (verb && verb < 3 && posValues[verb] & VERB_INFINITIVE) { WORDP D = StoreWord("you"); reflist = AllocateStackval(reflist, (uint64)D->word); } return 0; } HEAPREF list = concepts[field]; while (list) { //Meaning, Next uint64 word; list = UnpackHeapval(list, word, discard, discard); reflist = AllocateStackval(reflist, word); } list = topics[field]; while (list) { uint64 word; list = UnpackStackval(list, word); reflist = AllocateStackval(reflist, word); } reflist = AllocateStackval(reflist, (uint64)wordStarts[field]); if (stricmp(wordCanonical[field], wordStarts[field])) { reflist = AllocateStackval(reflist, (uint64)wordCanonical[field]); } return reflist; } static void ProcessWordLoop(STACKREF verblist, int verb, STACKREF subjectlist, int subject, STACKREF objectlist, int object, int base) { STACKREF slist = subjectlist; STACKREF olist = objectlist; char format[MAX_WORD_SIZE]; while (verblist) { uint64 verbword; verblist = UnpackStackval(verblist, verbword); sprintf(format, "|%s|", (char*)verbword); WORDP D = FindWord(format, 0, LOWERCASE_LOOKUP); if (!D) continue; // no one cares if (MarkWordHit(4, EXACTNOTSET, D, 0, verb, verb)) // new ref added { MarkSetPath(4 + 1, EXACTNOTSET, MakeMeaning(D), verb, verb, 4 + 1, FIXED); } if (slist && hasFundamentalMeanings & FUNDAMENTAL_SUBJECT) { // subject + verb subjectlist = slist; while (subjectlist) { uint64 subjectword; subjectlist = UnpackStackval(subjectlist, subjectword); sprintf(format, "%s|%s|", (char*)subjectword,(char*)verbword ); WORDP D = FindWord(format, 0); if (!D) continue; // no one cares if (MarkWordHit(4, EXACTNOTSET, D, 0, verb, verb)) // new ref added { unsigned int end = (object) ? object : verb; end |= subject << 8; MarkSetPath(4 + 1, EXACTNOTSET, MakeMeaning(D), verb, end, 4 + 1, FIXED); } // subject + verb + object if (olist && hasFundamentalMeanings & FUNDAMENTAL_SUBJECT_OBJECT) { objectlist = olist; while (objectlist) { uint64 objectword; objectlist = UnpackStackval(objectlist, objectword); sprintf(format, "%s|%s|%s",(char*) subjectword,(char*)verbword, (char*)objectword); WORDP E = FindWord(format, 0); if (E && MarkWordHit(4, EXACTNOTSET, E, 0, verb, verb)) // new ref added { int end = object | (subject << 8); MarkSetPath(4 + 1, EXACTNOTSET, MakeMeaning(E), verb, end, 4 + 1, FIXED); } } } } // verb + object if (olist && hasFundamentalMeanings & FUNDAMENTAL_OBJECT) { objectlist = olist; while (objectlist) { uint64 objectword; objectlist = UnpackStackval(objectlist, objectword); sprintf(format, "|%s|%s", (char*)verbword, (char*)objectword); WORDP E = FindWord(format, 0); if (!E) continue; // no one cares if (MarkWordHit(4, EXACTNOTSET, E, 0, verb, verb)) // new ref added { MarkSetPath(4 + 1, EXACTNOTSET, MakeMeaning(E), verb, object, 4 + 1, FIXED); } } } } } } static void ProcessSentenceConcepts(int subject, int verb, int object) { char* beginAllocation = AllocateStack(NULL, 4, false, 4); STACKREF subjectlist = (subject && hasFundamentalMeanings & FUNDAMENTAL_SUBJECT) ? BuildConceptList(subject,verb) : 0; STACKREF verblist = BuildConceptList(verb,0); STACKREF objectlist = (subject && hasFundamentalMeanings & FUNDAMENTAL_OBJECT) ? BuildConceptList(object,0) : 0; ProcessWordLoop(verblist, verb, subjectlist, subject, objectlist, object, verb); ReleaseStack(beginAllocation); } static void MarkFundamentalMeaning() { if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) { Log(USERLOG,"Fundamental Meanings:\r\n"); } int subject = 0; int object = 0; int verb = 0; for (int i = 1; i <= wordCount; ++i) { if (roles[i] & MAINVERB) { verb = i; int j = i; if (roles[i] & PASSIVE_VERB) // find subject as object { // he was attacked while (j-- > 0) { if (roles[j] & MAINSUBJECT) { object = j; break; } } j = i; while (++j <= wordCount) { // you were attacked by a monster if (!stricmp(wordStarts[j], "by")) { while (++j <= wordCount) { if (roles[j] & OBJECT2) { subject = j; break; } } break; } } } // active voice. we either have a subject or implied command (at 1) else // object is object { while (++j <= wordCount) { if (roles[j] & MAINOBJECT) { object = j; break; } } j = i; while (--j > 0) // find subject { if (roles[j] & MAINSUBJECT) { subject = j; break; } } } break; } } if (verb) { ProcessSentenceConcepts(subject, verb, object); ProcessPendingConcepts(); } } void MarkAllImpliedWords(bool limitnlp) { int i; pendingConceptList = NULL; for (i = 1; i <= wordCount; ++i) capState[i] = IsUpperCase(*wordStarts[i]); // note cap state failFired = false; TagIt(); // pos tag and maybe parse if (prepareMode == POS_MODE || tmpPrepareMode == POS_MODE || prepareMode == PENN_MODE || prepareMode == POSVERIFY_MODE || prepareMode == POSTIME_MODE) { return; } if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) { char* bad = GetUserVariable("$$cs_badspell", false, true); // spelling says this user is a mess if (*bad) Log(USERLOG,"\r\nNLP Suppressed by spellcheck.\r\n"); else Log(USERLOG,"\r\nConcepts %s: \r\n",language); } if (showMark) Log(ECHOUSERLOG, (char*)"----------------\r\n"); markLength = 0; // now mark every word in all seen for (i = 1; i <= wordCount; ++i) // mark that we have found this word, either in original or canonical form { marklimit = 0; // per word scan limit if (i == startSentence && upperCount > 10 && lowerCount < 5) MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord("~shout")), i, i); char* original = wordStarts[i]; if (!*original) continue; // ignore this if (!wordCanonical[i] || !*wordCanonical[i]) wordCanonical[i] = original; // in case failure below if (showMark) Log(ECHOUSERLOG, (char*)"\r\n"); if (trace & (TRACE_HIERARCHY | TRACE_PREPARE) || prepareMode == PREPARE_MODE) Log(USERLOG,"%d: %s (raw):\r\n", i, original); uint64 flags = posValues[i]; WORDP D = originalLower[i] ? originalLower[i] : originalUpper[i]; // one of them MUST have been set if (!D) D = StoreWord(original,AS_IS); // just so we can't fail later // put back non-tagger generalized forms of bits if (flags & NOUN_BITS) flags |= NOUN; if (flags & (VERB_BITS | NOUN_INFINITIVE | NOUN_GERUND)) flags |= VERB; if (flags & ADJECTIVE_BITS) flags |= ADJECTIVE | (allOriginalWordBits[i] & (MORE_FORM | MOST_FORM)); if (flags & NOUN_ADJECTIVE) flags |= (allOriginalWordBits[i] & (MORE_FORM | MOST_FORM)) | ADJECTIVE_NORMAL | ADJECTIVE; // what actress is the *prettiest -- be NOUN OR ADJECTIVE if (flags & ADVERB) flags |= ADVERB | (allOriginalWordBits[i] & (MORE_FORM | MOST_FORM)); if (D->properties & CURRENCY) flags |= CURRENCY; if (D->systemFlags & ORDINAL) { flags |= PLACE_NUMBER; AddParseBits(D, QUANTITY_NOUN); } if (!stricmp(wordCanonical[i], (char*)"be")) { if (!stricmp(original, (char*)"am") || !stricmp(original, (char*)"was")) flags |= SINGULAR_PERSON; } if (flags & NOUN_INFINITIVE && !(flags & NOUN_SINGULAR)) // transcribe back to verb only, leaving noun_infinitive status and not verb tense status { flags &= -1 ^ NOUN; // but still a noun_infinitive flags |= VERB; } finalPosValues[i] = flags; // these are what we finally decided were correct pos flags from tagger if (wordStarts[i][1] && (wordStarts[i][1] == ':' || wordStarts[i][2] == ':')) // time info 1:30 or 11:30 { if (originalLower[i] && IsDigit(wordStarts[i][0]) && IsDigit(wordStarts[i][3])) { AddSystemFlag(D, ACTUAL_TIME); } } WORDP OL = originalLower[i]; WORDP CL = canonicalLower[i]; WORDP OU = originalUpper[i]; WORDP CU = canonicalUpper[i]; if (tokenControl & TOKENIZE_BY_CHARACTER) { // canonical is same as original when doing ideographic CL = OL; CU = OU; } if (!CU && original[1]) // dont convert single letters to upper case "a" if it hasnt already decided its not a determiner { CU = FindWord(original, 0, UPPERCASE_LOOKUP); // try to find an upper to go with it, in case we can use that, but not as a human name if (OU) { ; } // it was originally uppercase or there is no lower case meaning else if (finalPosValues[i] & IDIOM) { ; } // keep if idiom else if (CU && CU->properties & (NOUN_FIRSTNAME | NOUN_HUMAN)) CU = NULL; // remove accidental names else if (CU && !CU->properties && !(CU->systemFlags & PATTERN_WORD)) CU = NULL; // there is no use for this (maybe only a sequence head) } if (!(finalPosValues[i] & (NOUN_BITS | ADJECTIVE_NOUN | IDIOM))) CU = OU = NULL; // cannot be upper case // WE need to mark the words themselves, before any inferred sets // because user may use EXCLUDE on an inferred set // both twitter usernames and hashtags are alphanumberic or _ // https://help.twitter.com/en/managing-your-account/twitter-username-rules // https://www.hashtags.org/featured/what-characters-can-a-hashtag-include/ if ((*wordStarts[i] == '@' || *wordStarts[i] == '#') && strlen(wordStarts[i]) > 2) { char* ptr = wordStarts[i]; bool hasAlpha = false; bool hasFirstAlpha = IsAlphaUTF8(*(ptr + 1)); while (*++ptr) { if (!IsDigit(*ptr) && !IsAlphaUTF8(*ptr) && *ptr != '_') break; if (IsAlphaUTF8(*ptr)) hasAlpha = true; } if (!*ptr && hasAlpha) { if (*wordStarts[i] == '@') MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord("~twitter_name")), i, i); if (*wordStarts[i] == '#' && hasFirstAlpha) MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord("~hashtag_label")), i, i); } } // detect a filename if (IsFileName(wordStarts[i])) { MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord("~filename")), i, i, FIXED); } // detect an emoji shortcode if (IsEmojiShortCode(wordStarts[i])) { MarkMeaningAndImplications(0, 0,MakeMeaning(StoreWord("~emoji")),i,i); } // detect acronym char* ptrx = wordStarts[i]; while (*++ptrx) { if (!IsUpperCase(*ptrx) && *ptrx != '&') break; } if (!*ptrx && wordStarts[i][1]) { bool ok = true; if (wordStarts[i - 1] && IsUpperCase(wordStarts[i - 1][0])) ok = false; if (wordStarts[i + 1] && IsUpperCase(wordStarts[i + 1][0])) ok = false; if (ok) MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord("~capacronym")), i, i); } // mark general number property -- (datezone can be marked noun_proper_singular) // adjective noun January 18, 2017 9:00 am if (finalPosValues[i] & (ADJECTIVE_NOUN | NOUN_PROPER_SINGULAR)) // a date can become an idiom, marking it as a proper noun and not a number { if (IsDigit(*wordStarts[i]) && IsDigit(wordStarts[i][1]) && IsDigit(wordStarts[i][2]) && IsDigit(wordStarts[i][3]) && !wordStarts[i][4]) MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~yearnumber")), i, i); } if (IsDate(wordStarts[i])) { MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~dateinfo")), i, i, FIXED, false); MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~formatteddate")), i, i, FIXED, false); } int number = IsNumber(wordStarts[i], numberStyle); if (number && number != NOT_A_NUMBER) { if (!wordCanonical[i][1] || !wordCanonical[i][2]) // 2 digit or 1 digit { int n = atoi(wordCanonical[i]); if (n > 0 && n < 32 && *wordStarts[i] != '$') MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~daynumber")), i, i); } if (IsDigit(*wordStarts[i]) && IsDigit(wordStarts[i][1]) && IsDigit(wordStarts[i][2]) && IsDigit(wordStarts[i][3]) && !wordStarts[i][4]) MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~yearnumber")), i, i); MarkMeaningAndImplications(0, 0, Mnumber, i, i); // let's mark kind of number also if (strchr(wordCanonical[i], '.')) MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord("~float")), i, i, CANONICAL); else { MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord("~integer")), i, i, CANONICAL); if (*wordStarts[i] != '-') MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord("~positiveInteger")), i, i, CANONICAL); else MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord("~negativeinteger")), i, i, CANONICAL); } // handle finding fractions as 3 token sequence mark as placenumber if (i < wordCount && *wordStarts[i + 1] == '/' && wordStarts[i + 1][1] == 0 && IsDigitWord(wordStarts[i + 2], numberStyle)) { MarkMeaningAndImplications(0, 0, MakeMeaning(Dplacenumber), i, i); if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) Log(USERLOG,"=%s/%s \r\n", wordStarts[i], wordStarts[i + 2]); } else if (IsPlaceNumber(wordStarts[i], numberStyle)) // finalPosValues[i] & (NOUN_NUMBER | ADJECTIVE_NUMBER) { MarkMeaningAndImplications(0, 0, MakeMeaning(Dplacenumber), i, i); } // special temperature property char c = GetTemperatureLetter(original); if (c) { if (c == 'F') MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord((char*)"~fahrenheit")), i, i); else if (c == 'C') MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord((char*)"~celsius")), i, i); else if (c == 'K') MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord((char*)"~kelvin")), i, i); char number1[MAX_WORD_SIZE]; sprintf(number1, (char*)"%d", atoi(original)); WORDP canon = StoreWord(number1, (NOUN_NUMBER | ADJECTIVE_NUMBER, NOUN_NODETERMINER)); if (canon) wordCanonical[i] = canon->word; } // special currency property char* number1; unsigned char* currency = GetCurrency((unsigned char*)wordStarts[i], number1); if (currency) { MarkMeaningAndImplications(0, 0, Mmoney, i, i); char* set = IsTextCurrency((char*)currency, NULL); if (set) // should not fail { MEANING M = MakeMeaning(FindWord(set)); MarkMeaningAndImplications(0, 0, M, i, i); } } } else if (IsNumber(wordStarts[i], numberStyle) == WORD_NUMBER) { MarkMeaningAndImplications(0, 0, Mnumber, i, i); } if (FindTopicIDByName(wordStarts[i])) MarkMeaningAndImplications(0, 0, MakeMeaning(Dtopic), i, i); if (CL && CL == DunknownWord) // allow unknown proper names to be marked unknown { MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord(original)), i, i); // allowed word MarkMeaningAndImplications(0, 0, MakeMeaning(Dunknown), i, i); // unknown word } // note "bank teller" we want bank to have recognizion of its noun meaning in concepts - must do FIRST as noun, since adjective value is abnormal unsigned int restriction = (unsigned int)(finalPosValues[i] & BASIC_POS); if (finalPosValues[i] & ADJECTIVE_NOUN) { StdMark(MakeTypedMeaning(OL, 0, NOUN), i, i, FIXED); // mark word as a noun } else // mark the original form (prefer lower to upper) { WORDP raw = OL; if (!raw) raw = OU; if (!raw) raw = StoreWord(original); StdMark(MakeTypedMeaning(raw, 0, restriction), i, i, FIXED); } markLength = 0; if (IS_NEW_WORD(OU) && (OL || CL)) { ; } // uppercase original was unknown and we have lower case forms, ignore upper. else { if (finalPosValues[i] & ADJECTIVE_NOUN) StdMark(MakeTypedMeaning(OU, 0, NOUN), i, i, FIXED); // mark word as a noun first, adjective is not normal else StdMark(MakeTypedMeaning(OU, 0, restriction), i, i, FIXED); } bool hasCL = CL ? true : false; if (!CL) CL = CU; if (CL) wordCanonical[i] = CL->word; // original meanings lowercase else if (!wordCanonical[i]) wordCanonical[i] = (char*)""; if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) { Log(USERLOG,"%d: %s (canonical):\r\n", i, wordCanonical[i]); // original meanings lowercase } // canonical word if (CL == OL) {} // dont bother else if (finalPosValues[i] & ADJECTIVE_BITS && allOriginalWordBits[i] & (VERB_PRESENT_PARTICIPLE | VERB_PAST_PARTICIPLE)) // see if adj is verb as canonical base - "ing and ed" forms { StdMark(MakeTypedMeaning(CL, 0, VERB), i, i, CANONICAL); } else if (finalPosValues[i] & (NOUN_GERUND | NOUN_INFINITIVE)) { StdMark(MakeTypedMeaning(CL, 0, VERB), i, i, CANONICAL); } else if (finalPosValues[i] & ADJECTIVE_NOUN) { StdMark(MakeTypedMeaning(CL, 0, NOUN), i, i, CANONICAL); StdMark(MakeTypedMeaning(CU, 0, NOUN), i, i, CANONICAL); } else StdMark(MakeTypedMeaning(CL, 0, (unsigned int)(finalPosValues[i] & BASIC_POS)), i, i, CANONICAL); markLength = 0; if (hasCL && CU != OU) // not yet done CU since had CL { // mark upper case canonical StdMark(MakeTypedMeaning(CU, 0, NOUN), i, i, CANONICAL); } // mark ancillary stuff MarkMeaningAndImplications(0, 0, MakeMeaning(wordTag[i]), i, i); // may do nothing MarkTags(i); MarkMeaningAndImplications(0, 0, MakeMeaning(wordRole[i]), i, i); // may do nothing #ifndef DISCARDPARSER MarkRoles(i); #endif if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) Log(USERLOG," "); // close canonical form uppercase markLength = 0; // peer into multiword expressions (noncanonical), in case user is emphasizing something so we dont lose the basic match on words // accept both upper and lower case forms . // But DONT peer into something proper like "Moby Dick" unsigned int n = BurstWord(wordStarts[i]); // peering INSIDE a single token.... WORDP E; if (tokenControl & NO_WITHIN || n == 1); // dont peek within hypenated words else if (finalPosValues[i] & (NOUN_PROPER_SINGULAR | NOUN_PROPER_PLURAL)) // mark first and last word, if they are recognized words { char* w = GetBurstWord(0); WORDP D1 = FindWord(w); w = GetBurstWord(n - 1); if (D1 && allOriginalWordBits[i] & NOUN_HUMAN) MarkMeaningAndImplications(0, 0, MakeMeaning(D1), i, i); // allow first name recognition with human names WORDP D2 = FindWord(w, 0, LOWERCASE_LOOKUP); if (D2 && (D2->properties & (NOUN | VERB | ADJECTIVE | ADVERB) || D->systemFlags & PATTERN_WORD)) MarkMeaningAndImplications(0, 0, MakeMeaning(D2), i, i); // allow final word as in "Bill Gates" "United States of America" , D2 = FindWord(w, 0, UPPERCASE_LOOKUP); if (D2 && (D2->properties & (NOUN | VERB | ADJECTIVE | ADVERB) || D->systemFlags & PATTERN_WORD)) MarkMeaningAndImplications(0, 0, MakeMeaning(D2), i, i); // allow final word as in "Bill Gates" "United States of America" , } else if (n >= 2 && n <= 4) // longer than 4 is not emphasis, its a sentence - we do not peer into titles { static char words[5][MAX_WORD_SIZE]; unsigned int k; for (k = 0; k < n; ++k) strcpy(words[k], GetBurstWord(k)); // need local copy since burstwords might be called again.. for (unsigned int m = n - 1; m < n; ++m) // just last word since common form "bank teller" { unsigned int prior = (m == (n - 1)) ? i : (i - 1); // -1 marks its word match INSIDE a string before the last word, allow it to see last word still E = FindWord(words[m], 0, LOWERCASE_LOOKUP); if (E) StdMark(MakeMeaning(E), i, prior, FIXED); } } // now look on either side of a hypenated word char* hypen = strchr(wordStarts[i], '-'); if (!number && hypen && hypen != wordStarts[i] && hypen[1]) { MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord(hypen)), i, i); // post form -colored char word[MAX_WORD_SIZE]; strcpy(word, wordStarts[i]); word[hypen + 1 - wordStarts[i]] = 0; MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord(word)), i, i); // pre form light- } // now look on either side of a "plussed" word char* plus = strchr(wordStarts[i], '+'); if (!number && plus && plus != wordStarts[i] && plus[1]) { MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord(plus)), i, i); // post form +05:30 char word[MAX_WORD_SIZE]; strcpy(word, wordStarts[i]); word[plus + 1 - wordStarts[i]] = 0; MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord(word)), i, i); // pre form word+ } D = (CL) ? CL : CU; // best recognition if (!D) D = StoreWord(original); // just so we can't fail later char* last; if (!(tokenControl & NO_WITHIN) && D->properties & NOUN && !(D->internalBits & UPPERCASE_HASH) && (last = strrchr(D->word, '_')) && finalPosValues[i] & NOUN) StdMark(MakeMeaning(FindWord(last + 1, 0)), i, i, CANONICAL); // composite noun, store last word as referenced also // ALL Foreign words detectable by utf8 char D = (OL) ? OL : OU; if (!D) D = StoreWord(original); // just so we can't fail later if (D->internalBits & UTF8) MarkMeaningAndImplications(0, 0, MakeMeaning(StoreWord((char*)"~utf8")), i, i); if (D->internalBits & UPPERCASE_HASH && D->length > 1 && !stricmp(language, "english")) MarkMeaningAndImplications(0, 0, MakeMeaning(Dpropername), i, i); // historical - internal is uppercase D = FindWord(wordStarts[i]); if (*wordStarts[i] == 'I' && !wordStarts[i][1]) { ; } // ignore "I" else if (D && D->internalBits & UPPERCASE_HASH && tokenControl & MARK_LOWER) { char word[MAX_WORD_SIZE]; MakeLowerCopy(word, D->word); if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) Log(USERLOG,"%d: %s (lower): ", i, word); // original meanings lowercase D = StoreWord(word); StdMark(MakeMeaning(D), i, i, CANONICAL); if (trace & TRACE_PREPARE || prepareMode == PREPARE_MODE) Log(USERLOG,"\r\n"); } ProcessPendingConcepts(); } // check for repeat input by user - but only if more than 2 words or are unknown (we dont mind yes, ok, etc repeated) // track how many repeats, for escalating response unsigned int sentenceLength = endSentence - startSentence + 1; bool notbrief = (sentenceLength > 2); if (sentenceLength == 1 && !FindWord(wordStarts[startSentence])) notbrief = true; unsigned int counter = 0; if (notbrief && humanSaidIndex) for (int j = 0; j < (int)(humanSaidIndex - 1); ++j) { if (strlen(humanSaid[j]) > 5 && !stricmp(humanSaid[humanSaidIndex - 1], humanSaid[j])) // he repeats himself { ++counter; char buf[100]; strcpy(buf, (char*)"~repeatinput"); buf[12] = (char)('0' + counter); buf[13] = 0; MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord(buf, 0, PRIMARY_CASE_ALLOWED)), 1, 1); // you can see how many times } } // now see if he is repeating stuff I said counter = 0; if (sentenceLength > 2) for (int j = 0; j < (int)chatbotSaidIndex; ++j) { if (humanSaidIndex && strlen(chatbotSaid[j]) > 5 && !stricmp(humanSaid[humanSaidIndex - 1], chatbotSaid[j])) // he repeats me { if (counter < sentenceLength) ++counter; MarkMeaningAndImplications(0, 0, MakeMeaning(FindWord((char*)"~repeatme", 0, PRIMARY_CASE_ALLOWED)), counter, counter); // you can see how many times } } // handle phrases now markLength = 0; if (!limitnlp) SetSequenceStamp(); // sequences of words ProcessPendingConcepts(); if (hasFundamentalMeanings && !limitnlp) MarkFundamentalMeaning(); ExecuteConceptPatterns(); // now use concept patterns }