Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* GRAPHITE2 LICENSING |
michael@0 | 2 | |
michael@0 | 3 | Copyright 2010, SIL International |
michael@0 | 4 | All rights reserved. |
michael@0 | 5 | |
michael@0 | 6 | This library is free software; you can redistribute it and/or modify |
michael@0 | 7 | it under the terms of the GNU Lesser General Public License as published |
michael@0 | 8 | by the Free Software Foundation; either version 2.1 of License, or |
michael@0 | 9 | (at your option) any later version. |
michael@0 | 10 | |
michael@0 | 11 | This program is distributed in the hope that it will be useful, |
michael@0 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
michael@0 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
michael@0 | 14 | Lesser General Public License for more details. |
michael@0 | 15 | |
michael@0 | 16 | You should also have received a copy of the GNU Lesser General Public |
michael@0 | 17 | License along with this library in the file named "LICENSE". |
michael@0 | 18 | If not, write to the Free Software Foundation, 51 Franklin Street, |
michael@0 | 19 | Suite 500, Boston, MA 02110-1335, USA or visit their web page on the |
michael@0 | 20 | internet at http://www.fsf.org/licenses/lgpl.html. |
michael@0 | 21 | |
michael@0 | 22 | Alternatively, the contents of this file may be used under the terms of the |
michael@0 | 23 | Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public |
michael@0 | 24 | License, as published by the Free Software Foundation, either version 2 |
michael@0 | 25 | of the License or (at your option) any later version. |
michael@0 | 26 | */ |
michael@0 | 27 | #include "inc/Main.h" |
michael@0 | 28 | #include "inc/debug.h" |
michael@0 | 29 | #include "inc/Endian.h" |
michael@0 | 30 | #include "inc/Pass.h" |
michael@0 | 31 | #include <cstring> |
michael@0 | 32 | #include <cstdlib> |
michael@0 | 33 | #include <cassert> |
michael@0 | 34 | #include "inc/Segment.h" |
michael@0 | 35 | #include "inc/Code.h" |
michael@0 | 36 | #include "inc/Rule.h" |
michael@0 | 37 | #include "inc/Error.h" |
michael@0 | 38 | |
michael@0 | 39 | using namespace graphite2; |
michael@0 | 40 | using vm::Machine; |
michael@0 | 41 | typedef Machine::Code Code; |
michael@0 | 42 | |
michael@0 | 43 | |
michael@0 | 44 | Pass::Pass() |
michael@0 | 45 | : m_silf(0), |
michael@0 | 46 | m_cols(0), |
michael@0 | 47 | m_rules(0), |
michael@0 | 48 | m_ruleMap(0), |
michael@0 | 49 | m_startStates(0), |
michael@0 | 50 | m_transitions(0), |
michael@0 | 51 | m_states(0), |
michael@0 | 52 | m_flags(0), |
michael@0 | 53 | m_iMaxLoop(0), |
michael@0 | 54 | m_numGlyphs(0), |
michael@0 | 55 | m_numRules(0), |
michael@0 | 56 | m_numStates(0), |
michael@0 | 57 | m_numTransition(0), |
michael@0 | 58 | m_numSuccess(0), |
michael@0 | 59 | m_numColumns(0), |
michael@0 | 60 | m_minPreCtxt(0), |
michael@0 | 61 | m_maxPreCtxt(0) |
michael@0 | 62 | { |
michael@0 | 63 | } |
michael@0 | 64 | |
michael@0 | 65 | Pass::~Pass() |
michael@0 | 66 | { |
michael@0 | 67 | free(m_cols); |
michael@0 | 68 | free(m_startStates); |
michael@0 | 69 | free(m_transitions); |
michael@0 | 70 | free(m_states); |
michael@0 | 71 | free(m_ruleMap); |
michael@0 | 72 | |
michael@0 | 73 | delete [] m_rules; |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t subtable_base, GR_MAYBE_UNUSED Face & face, Error &e) |
michael@0 | 77 | { |
michael@0 | 78 | const byte * p = pass_start, |
michael@0 | 79 | * const pass_end = p + pass_length; |
michael@0 | 80 | size_t numRanges; |
michael@0 | 81 | |
michael@0 | 82 | if (e.test(pass_length < 40, E_BADPASSLENGTH)) return face.error(e); |
michael@0 | 83 | // Read in basic values |
michael@0 | 84 | m_flags = be::read<byte>(p); |
michael@0 | 85 | m_iMaxLoop = be::read<byte>(p); |
michael@0 | 86 | be::skip<byte>(p,2); // skip maxContext & maxBackup |
michael@0 | 87 | m_numRules = be::read<uint16>(p); |
michael@0 | 88 | be::skip<uint16>(p); // fsmOffset - not sure why we would want this |
michael@0 | 89 | const byte * const pcCode = pass_start + be::read<uint32>(p) - subtable_base, |
michael@0 | 90 | * const rcCode = pass_start + be::read<uint32>(p) - subtable_base, |
michael@0 | 91 | * const aCode = pass_start + be::read<uint32>(p) - subtable_base; |
michael@0 | 92 | be::skip<uint32>(p); |
michael@0 | 93 | m_numStates = be::read<uint16>(p); |
michael@0 | 94 | m_numTransition = be::read<uint16>(p); |
michael@0 | 95 | m_numSuccess = be::read<uint16>(p); |
michael@0 | 96 | m_numColumns = be::read<uint16>(p); |
michael@0 | 97 | numRanges = be::read<uint16>(p); |
michael@0 | 98 | be::skip<uint16>(p, 3); // skip searchRange, entrySelector & rangeShift. |
michael@0 | 99 | assert(p - pass_start == 40); |
michael@0 | 100 | // Perform some sanity checks. |
michael@0 | 101 | if ( e.test(m_numTransition > m_numStates, E_BADNUMTRANS) |
michael@0 | 102 | || e.test(m_numSuccess > m_numStates, E_BADNUMSUCCESS) |
michael@0 | 103 | || e.test(m_numSuccess + m_numTransition < m_numStates, E_BADNUMSTATES) |
michael@0 | 104 | || e.test(numRanges == 0, E_NORANGES)) |
michael@0 | 105 | return face.error(e); |
michael@0 | 106 | |
michael@0 | 107 | m_successStart = m_numStates - m_numSuccess; |
michael@0 | 108 | if (e.test(p + numRanges * 6 - 4 > pass_end, E_BADPASSLENGTH)) return face.error(e); |
michael@0 | 109 | m_numGlyphs = be::peek<uint16>(p + numRanges * 6 - 4) + 1; |
michael@0 | 110 | // Calculate the start of various arrays. |
michael@0 | 111 | const byte * const ranges = p; |
michael@0 | 112 | be::skip<uint16>(p, numRanges*3); |
michael@0 | 113 | const byte * const o_rule_map = p; |
michael@0 | 114 | be::skip<uint16>(p, m_numSuccess + 1); |
michael@0 | 115 | |
michael@0 | 116 | // More sanity checks |
michael@0 | 117 | if (e.test(reinterpret_cast<const byte *>(o_rule_map + m_numSuccess*sizeof(uint16)) > pass_end |
michael@0 | 118 | || p > pass_end, E_BADRULEMAPLEN)) |
michael@0 | 119 | return face.error(e); |
michael@0 | 120 | const size_t numEntries = be::peek<uint16>(o_rule_map + m_numSuccess*sizeof(uint16)); |
michael@0 | 121 | const byte * const rule_map = p; |
michael@0 | 122 | be::skip<uint16>(p, numEntries); |
michael@0 | 123 | |
michael@0 | 124 | if (e.test(p + 2*sizeof(uint8) > pass_end, E_BADPASSLENGTH)) return face.error(e); |
michael@0 | 125 | m_minPreCtxt = be::read<uint8>(p); |
michael@0 | 126 | m_maxPreCtxt = be::read<uint8>(p); |
michael@0 | 127 | if (e.test(m_minPreCtxt > m_maxPreCtxt, E_BADCTXTLENBOUNDS)) return face.error(e); |
michael@0 | 128 | const byte * const start_states = p; |
michael@0 | 129 | be::skip<int16>(p, m_maxPreCtxt - m_minPreCtxt + 1); |
michael@0 | 130 | const uint16 * const sort_keys = reinterpret_cast<const uint16 *>(p); |
michael@0 | 131 | be::skip<uint16>(p, m_numRules); |
michael@0 | 132 | const byte * const precontext = p; |
michael@0 | 133 | be::skip<byte>(p, m_numRules); |
michael@0 | 134 | be::skip<byte>(p); // skip reserved byte |
michael@0 | 135 | |
michael@0 | 136 | if (e.test(p + sizeof(uint16) > pass_end, E_BADCTXTLENS)) return face.error(e); |
michael@0 | 137 | const size_t pass_constraint_len = be::read<uint16>(p); |
michael@0 | 138 | const uint16 * const o_constraint = reinterpret_cast<const uint16 *>(p); |
michael@0 | 139 | be::skip<uint16>(p, m_numRules + 1); |
michael@0 | 140 | const uint16 * const o_actions = reinterpret_cast<const uint16 *>(p); |
michael@0 | 141 | be::skip<uint16>(p, m_numRules + 1); |
michael@0 | 142 | const byte * const states = p; |
michael@0 | 143 | be::skip<int16>(p, m_numTransition*m_numColumns); |
michael@0 | 144 | be::skip<byte>(p); // skip reserved byte |
michael@0 | 145 | if (e.test(p != pcCode, E_BADPASSCCODEPTR) || e.test(p >= pass_end, E_BADPASSLENGTH)) return face.error(e); |
michael@0 | 146 | be::skip<byte>(p, pass_constraint_len); |
michael@0 | 147 | if (e.test(p != rcCode, E_BADRULECCODEPTR) || e.test(p >= pass_end, E_BADPASSLENGTH) |
michael@0 | 148 | || e.test(size_t(rcCode - pcCode) != pass_constraint_len, E_BADCCODELEN)) return face.error(e); |
michael@0 | 149 | be::skip<byte>(p, be::peek<uint16>(o_constraint + m_numRules)); |
michael@0 | 150 | if (e.test(p != aCode, E_BADACTIONCODEPTR) || e.test(p >= pass_end, E_BADPASSLENGTH)) return face.error(e); |
michael@0 | 151 | be::skip<byte>(p, be::peek<uint16>(o_actions + m_numRules)); |
michael@0 | 152 | |
michael@0 | 153 | // We should be at the end or within the pass |
michael@0 | 154 | if (e.test(p > pass_end, E_BADPASSLENGTH)) return face.error(e); |
michael@0 | 155 | |
michael@0 | 156 | // Load the pass constraint if there is one. |
michael@0 | 157 | if (pass_constraint_len) |
michael@0 | 158 | { |
michael@0 | 159 | face.error_context(face.error_context() + 1); |
michael@0 | 160 | m_cPConstraint = vm::Machine::Code(true, pcCode, pcCode + pass_constraint_len, |
michael@0 | 161 | precontext[0], be::peek<uint16>(sort_keys), *m_silf, face); |
michael@0 | 162 | if (e.test(!m_cPConstraint, E_OUTOFMEM) |
michael@0 | 163 | || e.test(m_cPConstraint.status(), m_cPConstraint.status() + E_CODEFAILURE)) |
michael@0 | 164 | return face.error(e); |
michael@0 | 165 | face.error_context(face.error_context() - 1); |
michael@0 | 166 | } |
michael@0 | 167 | if (!readRanges(ranges, numRanges, e)) return face.error(e); |
michael@0 | 168 | if (!readRules(rule_map, numEntries, precontext, sort_keys, |
michael@0 | 169 | o_constraint, rcCode, o_actions, aCode, face, e)) return false; |
michael@0 | 170 | #ifdef GRAPHITE2_TELEMETRY |
michael@0 | 171 | telemetry::category _states_cat(face.tele.states); |
michael@0 | 172 | #endif |
michael@0 | 173 | return readStates(start_states, states, o_rule_map, face, e); |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | |
michael@0 | 177 | bool Pass::readRules(const byte * rule_map, const size_t num_entries, |
michael@0 | 178 | const byte *precontext, const uint16 * sort_key, |
michael@0 | 179 | const uint16 * o_constraint, const byte *rc_data, |
michael@0 | 180 | const uint16 * o_action, const byte * ac_data, |
michael@0 | 181 | Face & face, Error &e) |
michael@0 | 182 | { |
michael@0 | 183 | const byte * const ac_data_end = ac_data + be::peek<uint16>(o_action + m_numRules); |
michael@0 | 184 | const byte * const rc_data_end = rc_data + be::peek<uint16>(o_constraint + m_numRules); |
michael@0 | 185 | |
michael@0 | 186 | if (e.test(!(m_rules = new Rule [m_numRules]), E_OUTOFMEM)) return face.error(e); |
michael@0 | 187 | precontext += m_numRules; |
michael@0 | 188 | sort_key += m_numRules; |
michael@0 | 189 | o_constraint += m_numRules; |
michael@0 | 190 | o_action += m_numRules; |
michael@0 | 191 | |
michael@0 | 192 | // Load rules. |
michael@0 | 193 | const byte * ac_begin = 0, * rc_begin = 0, |
michael@0 | 194 | * ac_end = ac_data + be::peek<uint16>(o_action), |
michael@0 | 195 | * rc_end = rc_data + be::peek<uint16>(o_constraint); |
michael@0 | 196 | Rule * r = m_rules + m_numRules - 1; |
michael@0 | 197 | for (size_t n = m_numRules; n; --n, --r, ac_end = ac_begin, rc_end = rc_begin) |
michael@0 | 198 | { |
michael@0 | 199 | face.error_context((face.error_context() & 0xFFFF00) + EC_ARULE + ((n - 1) << 24)); |
michael@0 | 200 | r->preContext = *--precontext; |
michael@0 | 201 | r->sort = be::peek<uint16>(--sort_key); |
michael@0 | 202 | #ifndef NDEBUG |
michael@0 | 203 | r->rule_idx = n - 1; |
michael@0 | 204 | #endif |
michael@0 | 205 | if (r->sort > 63 || r->preContext >= r->sort || r->preContext > m_maxPreCtxt || r->preContext < m_minPreCtxt) |
michael@0 | 206 | return false; |
michael@0 | 207 | ac_begin = ac_data + be::peek<uint16>(--o_action); |
michael@0 | 208 | --o_constraint; |
michael@0 | 209 | rc_begin = be::peek<uint16>(o_constraint) ? rc_data + be::peek<uint16>(o_constraint) : rc_end; |
michael@0 | 210 | |
michael@0 | 211 | if (ac_begin > ac_end || ac_begin > ac_data_end || ac_end > ac_data_end |
michael@0 | 212 | || rc_begin > rc_end || rc_begin > rc_data_end || rc_end > rc_data_end) |
michael@0 | 213 | return false; |
michael@0 | 214 | r->action = new vm::Machine::Code(false, ac_begin, ac_end, r->preContext, r->sort, *m_silf, face); |
michael@0 | 215 | r->constraint = new vm::Machine::Code(true, rc_begin, rc_end, r->preContext, r->sort, *m_silf, face); |
michael@0 | 216 | |
michael@0 | 217 | if (e.test(!r->action || !r->constraint, E_OUTOFMEM) |
michael@0 | 218 | || e.test(r->action->status() != Code::loaded, r->action->status() + E_CODEFAILURE) |
michael@0 | 219 | || e.test(r->constraint->status() != Code::loaded, r->constraint->status() + E_CODEFAILURE) |
michael@0 | 220 | || e.test(!r->constraint->immutable(), E_MUTABLECCODE)) |
michael@0 | 221 | return face.error(e); |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | // Load the rule entries map |
michael@0 | 225 | face.error_context((face.error_context() & 0xFFFF00) + EC_APASS); |
michael@0 | 226 | RuleEntry * re = m_ruleMap = gralloc<RuleEntry>(num_entries); |
michael@0 | 227 | if (e.test(!re, E_OUTOFMEM)) return face.error(e); |
michael@0 | 228 | for (size_t n = num_entries; n; --n, ++re) |
michael@0 | 229 | { |
michael@0 | 230 | const ptrdiff_t rn = be::read<uint16>(rule_map); |
michael@0 | 231 | if (e.test(rn >= m_numRules, E_BADRULENUM)) return face.error(e); |
michael@0 | 232 | re->rule = m_rules + rn; |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | return true; |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | static int cmpRuleEntry(const void *a, const void *b) { return (*(RuleEntry *)a < *(RuleEntry *)b ? -1 : |
michael@0 | 239 | (*(RuleEntry *)b < *(RuleEntry *)a ? 1 : 0)); } |
michael@0 | 240 | |
michael@0 | 241 | bool Pass::readStates(const byte * starts, const byte *states, const byte * o_rule_map, GR_MAYBE_UNUSED Face & face, Error &e) |
michael@0 | 242 | { |
michael@0 | 243 | #ifdef GRAPHITE2_TELEMETRY |
michael@0 | 244 | telemetry::category _states_cat(face.tele.starts); |
michael@0 | 245 | #endif |
michael@0 | 246 | m_startStates = gralloc<uint16>(m_maxPreCtxt - m_minPreCtxt + 1); |
michael@0 | 247 | #ifdef GRAPHITE2_TELEMETRY |
michael@0 | 248 | telemetry::set_category(face.tele.states); |
michael@0 | 249 | #endif |
michael@0 | 250 | m_states = gralloc<State>(m_numStates); |
michael@0 | 251 | #ifdef GRAPHITE2_TELEMETRY |
michael@0 | 252 | telemetry::set_category(face.tele.transitions); |
michael@0 | 253 | #endif |
michael@0 | 254 | m_transitions = gralloc<uint16>(m_numTransition * m_numColumns); |
michael@0 | 255 | |
michael@0 | 256 | if (e.test(!m_startStates || !m_states || !m_transitions, E_OUTOFMEM)) return face.error(e); |
michael@0 | 257 | // load start states |
michael@0 | 258 | for (uint16 * s = m_startStates, |
michael@0 | 259 | * const s_end = s + m_maxPreCtxt - m_minPreCtxt + 1; s != s_end; ++s) |
michael@0 | 260 | { |
michael@0 | 261 | *s = be::read<uint16>(starts); |
michael@0 | 262 | if (e.test(*s >= m_numStates, E_BADSTATE)) |
michael@0 | 263 | { |
michael@0 | 264 | face.error_context((face.error_context() & 0xFFFF00) + EC_ASTARTS + ((s - m_startStates) << 24)); |
michael@0 | 265 | return face.error(e); // true; |
michael@0 | 266 | } |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | // load state transition table. |
michael@0 | 270 | for (uint16 * t = m_transitions, |
michael@0 | 271 | * const t_end = t + m_numTransition*m_numColumns; t != t_end; ++t) |
michael@0 | 272 | { |
michael@0 | 273 | *t = be::read<uint16>(states); |
michael@0 | 274 | if (e.test(*t >= m_numStates, E_BADSTATE)) |
michael@0 | 275 | { |
michael@0 | 276 | face.error_context((face.error_context() & 0xFFFF00) + EC_ATRANS + (((t - m_transitions) / m_numColumns) << 24)); |
michael@0 | 277 | return face.error(e); |
michael@0 | 278 | } |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | State * s = m_states, |
michael@0 | 282 | * const success_begin = m_states + m_numStates - m_numSuccess; |
michael@0 | 283 | const RuleEntry * rule_map_end = m_ruleMap + be::peek<uint16>(o_rule_map + m_numSuccess*sizeof(uint16)); |
michael@0 | 284 | for (size_t n = m_numStates; n; --n, ++s) |
michael@0 | 285 | { |
michael@0 | 286 | RuleEntry * const begin = s < success_begin ? 0 : m_ruleMap + be::read<uint16>(o_rule_map), |
michael@0 | 287 | * const end = s < success_begin ? 0 : m_ruleMap + be::peek<uint16>(o_rule_map); |
michael@0 | 288 | |
michael@0 | 289 | if (e.test(begin >= rule_map_end || end > rule_map_end || begin > end, E_BADRULEMAPPING)) |
michael@0 | 290 | { |
michael@0 | 291 | face.error_context((face.error_context() & 0xFFFF00) + EC_ARULEMAP + (n << 24)); |
michael@0 | 292 | return face.error(e); |
michael@0 | 293 | } |
michael@0 | 294 | s->rules = begin; |
michael@0 | 295 | s->rules_end = (end - begin <= FiniteStateMachine::MAX_RULES)? end : |
michael@0 | 296 | begin + FiniteStateMachine::MAX_RULES; |
michael@0 | 297 | qsort(begin, end - begin, sizeof(RuleEntry), &cmpRuleEntry); |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | return true; |
michael@0 | 301 | } |
michael@0 | 302 | |
michael@0 | 303 | bool Pass::readRanges(const byte * ranges, size_t num_ranges, Error &e) |
michael@0 | 304 | { |
michael@0 | 305 | m_cols = gralloc<uint16>(m_numGlyphs); |
michael@0 | 306 | if (e.test(!m_cols, E_OUTOFMEM)) return false; |
michael@0 | 307 | memset(m_cols, 0xFF, m_numGlyphs * sizeof(uint16)); |
michael@0 | 308 | for (size_t n = num_ranges; n; --n) |
michael@0 | 309 | { |
michael@0 | 310 | uint16 * ci = m_cols + be::read<uint16>(ranges), |
michael@0 | 311 | * ci_end = m_cols + be::read<uint16>(ranges) + 1, |
michael@0 | 312 | col = be::read<uint16>(ranges); |
michael@0 | 313 | |
michael@0 | 314 | if (e.test(ci >= ci_end || ci_end > m_cols+m_numGlyphs || col >= m_numColumns, E_BADRANGE)) |
michael@0 | 315 | return false; |
michael@0 | 316 | |
michael@0 | 317 | // A glyph must only belong to one column at a time |
michael@0 | 318 | while (ci != ci_end && *ci == 0xffff) |
michael@0 | 319 | *ci++ = col; |
michael@0 | 320 | |
michael@0 | 321 | if (e.test(ci != ci_end, E_BADRANGE)) |
michael@0 | 322 | return false; |
michael@0 | 323 | } |
michael@0 | 324 | return true; |
michael@0 | 325 | } |
michael@0 | 326 | |
michael@0 | 327 | |
michael@0 | 328 | void Pass::runGraphite(Machine & m, FiniteStateMachine & fsm) const |
michael@0 | 329 | { |
michael@0 | 330 | Slot *s = m.slotMap().segment.first(); |
michael@0 | 331 | if (!s || !testPassConstraint(m)) return; |
michael@0 | 332 | Slot *currHigh = s->next(); |
michael@0 | 333 | |
michael@0 | 334 | #if !defined GRAPHITE2_NTRACING |
michael@0 | 335 | if (fsm.dbgout) *fsm.dbgout << "rules" << json::array; |
michael@0 | 336 | json::closer rules_array_closer(fsm.dbgout); |
michael@0 | 337 | #endif |
michael@0 | 338 | |
michael@0 | 339 | m.slotMap().highwater(currHigh); |
michael@0 | 340 | int lc = m_iMaxLoop; |
michael@0 | 341 | do |
michael@0 | 342 | { |
michael@0 | 343 | findNDoRule(s, m, fsm); |
michael@0 | 344 | if (s && (m.slotMap().highpassed() || s == m.slotMap().highwater() || --lc == 0)) { |
michael@0 | 345 | if (!lc) |
michael@0 | 346 | { |
michael@0 | 347 | // if (dbgout) *dbgout << json::item << json::flat << rule_event(-1, s, 1); |
michael@0 | 348 | s = m.slotMap().highwater(); |
michael@0 | 349 | } |
michael@0 | 350 | lc = m_iMaxLoop; |
michael@0 | 351 | if (s) |
michael@0 | 352 | m.slotMap().highwater(s->next()); |
michael@0 | 353 | } |
michael@0 | 354 | } while (s); |
michael@0 | 355 | } |
michael@0 | 356 | |
michael@0 | 357 | bool Pass::runFSM(FiniteStateMachine& fsm, Slot * slot) const |
michael@0 | 358 | { |
michael@0 | 359 | fsm.reset(slot, m_maxPreCtxt); |
michael@0 | 360 | if (fsm.slots.context() < m_minPreCtxt) |
michael@0 | 361 | return false; |
michael@0 | 362 | |
michael@0 | 363 | uint16 state = m_startStates[m_maxPreCtxt - fsm.slots.context()]; |
michael@0 | 364 | uint8 free_slots = SlotMap::MAX_SLOTS; |
michael@0 | 365 | do |
michael@0 | 366 | { |
michael@0 | 367 | fsm.slots.pushSlot(slot); |
michael@0 | 368 | if (--free_slots == 0 |
michael@0 | 369 | || slot->gid() >= m_numGlyphs |
michael@0 | 370 | || m_cols[slot->gid()] == 0xffffU |
michael@0 | 371 | || state >= m_numTransition) |
michael@0 | 372 | return free_slots != 0; |
michael@0 | 373 | |
michael@0 | 374 | const uint16 * transitions = m_transitions + state*m_numColumns; |
michael@0 | 375 | state = transitions[m_cols[slot->gid()]]; |
michael@0 | 376 | if (state >= m_successStart) |
michael@0 | 377 | fsm.rules.accumulate_rules(m_states[state]); |
michael@0 | 378 | |
michael@0 | 379 | slot = slot->next(); |
michael@0 | 380 | } while (state != 0 && slot); |
michael@0 | 381 | |
michael@0 | 382 | fsm.slots.pushSlot(slot); |
michael@0 | 383 | return true; |
michael@0 | 384 | } |
michael@0 | 385 | |
michael@0 | 386 | #if !defined GRAPHITE2_NTRACING |
michael@0 | 387 | |
michael@0 | 388 | inline |
michael@0 | 389 | Slot * input_slot(const SlotMap & slots, const int n) |
michael@0 | 390 | { |
michael@0 | 391 | Slot * s = slots[slots.context() + n]; |
michael@0 | 392 | if (!s->isCopied()) return s; |
michael@0 | 393 | |
michael@0 | 394 | return s->prev() ? s->prev()->next() : (s->next() ? s->next()->prev() : slots.segment.last()); |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | inline |
michael@0 | 398 | Slot * output_slot(const SlotMap & slots, const int n) |
michael@0 | 399 | { |
michael@0 | 400 | Slot * s = slots[slots.context() + n - 1]; |
michael@0 | 401 | return s ? s->next() : slots.segment.first(); |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | #endif //!defined GRAPHITE2_NTRACING |
michael@0 | 405 | |
michael@0 | 406 | void Pass::findNDoRule(Slot * & slot, Machine &m, FiniteStateMachine & fsm) const |
michael@0 | 407 | { |
michael@0 | 408 | assert(slot); |
michael@0 | 409 | |
michael@0 | 410 | if (runFSM(fsm, slot)) |
michael@0 | 411 | { |
michael@0 | 412 | // Search for the first rule which passes the constraint |
michael@0 | 413 | const RuleEntry * r = fsm.rules.begin(), |
michael@0 | 414 | * const re = fsm.rules.end(); |
michael@0 | 415 | while (r != re && !testConstraint(*r->rule, m)) ++r; |
michael@0 | 416 | |
michael@0 | 417 | #if !defined GRAPHITE2_NTRACING |
michael@0 | 418 | if (fsm.dbgout) |
michael@0 | 419 | { |
michael@0 | 420 | if (fsm.rules.size() != 0) |
michael@0 | 421 | { |
michael@0 | 422 | *fsm.dbgout << json::item << json::object; |
michael@0 | 423 | dumpRuleEventConsidered(fsm, *r); |
michael@0 | 424 | if (r != re) |
michael@0 | 425 | { |
michael@0 | 426 | const int adv = doAction(r->rule->action, slot, m); |
michael@0 | 427 | dumpRuleEventOutput(fsm, *r->rule, slot); |
michael@0 | 428 | if (r->rule->action->deletes()) fsm.slots.collectGarbage(); |
michael@0 | 429 | adjustSlot(adv, slot, fsm.slots); |
michael@0 | 430 | *fsm.dbgout << "cursor" << objectid(dslot(&fsm.slots.segment, slot)) |
michael@0 | 431 | << json::close; // Close RuelEvent object |
michael@0 | 432 | |
michael@0 | 433 | return; |
michael@0 | 434 | } |
michael@0 | 435 | else |
michael@0 | 436 | { |
michael@0 | 437 | *fsm.dbgout << json::close // close "considered" array |
michael@0 | 438 | << "output" << json::null |
michael@0 | 439 | << "cursor" << objectid(dslot(&fsm.slots.segment, slot->next())) |
michael@0 | 440 | << json::close; |
michael@0 | 441 | } |
michael@0 | 442 | } |
michael@0 | 443 | } |
michael@0 | 444 | else |
michael@0 | 445 | #endif |
michael@0 | 446 | { |
michael@0 | 447 | if (r != re) |
michael@0 | 448 | { |
michael@0 | 449 | const int adv = doAction(r->rule->action, slot, m); |
michael@0 | 450 | if (r->rule->action->deletes()) fsm.slots.collectGarbage(); |
michael@0 | 451 | adjustSlot(adv, slot, fsm.slots); |
michael@0 | 452 | return; |
michael@0 | 453 | } |
michael@0 | 454 | } |
michael@0 | 455 | } |
michael@0 | 456 | |
michael@0 | 457 | slot = slot->next(); |
michael@0 | 458 | } |
michael@0 | 459 | |
michael@0 | 460 | #if !defined GRAPHITE2_NTRACING |
michael@0 | 461 | |
michael@0 | 462 | void Pass::dumpRuleEventConsidered(const FiniteStateMachine & fsm, const RuleEntry & re) const |
michael@0 | 463 | { |
michael@0 | 464 | *fsm.dbgout << "considered" << json::array; |
michael@0 | 465 | for (const RuleEntry *r = fsm.rules.begin(); r != &re; ++r) |
michael@0 | 466 | { |
michael@0 | 467 | if (r->rule->preContext > fsm.slots.context()) continue; |
michael@0 | 468 | *fsm.dbgout << json::flat << json::object |
michael@0 | 469 | << "id" << r->rule - m_rules |
michael@0 | 470 | << "failed" << true |
michael@0 | 471 | << "input" << json::flat << json::object |
michael@0 | 472 | << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, -r->rule->preContext))) |
michael@0 | 473 | << "length" << r->rule->sort |
michael@0 | 474 | << json::close // close "input" |
michael@0 | 475 | << json::close; // close Rule object |
michael@0 | 476 | } |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | |
michael@0 | 480 | void Pass::dumpRuleEventOutput(const FiniteStateMachine & fsm, const Rule & r, Slot * const last_slot) const |
michael@0 | 481 | { |
michael@0 | 482 | *fsm.dbgout << json::item << json::flat << json::object |
michael@0 | 483 | << "id" << &r - m_rules |
michael@0 | 484 | << "failed" << false |
michael@0 | 485 | << "input" << json::flat << json::object |
michael@0 | 486 | << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, 0))) |
michael@0 | 487 | << "length" << r.sort - r.preContext |
michael@0 | 488 | << json::close // close "input" |
michael@0 | 489 | << json::close // close Rule object |
michael@0 | 490 | << json::close // close considered array |
michael@0 | 491 | << "output" << json::object |
michael@0 | 492 | << "range" << json::flat << json::object |
michael@0 | 493 | << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, 0))) |
michael@0 | 494 | << "end" << objectid(dslot(&fsm.slots.segment, last_slot)) |
michael@0 | 495 | << json::close // close "input" |
michael@0 | 496 | << "slots" << json::array; |
michael@0 | 497 | const Position rsb_prepos = last_slot ? last_slot->origin() : fsm.slots.segment.advance(); |
michael@0 | 498 | fsm.slots.segment.positionSlots(0); |
michael@0 | 499 | |
michael@0 | 500 | for(Slot * slot = output_slot(fsm.slots, 0); slot != last_slot; slot = slot->next()) |
michael@0 | 501 | *fsm.dbgout << dslot(&fsm.slots.segment, slot); |
michael@0 | 502 | *fsm.dbgout << json::close // close "slots" |
michael@0 | 503 | << "postshift" << (last_slot ? last_slot->origin() : fsm.slots.segment.advance()) - rsb_prepos |
michael@0 | 504 | << json::close; // close "output" object |
michael@0 | 505 | |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | #endif |
michael@0 | 509 | |
michael@0 | 510 | |
michael@0 | 511 | inline |
michael@0 | 512 | bool Pass::testPassConstraint(Machine & m) const |
michael@0 | 513 | { |
michael@0 | 514 | if (!m_cPConstraint) return true; |
michael@0 | 515 | |
michael@0 | 516 | assert(m_cPConstraint.constraint()); |
michael@0 | 517 | |
michael@0 | 518 | m.slotMap().reset(*m.slotMap().segment.first(), 0); |
michael@0 | 519 | m.slotMap().pushSlot(m.slotMap().segment.first()); |
michael@0 | 520 | vm::slotref * map = m.slotMap().begin(); |
michael@0 | 521 | const uint32 ret = m_cPConstraint.run(m, map); |
michael@0 | 522 | |
michael@0 | 523 | #if !defined GRAPHITE2_NTRACING |
michael@0 | 524 | json * const dbgout = m.slotMap().segment.getFace()->logger(); |
michael@0 | 525 | if (dbgout) |
michael@0 | 526 | *dbgout << "constraint" << (ret && m.status() == Machine::finished); |
michael@0 | 527 | #endif |
michael@0 | 528 | |
michael@0 | 529 | return ret && m.status() == Machine::finished; |
michael@0 | 530 | } |
michael@0 | 531 | |
michael@0 | 532 | |
michael@0 | 533 | bool Pass::testConstraint(const Rule & r, Machine & m) const |
michael@0 | 534 | { |
michael@0 | 535 | const uint16 curr_context = m.slotMap().context(); |
michael@0 | 536 | if (unsigned(r.sort - r.preContext) > m.slotMap().size() - curr_context |
michael@0 | 537 | || curr_context - r.preContext < 0) return false; |
michael@0 | 538 | if (!*r.constraint) return true; |
michael@0 | 539 | assert(r.constraint->constraint()); |
michael@0 | 540 | |
michael@0 | 541 | vm::slotref * map = m.slotMap().begin() + curr_context - r.preContext; |
michael@0 | 542 | for (int n = r.sort; n && map; --n, ++map) |
michael@0 | 543 | { |
michael@0 | 544 | if (!*map) continue; |
michael@0 | 545 | const int32 ret = r.constraint->run(m, map); |
michael@0 | 546 | if (!ret || m.status() != Machine::finished) |
michael@0 | 547 | return false; |
michael@0 | 548 | } |
michael@0 | 549 | |
michael@0 | 550 | return true; |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | |
michael@0 | 554 | void SlotMap::collectGarbage() |
michael@0 | 555 | { |
michael@0 | 556 | for(Slot **s = begin(), *const *const se = end() - 1; s != se; ++s) { |
michael@0 | 557 | Slot *& slot = *s; |
michael@0 | 558 | if(slot->isDeleted() || slot->isCopied()) |
michael@0 | 559 | segment.freeSlot(slot); |
michael@0 | 560 | } |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | |
michael@0 | 564 | |
michael@0 | 565 | int Pass::doAction(const Code *codeptr, Slot * & slot_out, vm::Machine & m) const |
michael@0 | 566 | { |
michael@0 | 567 | assert(codeptr); |
michael@0 | 568 | if (!*codeptr) return 0; |
michael@0 | 569 | SlotMap & smap = m.slotMap(); |
michael@0 | 570 | vm::slotref * map = &smap[smap.context()]; |
michael@0 | 571 | smap.highpassed(false); |
michael@0 | 572 | |
michael@0 | 573 | int32 ret = codeptr->run(m, map); |
michael@0 | 574 | |
michael@0 | 575 | if (m.status() != Machine::finished) |
michael@0 | 576 | { |
michael@0 | 577 | slot_out = NULL; |
michael@0 | 578 | smap.highwater(0); |
michael@0 | 579 | return 0; |
michael@0 | 580 | } |
michael@0 | 581 | |
michael@0 | 582 | slot_out = *map; |
michael@0 | 583 | return ret; |
michael@0 | 584 | } |
michael@0 | 585 | |
michael@0 | 586 | |
michael@0 | 587 | void Pass::adjustSlot(int delta, Slot * & slot_out, SlotMap & smap) const |
michael@0 | 588 | { |
michael@0 | 589 | if (delta < 0) |
michael@0 | 590 | { |
michael@0 | 591 | if (!slot_out) |
michael@0 | 592 | { |
michael@0 | 593 | slot_out = smap.segment.last(); |
michael@0 | 594 | ++delta; |
michael@0 | 595 | if (smap.highpassed() && !smap.highwater()) |
michael@0 | 596 | smap.highpassed(false); |
michael@0 | 597 | } |
michael@0 | 598 | while (++delta <= 0 && slot_out) |
michael@0 | 599 | { |
michael@0 | 600 | if (smap.highpassed() && smap.highwater() == slot_out) |
michael@0 | 601 | smap.highpassed(false); |
michael@0 | 602 | slot_out = slot_out->prev(); |
michael@0 | 603 | } |
michael@0 | 604 | } |
michael@0 | 605 | else if (delta > 0) |
michael@0 | 606 | { |
michael@0 | 607 | if (!slot_out) |
michael@0 | 608 | { |
michael@0 | 609 | slot_out = smap.segment.first(); |
michael@0 | 610 | --delta; |
michael@0 | 611 | } |
michael@0 | 612 | while (--delta >= 0 && slot_out) |
michael@0 | 613 | { |
michael@0 | 614 | slot_out = slot_out->next(); |
michael@0 | 615 | if (slot_out == smap.highwater() && slot_out) |
michael@0 | 616 | smap.highpassed(true); |
michael@0 | 617 | } |
michael@0 | 618 | } |
michael@0 | 619 | } |
michael@0 | 620 |