diff options
-rw-r--r-- | api/base.ts | 20 | ||||
-rw-r--r-- | api/glossary.ts | 15 | ||||
-rw-r--r-- | api/readme.md | 6 | ||||
-rw-r--r-- | api/sentence.ts | 15 | ||||
-rw-r--r-- | api/word.ts | 15 | ||||
-rw-r--r-- | api/yomikun.ts | 16 | ||||
-rw-r--r-- | core/api.ts | 9 | ||||
-rw-r--r-- | core/direct/client.ts | 12 | ||||
-rw-r--r-- | core/http/client.ts | 18 | ||||
-rw-r--r-- | core/http/server.ts | 14 | ||||
-rw-r--r-- | core/http/types.ts | 8 | ||||
-rw-r--r-- | core/raw/api.ts | 10 | ||||
-rw-r--r-- | core/readme.md | 12 | ||||
-rw-r--r-- | examples/sentence-word-lookup.ts | 22 | ||||
-rw-r--r-- | import/jmdict/makefile | 5 | ||||
-rw-r--r-- | main.ts | 38 | ||||
-rw-r--r-- | test/reading/test.ts | 24 |
17 files changed, 185 insertions, 74 deletions
diff --git a/api/base.ts b/api/base.ts new file mode 100644 index 0000000..e89e76b --- /dev/null +++ b/api/base.ts @@ -0,0 +1,20 @@ +import Core from "../core/api.ts"; +import Yomikun from "./yomikun.ts"; + +/** @summary generic class that keeps a reference to parent API reference */ +export default abstract class APIBase { + private _resolveAPI: (api: Yomikun) => void = _ => {}; + + protected api: Promise<Yomikun>; + + constructor() { + this.api = new Promise<Yomikun>(res => this._resolveAPI = res); + } + + /** @summary set API reference and return self (for use directly after constructor) */ + withParent(api: Yomikun) { + this._resolveAPI(api); + return this; + } +} + diff --git a/api/glossary.ts b/api/glossary.ts new file mode 100644 index 0000000..0b16e48 --- /dev/null +++ b/api/glossary.ts @@ -0,0 +1,15 @@ +import APIBase from "./base.ts"; + +export default class Glossary extends APIBase { + constructor() { + super(); + } + + dict(name: string): Array<string> { + return this.all(); + } + + all(): Array<string> { + return []; + } +} diff --git a/api/readme.md b/api/readme.md new file mode 100644 index 0000000..58ebbcb --- /dev/null +++ b/api/readme.md @@ -0,0 +1,6 @@ +# Yomikun API + +This folder contains an abstracted version of the [Core +API](../core/readme.md), which is recommended for general use. Please see [the +API examples](../examples/readme.md). + diff --git a/api/sentence.ts b/api/sentence.ts new file mode 100644 index 0000000..dc14cb2 --- /dev/null +++ b/api/sentence.ts @@ -0,0 +1,15 @@ +import APIBase from "./base.ts"; +import Word from "./word.ts"; +import Yomikun from "./yomikun.ts"; + +export default class Sentence extends APIBase { + public words: Array<Word> = []; + + constructor() { + super(); + } + + first(searchValue: RegExp | string): Word | undefined { + return this.words[0]; + } +} diff --git a/api/word.ts b/api/word.ts new file mode 100644 index 0000000..d2df69e --- /dev/null +++ b/api/word.ts @@ -0,0 +1,15 @@ +import Glossary from "./glossary.ts"; +import APIBase from "./base.ts"; + +export default class Word extends APIBase { + public writing: string = "TODO"; + public reading: string = "TODO"; + + constructor() { + super(); + } + + async glossary() { + return new Glossary().withParent(await this.api); + } +} diff --git a/api/yomikun.ts b/api/yomikun.ts new file mode 100644 index 0000000..4971567 --- /dev/null +++ b/api/yomikun.ts @@ -0,0 +1,16 @@ +import Core from "../core/api.ts"; +import RemoteCoreClient from "../core/http/client.ts"; +import Sentence from "./sentence.ts"; + +export default class Yomikun { + protected core: Core; + + constructor(core?: Core) { + this.core = core ?? new RemoteCoreClient(); + } + + async sentence(input: string): Promise<Sentence> { + return new Sentence().withParent(this); + } +} + diff --git a/core/api.ts b/core/api.ts index 017e131..51f976a 100644 --- a/core/api.ts +++ b/core/api.ts @@ -1,12 +1,13 @@ import { ParseResult } from "../language/types.ts"; /** - * @summary API interface + * @summary Core interface * - * This interface gets implemented by all API clients, so clients can be - * swapped easily. + * This interface gets implemented by all Core clients, so clients can be + * swapped easily. You should probably not directly use any Core, but use the + * abstracted API from ../api/ */ -export default abstract class API { +export default abstract class Core { /** @summary resolved when ready */ abstract ready: Promise<void>; diff --git a/core/direct/client.ts b/core/direct/client.ts index fda4b60..328cf1f 100644 --- a/core/direct/client.ts +++ b/core/direct/client.ts @@ -1,11 +1,11 @@ -import API from "../api.ts"; -import YomikunRAWAPI from "../raw/api.ts"; +import Core from "../api.ts"; +import RawCore from "../raw/api.ts"; /** - * @summary Yomikun direct API + * @summary Direct Core * - * Alias to YomikunRAWAPI. calls API methods directly, and thus only works - * server-side. Used to test the API locally without HTTP overhead. + * Alias to RawCore. Calls Core methods directly, and thus only works + * server-side. Used to test the Core locally without HTTP overhead. */ -export default class YomikunDirectAPIClient extends YomikunRAWAPI implements API { } +export default class DirectCoreClient extends RawCore implements Core { } diff --git a/core/http/client.ts b/core/http/client.ts index 365aa76..118e8f5 100644 --- a/core/http/client.ts +++ b/core/http/client.ts @@ -1,16 +1,16 @@ import "../../util/array.ts"; -import API from "../api.ts"; +import Core from "../api.ts"; import { ConnectionProps, ConnectionPropsDefault } from "./props.ts"; -import { APIRequest, APIRequestParseSentence, APIResponseParseSentence } from "./types.ts"; +import { CoreRequest, CoreRequestParseSentence, CoreResponseParseSentence } from "./types.ts"; /** - * @summary Yomikun HTTP API + * @summary HTTP Core client * - * Uses the Yomikun server to call API methods. Handles (de)serialization - * automatically. + * Connects to an instance of RemoteCoreServer to call Core methods. Handles + * (de)serialization automatically. */ -export default class YomikunRemoteAPIClient implements API { +export default class RemoteCoreClient implements Core { private props: ConnectionProps; ready: Promise<void> = Promise.resolve(); @@ -18,7 +18,7 @@ export default class YomikunRemoteAPIClient implements API { this.props = { ...ConnectionPropsDefault, ...options }; } - private async request(details: APIRequest) { + private async request(details: CoreRequest) { var response = await fetch(`http://${this.props.host}:${this.props.port}`, { method: "POST", headers: { @@ -31,13 +31,13 @@ export default class YomikunRemoteAPIClient implements API { } async parseSentence(input: string) { - var request: APIRequestParseSentence = { + var request: CoreRequestParseSentence = { command: "parseSentence", options: { input: input, }, }; - var { response } = await this.request(request) as APIResponseParseSentence; + var { response } = await this.request(request) as CoreResponseParseSentence; return response; } } diff --git a/core/http/server.ts b/core/http/server.ts index 1af8d25..0a9a082 100644 --- a/core/http/server.ts +++ b/core/http/server.ts @@ -2,22 +2,22 @@ import { serve } from "https://deno.land/std@0.192.0/http/server.ts"; import "../../util/string.ts"; -import YomikunRAWAPI from "../raw/api.ts"; +import RawCore from "../raw/api.ts"; import { ConnectionProps, ConnectionPropsDefault } from "./props.ts"; -import { APIRequest, APIRequestParseSentence, APIResponseParseSentence } from "./types.ts"; +import { CoreRequest, CoreRequestParseSentence, CoreResponseParseSentence } from "./types.ts"; -export default class YomikunRemoteAPIServer extends YomikunRAWAPI { +export default class RemoteCoreServer extends RawCore { private props: ConnectionProps; - private handlers: Record<string, (req: APIRequest) => Promise<Response>> = { + private handlers: Record<string, (req: CoreRequest) => Promise<Response>> = { parseSentence: async _req => { - var req = _req as APIRequestParseSentence; + var req = _req as CoreRequestParseSentence; var input = req.options?.input if (!input) return new Response("", { status: 404 }); return new Response(JSON.stringify({ command: "parseSentence", response: await this.parseSentence(input), - } as APIResponseParseSentence)); + } as CoreResponseParseSentence)); }, }; @@ -29,7 +29,7 @@ export default class YomikunRemoteAPIServer extends YomikunRAWAPI { async start() { serve(async (req) => { if (req.method != "POST") return new Response("", { status: 400 }); // wrong request (not post) - var request = (await req.text()).json({}) as APIRequest; + var request = (await req.text()).json({}) as CoreRequest; if (!request.command) return new Response("", { status: 400 }); // wrong request (no command) var handler = this.handlers[request.command]; if (!handler) return new Response("", { status: 404 }); // not found (unknown command) diff --git a/core/http/types.ts b/core/http/types.ts index bbba1b5..af2cfea 100644 --- a/core/http/types.ts +++ b/core/http/types.ts @@ -1,24 +1,24 @@ import { ParseResult } from "../../language/types.ts"; -export interface APIRequest { +export interface CoreRequest { command: string; options: any; }; -export interface APIRequestParseSentence extends APIRequest { +export interface CoreRequestParseSentence extends CoreRequest { command: "parseSentence"; options: { input: string; }; }; -export interface APIResponse { +export interface CoreResponse { command: string; response: any; // final: boolean; }; -export interface APIResponseParseSentence extends APIResponse { +export interface CoreResponseParseSentence extends CoreResponse { command: "parseSentence"; response: ParseResult; }; diff --git a/core/raw/api.ts b/core/raw/api.ts index 06db022..f47dead 100644 --- a/core/raw/api.ts +++ b/core/raw/api.ts @@ -1,15 +1,15 @@ -import API from "../api.ts"; +import Core from "../api.ts"; import Parser from "../../language/parser.ts"; import YomikunError from "../../util/error.ts"; -/** @summary internal Yomikun API client (DO NOT USE DIRECTLY) */ -export default class YomikunRAWAPI implements API { +/** @summary internal Core (DO NOT USE DIRECTLY) */ +export default class RawCore implements Core { private _parser: Parser; ready: Promise<void>; constructor() { - if (this.constructor === YomikunRAWAPI) { - throw new YomikunError("YomikunRAWAPI instantiated! please use YomikunDirectAPIClient instead"); + if (this.constructor === RawCore) { + throw new YomikunError("RawCore instantiated! Use DirectCoreClient instead!"); } this._parser = new Parser(); diff --git a/core/readme.md b/core/readme.md new file mode 100644 index 0000000..3c602a9 --- /dev/null +++ b/core/readme.md @@ -0,0 +1,12 @@ +# Core API + +This folder contains the Core API. A Core instance is used as a standardized +API for interfacing with internal classes/modules. Different Cores can be used +interchangeably with the [Yomikun API](../api/readme.md) for providing the API +over various types of connections. + +Currently implemented cores: + +- HTTP +- Direct + diff --git a/examples/sentence-word-lookup.ts b/examples/sentence-word-lookup.ts index 7f5331b..ff82853 100644 --- a/examples/sentence-word-lookup.ts +++ b/examples/sentence-word-lookup.ts @@ -1,23 +1,27 @@ -import YomikunDirectAPIClient from "../core/direct/client.ts"; +import Yomikun from "../api/yomikun.ts"; +import DirectCoreClient from "../core/direct/client.ts"; // Create a direct (local) API instance -var api = new YomikunDirectAPIClient(); +var api = new Yomikun(new DirectCoreClient()); + // Excplicitly wait until everything is ready // await api.ready; // This sentence does not contain all information until it is explicitly // fetched by the user. Each subclass instantiated from an API instance keeps a // reference to that API instance for fetching additional data. -var sentence = api.sentence("この紅茶は甘すぎる"); +var sentence = await api.sentence("この紅茶は甘すぎる"); +console.log(await sentence.test()); // Pick the word 紅茶 from the sentence in some different ways: -// var word = sentence.at("紅茶"); // reference substring (matches first only) -// var word = sentence.terms[1]; // reference word index (depends on correct deconjugations/parsing) -var word = sentence.terms.find(t => t.writing == "紅茶"); // filter terms by writing (matches first only) +var word = sentence.words.find(w => w.writing == "紅茶"); // filter terms by writing (matches first only) +// var word = sentence.first("紅茶"); // reference substring (matches first only) +// var word = sentence.words[1]; // reference word index (depends on correct deconjugations/parsing) // Fetch definitions for word -var glossary = word.glossary(); - +var glossary = await word?.glossary(); -// WIP +// Show some definitions +console.log(glossary?.dict("jmdict_eng")[0]); // print first definition from JMdict +// console.log(glossary.all()); // print all definitions diff --git a/import/jmdict/makefile b/import/jmdict/makefile index d19c9af..fdbca6c 100644 --- a/import/jmdict/makefile +++ b/import/jmdict/makefile @@ -1,6 +1,8 @@ CURL = curl UNZIP = unzip +.PHONY: clean all + all: jmdict.dict.sql jmdict.zip: @@ -11,3 +13,6 @@ jmdict.json: jmdict.zip jmdict.dict.sql: jmdict.json jmdict.ts deno run -A --unstable ./jmdict.ts < $< > $@ + +clean: + $(RM) jmdict.dict.sql jmdict.sql jmdict.json jmdict.zip @@ -17,42 +17,42 @@ function prettyprintParseResult(input: ParseResult) { console.log(input.tokens.map(t => t.source).join(" ")); } -import API from "./core/api.ts"; +import Core from "./core/api.ts"; -import YomikunDirectAPIClient from "./core/direct/client.ts"; +import DirectCoreClient from "./core/direct/client.ts"; -import YomikunRemoteAPIServer from "./core/http/server.ts"; -import YomikunRemoteAPIClient from "./core/http/client.ts"; +import RemoteCoreServer from "./core/http/server.ts"; +import RemoteCoreClient from "./core/http/client.ts"; -async function apiTest(api: API) { - prettyprintParseResult(await api.parseSentence("浮上したハイラル城の下にてゼルダ様達の捜索を行うこととなった")); +async function coreTest(core: Core) { + prettyprintParseResult(await core.parseSentence("浮上したハイラル城の下にてゼルダ様達の捜索を行うこととなった")); console.log("-------------"); - prettyprintParseResult(await api.parseSentence("浮上した城の様")); + prettyprintParseResult(await core.parseSentence("浮上した城の様")); console.log("-------------"); - prettyprintParseResult(await api.parseSentence("迷子になってしまった")); + prettyprintParseResult(await core.parseSentence("迷子になってしまった")); } -// test 1 (direct api) +// test 1 (direct core) await (async () => { - var api = new YomikunDirectAPIClient(); - await api.ready; + var core = new DirectCoreClient(); + await core.ready; - console.log("Prepare direct api done"); - await apiTest(api); + console.log("Prepare direct core done"); + await coreTest(core); })(); console.log("\n".repeat(2)); -// test 2 (remote api) +// test 2 (remote core) await (async () => { // default host = localhost:9400 - new YomikunRemoteAPIServer().start(); + new RemoteCoreServer().start(); - var api = new YomikunRemoteAPIClient(); - await api.ready; + var core = new RemoteCoreClient(); + await core.ready; - console.log("Prepare remote api done"); - await apiTest(api); + console.log("Prepare remote core done"); + await coreTest(core); Deno.exit(0); })(); diff --git a/test/reading/test.ts b/test/reading/test.ts index 9caf890..051cfc6 100644 --- a/test/reading/test.ts +++ b/test/reading/test.ts @@ -1,12 +1,11 @@ import * as path from 'https://deno.land/std@0.102.0/path/mod.ts'; -Deno.chdir(path.dirname(path.fromFileUrl(Deno.mainModule)) + "/../.."); import { assertEquals } from "https://deno.land/std@0.192.0/testing/asserts.ts"; -import YomikunDirectAPIClient from "../../core/direct/client.ts"; +import DirectCoreClient from '../../core/direct/client.ts'; -var api = new YomikunDirectAPIClient(); -await api.prepare(); +var core = new DirectCoreClient(); +await core.ready; interface Test { test: { @@ -20,14 +19,17 @@ interface Test { const here = path.dirname(path.fromFileUrl(import.meta.url)); const tests = JSON.parse(await Deno.readTextFile(path.resolve(here, 'cases.json'))) as Test[]; -console.log(`amount of sentences: ${tests.length}`); -console.log(`average sentence length: ${tests.map(t => t.test.input.length).reduce((a, b) => a + b) / tests.length}`); - -console.time("parse"); +var timeStart = performance.now(); for (var { test } of tests) { - var result = await api.parseSentence(test.input); + var result = await core.parseSentence(test.input); // TODO: add reading back into conjugated verb to complete this test - } -console.timeEnd("parse"); +var timeEnd = performance.now(); +var duration = timeEnd - timeStart; +var averageLength = tests.map(t => t.test.input.length).reduce((a, b) => a + b) / tests.length; + +console.log(` amount of sentences: ${tests.length}`); +console.log(`average sentence length: ${averageLength.toFixed(1)} characters`); +console.log(` test duration: ${duration.toFixed(0)} ms`); +console.log(` average throughput: ${(tests.length / (duration / 1e3)).toFixed(1)} sentence/second`); |