"""Real Chinese IME candidates from librime + rime-ice, for the Standard zh benchmark. The baseline engine is librime (rime.dll, ctypes over rime_get_api) with the rime-ice schema, patched only where the benchmark needs a fixed, input-only engine: - user dictionary off, so no query changes the ranking of a later one; - the Lua date/time/lunar/UUID/number/calculator translators and the English translators (melt_eng, cn_en) removed, so key sequences such as `rq` or `pain` return Chinese words only; - emoji and traditionalization off. Everything else (the rime-ice dictionaries, spelling algebra with abbreviated pinyin, the Lua filters that reorder candidates) is unchanged. --variant selects the engine configuration (default ice, the frozen benchmark pools): ice librime + rime-ice, as above ice-gram ice plus the wanxiang grammar model through librime-octagram, with the parameters of rime-ice's own others/recipes/grammar.recipe.yaml luna the stock Luna Pinyin, simplified output (luna_pinyin_simp), with rime-prelude, rime-essay and rime-stroke; user dictionary off Each variant deploys into its own --work directory. deploy build the dictionaries into /user/build (takes a few minutes) probe print the candidates of a few key sequences query rows {id, keys, n_han} -> rows {id, keys, candidates:[...]} where a candidate is kept only if it is all Han and has exactly n_han characters, i.e. it converts the whole input (Rime also lists candidates for a prefix of the input); the first --n are kept, in Rime order. The rime-ice checkout is read as data (--shared); the patch files are written into /user by this script, so the engine configuration is recorded here in Git. """ import argparse import ctypes as C import json from pathlib import Path import re import sys ROOT=Path(__file__).resolve().parents[2] ENGINE=ROOT/'datasets/ime-standard-zh-v1/engine' HAN=re.compile(r'^[〇㐀-䶿一-鿿豈-﫿\U00020000-\U0003134f]+$') DEFAULT_CUSTOM='''patch: schema_list: - schema: rime_ice menu/page_size: 20 ''' SCHEMA_CUSTOM='''patch: translator/enable_user_dict: false engine/translators: - punct_translator - script_translator - table_translator@custom_phrase ''' # rime-ice others/recipes/grammar.recipe.yaml (99662ba), copied without change. GRAMMAR_PATCH=''' grammar: language: wanxiang-lts-zh-hans collocation_max_length: 6 collocation_min_length: 3 collocation_penalty: -14 non_collocation_penalty: -6 weak_collocation_penalty: -100 rear_penalty: -20 translator/contextual_suggestions: false translator/max_homophones: 8 ''' GRAMMAR_FILE=ENGINE/'grammar/wanxiang-lts-zh-hans.gram' LUNA_REPOS=('rime-luna-pinyin','rime-prelude','rime-essay','rime-stroke') LUNA_CUSTOM='''patch: translator/enable_user_dict: false ''' WORK=ROOT/'outputs/benchmarks/ime-standard-zh-v1' VARIANTS={ 'ice':dict(schema='rime_ice',shared=ENGINE/'rime-ice',work=WORK/'rime',patch=SCHEMA_CUSTOM,label='rime-ice'), 'ice-gram':dict(schema='rime_ice',shared=ENGINE/'rime-ice',work=WORK/'rime-ice-gram',patch=SCHEMA_CUSTOM+GRAMMAR_PATCH, label='rime-ice + wanxiang-lts-zh-hans grammar'), 'luna':dict(schema='luna_pinyin_simp',shared=None,work=WORK/'rime-luna',patch=LUNA_CUSTOM,label='luna_pinyin_simp'), } def luna_shared(work): """One shared data directory from the pinned Luna Pinyin repositories and librime's OpenCC data.""" import shutil shared=Path(work)/'shared' if not shared.exists(): shared.mkdir(parents=True) for repo in LUNA_REPOS: for f in (ENGINE/'luna'/repo).glob('*'): if f.suffix in ('.yaml','.txt'): shutil.copy(f,shared/f.name) shutil.copytree(ENGINE/'librime/share/opencc',shared/'opencc') return shared API_FIELDS=('setup set_notification_handler initialize finalize start_maintenance is_maintenance_mode ' 'join_maintenance_thread deployer_initialize prebuild deploy deploy_schema deploy_config_file ' 'sync_user_data create_session find_session destroy_session cleanup_stale_sessions ' 'cleanup_all_sessions process_key commit_composition clear_composition get_commit free_commit ' 'get_context free_context get_status free_status set_option get_option set_property get_property ' 'get_schema_list free_schema_list get_current_schema select_schema schema_open config_open ' 'config_close config_get_bool config_get_int config_get_double config_get_string config_get_cstring ' 'config_update_signature config_begin_map config_next config_end simulate_key_sequence ' 'register_module find_module run_task get_shared_data_dir get_user_data_dir get_sync_dir get_user_id ' 'get_user_data_sync_dir config_init config_load_string config_set_bool config_set_int ' 'config_set_double config_set_string config_get_item config_set_item config_clear config_create_list ' 'config_create_map config_list_size config_begin_list get_input get_caret_pos select_candidate ' 'get_version set_caret_pos select_candidate_on_current_page candidate_list_begin candidate_list_next ' 'candidate_list_end user_config_open candidate_list_from_index get_prebuilt_data_dir get_staging_dir ' 'commit_proto context_proto status_proto get_state_label delete_candidate ' 'delete_candidate_on_current_page get_state_label_abbreviated set_input').split() class Traits(C.Structure): _fields_=[('data_size',C.c_int),('shared_data_dir',C.c_char_p),('user_data_dir',C.c_char_p), ('distribution_name',C.c_char_p),('distribution_code_name',C.c_char_p), ('distribution_version',C.c_char_p),('app_name',C.c_char_p),('modules',C.c_void_p), ('min_log_level',C.c_int),('log_dir',C.c_char_p),('prebuilt_data_dir',C.c_char_p), ('staging_dir',C.c_char_p)] class Candidate(C.Structure): _fields_=[('text',C.c_char_p),('comment',C.c_char_p),('reserved',C.c_void_p)] class CandIter(C.Structure): _fields_=[('ptr',C.c_void_p),('index',C.c_int),('candidate',Candidate)] class Api(C.Structure): _fields_=[('data_size',C.c_int)]+[(n,C.c_void_p) for n in API_FIELDS] SID=C.c_size_t SIGS=dict(setup=(None,C.POINTER(Traits)),initialize=(None,C.POINTER(Traits)),finalize=(None,), start_maintenance=(C.c_int,C.c_int),join_maintenance_thread=(None,),create_session=(SID,), destroy_session=(C.c_int,SID),select_schema=(C.c_int,SID,C.c_char_p), set_option=(None,SID,C.c_char_p,C.c_int),clear_composition=(None,SID), set_input=(C.c_int,SID,C.c_char_p),get_input=(C.c_char_p,SID), candidate_list_begin=(C.c_int,SID,C.POINTER(CandIter)), candidate_list_next=(C.c_int,C.POINTER(CandIter)),candidate_list_end=(None,C.POINTER(CandIter)), get_version=(C.c_char_p,)) class Rime: def __init__(self,shared,work,dll,log_level=2,variant='ice'): v=VARIANTS[variant]; self.schema=v['schema']; self.label=v['label'] self.user=Path(work)/'user'; self.user.mkdir(parents=True,exist_ok=True) (self.user/'default.custom.yaml').write_text(DEFAULT_CUSTOM.replace('rime_ice',self.schema),encoding='utf-8') (self.user/f'{self.schema}.custom.yaml').write_text(v['patch'],encoding='utf-8') if variant=='ice-gram' and not (self.user/GRAMMAR_FILE.name).exists(): import shutil; shutil.copy(GRAMMAR_FILE,self.user/GRAMMAR_FILE.name) empty=Path(work)/'prebuilt-none'; empty.mkdir(exist_ok=True) # never use a shipped build lib=C.CDLL(str(dll)); lib.rime_get_api.restype=C.POINTER(Api) raw=lib.rime_get_api().contents assert raw.data_size>=C.sizeof(Api)-C.sizeof(C.c_int), 'rime.dll older than rime_api.h' self.f={n:C.CFUNCTYPE(s[0],*s[1:])(getattr(raw,n)) for n,s in SIGS.items()} self._keep=[str(shared).encode(),str(self.user).encode(),str(empty).encode()] t=Traits(); t.data_size=C.sizeof(Traits)-C.sizeof(C.c_int) t.shared_data_dir,t.user_data_dir,t.prebuilt_data_dir=self._keep t.distribution_name=b'VimeML'; t.distribution_code_name=b'vimeml'; t.distribution_version=b'1' t.app_name=b'rime.vimeml'; t.min_log_level=log_level; t.log_dir=b'' self.traits=t self.f['setup'](C.byref(t)); self.f['initialize'](C.byref(t)) self.version=self.f['get_version']().decode() def deploy(self): if self.f['start_maintenance'](1): self.f['join_maintenance_thread']() def session(self): s=self.f['create_session']() assert s and self.f['select_schema'](s,self.schema.encode()), f'schema {self.schema} not deployed' for o in (b'ascii_mode',b'emoji',b'traditionalization',b'full_shape',b'zh_hant',b'zh_hant_hk',b'zh_hant_tw'): self.f['set_option'](s,o,0) if self.schema=='luna_pinyin_simp': self.f['set_option'](s,b'zh_hans',1) return s def candidates(self,s,keys,limit=300): self.f['clear_composition'](s) assert self.f['set_input'](s,keys.encode('ascii')) out=[]; it=CandIter() if self.f['candidate_list_begin'](s,C.byref(it)): while len(out)',' '.join(t for t,_ in c[:30])) return src,dst=map(Path,a.args) with open(src,encoding='utf-8') as fi, open(dst,'w',encoding='utf-8') as fo: for line in fi: if not line.strip(): continue q=json.loads(line) c=full(r.candidates(s,q['keys'],a.scan),q['n_han'],a.n) fo.write(json.dumps(dict(id=q['id'],keys=q['keys'],engine=f'librime {r.version} + {r.label}', candidates=c),ensure_ascii=False)+'\n') if __name__=='__main__': sys.exit(main())