diff options
-rw-r--r-- | core/http/client.ts | 33 | ||||
-rw-r--r-- | core/http/server.ts | 39 | ||||
-rw-r--r-- | core/http/types.ts | 25 | ||||
-rw-r--r-- | language/parser.ts | 16 | ||||
-rw-r--r-- | language/tags.ts | 6 | ||||
-rw-r--r-- | main.ts | 8 | ||||
-rw-r--r-- | util/array.ts | 10 | ||||
-rw-r--r-- | util/set.ts | 4 | ||||
-rw-r--r-- | util/string.ts | 41 |
9 files changed, 138 insertions, 44 deletions
diff --git a/core/http/client.ts b/core/http/client.ts index 42d75f0..a77b616 100644 --- a/core/http/client.ts +++ b/core/http/client.ts @@ -1,7 +1,9 @@ -import { ParseDepth, ParseResult } from "../../language/types.ts"; -import YomikunError from "../../util/error.ts"; +import "../../util/array.ts"; + +import { ParseResult } from "../../language/types.ts"; import API from "../api.ts"; import { ConnectionProps, ConnectionPropsDefault } from "./props.ts"; +import { APIRequest, APIRequestParseSentence, APIResponseParseSentence } from "./types.ts"; /** * @summary Yomikun HTTP API @@ -18,14 +20,27 @@ export default class YomikunRemoteAPIClient implements API { async prepare() { } - async parseSentence(input: string) { - var response = await fetch(`http://${this.props.host}:${this.props.port}/parseSentence`); - console.log(response.body); + private async request(details: APIRequest) { + var response = await fetch(`http://${this.props.host}:${this.props.port}`, { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(details), + }); + return response.json(); + } - return { - depth: ParseDepth.Term, - tokens: [], - } as ParseResult; + async parseSentence(input: string) { + var request: APIRequestParseSentence = { + command: "parseSentence", + options: { + input: input, + }, + }; + var { response } = await this.request(request) as APIResponseParseSentence; + return response; } } diff --git a/core/http/server.ts b/core/http/server.ts index 8a6786e..b5d6c13 100644 --- a/core/http/server.ts +++ b/core/http/server.ts @@ -1,33 +1,44 @@ import { serve } from "https://deno.land/std@0.192.0/http/server.ts"; +import "../../util/string.ts"; + import { ParseResult } from "../../language/types.ts"; import YomikunRAWAPI from "../raw/api.ts"; import { ConnectionProps, ConnectionPropsDefault } from "./props.ts"; +import { APIRequest, APIRequestParseSentence, APIResponseParseSentence } from "./types.ts"; -interface Endpoint { - endpoint: string; -}; export default class YomikunRemoteAPIServer extends YomikunRAWAPI { private props: ConnectionProps; + private handlers: Record<string, (req: APIRequest) => Promise<Response>> = { + parseSentence: async _req => { + var req = _req as APIRequestParseSentence; + 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)); + }, + }; constructor(options?: ConnectionProps) { super(); this.props = { ...ConnectionPropsDefault, ...options }; } - async parseSentence(input: string) { - return await super.parseSentence(input); - } - async start() { - serve((req) => { - return new Response("Hello world!"); - }, { port: this.props.port }); - } - - async prepare() { - await super.prepare(); + serve(async (req) => { + if (req.method != "POST") return new Response("", { status: 400 }); // wrong request (not post) + var request = (await req.text()).json({}) as APIRequest; + 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) + return await handler(request); + }, { + port: this.props.port, + onListen: () => { } + }); } } diff --git a/core/http/types.ts b/core/http/types.ts new file mode 100644 index 0000000..bbba1b5 --- /dev/null +++ b/core/http/types.ts @@ -0,0 +1,25 @@ +import { ParseResult } from "../../language/types.ts"; + +export interface APIRequest { + command: string; + options: any; +}; + +export interface APIRequestParseSentence extends APIRequest { + command: "parseSentence"; + options: { + input: string; + }; +}; + +export interface APIResponse { + command: string; + response: any; + // final: boolean; +}; + +export interface APIResponseParseSentence extends APIResponse { + command: "parseSentence"; + response: ParseResult; +}; + diff --git a/language/parser.ts b/language/parser.ts index 9c6bef2..9bfdc4b 100644 --- a/language/parser.ts +++ b/language/parser.ts @@ -52,12 +52,12 @@ export default class Parser { if (!result.tags.anyOf(Object.values(Tag.Class.Verb))) return false; // ignore other wrong deconjugations - if (result.tags.has(Tag.Class.Verb.U) && - !result.tags.has(Tag.Inflection.Reason.U)) return false; - if (result.tags.has(Tag.Class.Verb.Ru) && - !result.tags.has(Tag.Inflection.Reason.Ru)) return false; - if (result.tags.has(Tag.Class.Verb.Suru) && - !result.tags.has(Tag.Inflection.Reason.Suru)) return false; + if (result.tags.includes(Tag.Class.Verb.U) && + !result.tags.includes(Tag.Inflection.Reason.U)) return false; + if (result.tags.includes(Tag.Class.Verb.Ru) && + !result.tags.includes(Tag.Inflection.Reason.Ru)) return false; + if (result.tags.includes(Tag.Class.Verb.Suru) && + !result.tags.includes(Tag.Inflection.Reason.Suru)) return false; } // all other results should be valid grammatically @@ -73,12 +73,12 @@ export default class Parser { const lastTokenName = parseResult.tokens.peek()?.tags.anyOf(Object.values(Tag.Name)); // give higher priority to suffixes when last token was a name, else lower priority - if (result.tags.has(Tag.Class.Suffix)) + if (result.tags.includes(Tag.Class.Suffix)) result.sort *= lastTokenName ? PRIORITY_MOD_HIGHER : PRIORITY_MOD_LOWER; // give lower priority to terms matched only by their readings, and are // usually written in kanji - if (!result.tags.has(Tag.Auxiliary.UsuallyKana) && !result.match.kanji) + if (!result.tags.includes(Tag.Auxiliary.UsuallyKana) && !result.match.kanji) result.sort *= PRIORITY_MOD_LOWER; return result; diff --git a/language/tags.ts b/language/tags.ts index d56ce98..d40904f 100644 --- a/language/tags.ts +++ b/language/tags.ts @@ -1,3 +1,5 @@ +import "../util/array.ts"; + /** @constant Tags that have significant meaning to the parser */ export const Tag = { /** @constant grammatical classes */ @@ -98,7 +100,7 @@ export const Tag = { export type TokenTag = string; // no way around it -export type TokenTags = Set<TokenTag>; +export type TokenTags = Array<TokenTag>; /** @summary parse concatenated tag string to TokenTags */ export function parseTags(input: string) { @@ -111,6 +113,6 @@ export function parseTags(input: string) { filteredTags.push(tag); } - return new Set(filteredTags) as TokenTags; + return filteredTags.set().arr() as TokenTags; // make sure array doesn't contain duplicates } @@ -13,7 +13,7 @@ function prettyprintParseResult(input: ParseResult) { out += " ("; out += token.reading.map(r => r.ruby ? r.ruby : r.text).reduce((a, b) => a + b); out += ") "; - out += token.tags.arr().map(a => `[${a}]`).join(" "); + out += token.tags.map(a => `[${a}]`).join(" "); console.log(out); } @@ -36,7 +36,7 @@ async function apiTest(api: API) { } // test 1 (direct api) -(async () => { +await (async () => { var api = new YomikunDirectAPIClient(); await api.prepare(); @@ -44,8 +44,10 @@ async function apiTest(api: API) { await apiTest(api); })(); +console.log("\n".repeat(2)); + // test 2 (remote api) -(async () => { +await (async () => { // default host = localhost:9400 var server = new YomikunRemoteAPIServer(); await server.prepare(); diff --git a/util/array.ts b/util/array.ts index 76e2a9e..5b8c512 100644 --- a/util/array.ts +++ b/util/array.ts @@ -1,17 +1,23 @@ declare global { interface Array<T> { + /** @summary check if any of the elements of `arr2` are included in `this` */ anyOf(arr2: Array<T>): boolean; + /** @summary return last element of array without removing it */ peek(): T; + /** @summary create Set from this array */ + set(): Set<T>; } } -/** @summary check if any of the elements of `arr2` are included in `this` */ Array.prototype.anyOf = function(arr2) { return !!this.filter(e => arr2.includes(e)).length; }; -/** @summary return last element of array without removing it */ Array.prototype.peek = function() { return this[this.length - 1]; }; +Array.prototype.set = function() { + return new Set(this); +} + diff --git a/util/set.ts b/util/set.ts index 9790682..1b4eb19 100644 --- a/util/set.ts +++ b/util/set.ts @@ -1,16 +1,16 @@ declare global { interface Set<T> { + /** @summary check if any of the elements of `arr2` are included in `this` */ anyOf(arr2: Array<T>): boolean; + /** @summary return set items as array */ arr(): Array<T>; } } -/** @summary return set items as array */ Set.prototype.arr = function() { return Array.from(this); } -/** @summary check if any of the elements of `arr2` are included in `this` */ Set.prototype.anyOf = function(arr2) { return !!this.arr().filter(e => arr2.includes(e)).length; }; diff --git a/util/string.ts b/util/string.ts index e0cc5eb..16d8f0a 100644 --- a/util/string.ts +++ b/util/string.ts @@ -4,12 +4,33 @@ import JapaneseString from "../language/japanese.ts"; declare global { /** @summary extended String prototype functions */ interface String { + /** @summary get UnicodeRange for character at index 0 */ range(): UnicodeRange; + /** @summary create a RangeTally object for counting used unicode ranges in string */ rangeTally(): RangeTally; + /** @summary get JapaneseString from this string */ jp(): JapaneseString; + /** @summary parse concatenated tag string to TokenTags */ parseTags(): TokenTags; + + /** + * @summary Remove all instances of a substring in a string, using a regular expression or search string + * @param searchValue A string to search for + */ + removeAll(searchValue: string | RegExp): string; + + /** + * @summary parse string as JSON, with optional fallback value + * + * fallback is undefined by default. if fallback is specified, it will be + * returned if JSON.parse throws any error. if fallback is not specified, + * no errors will be caught. + * + * @argument fallback return this value if parsing fails + */ + json(fallback?: any): any; } } @@ -27,7 +48,6 @@ export enum UnicodeRange { type RangeTally = Record<UnicodeRange, number>; -/** @summary get UnicodeRange for character at index 0 */ String.prototype.range = function() { var code = this.charCodeAt(0); @@ -46,20 +66,33 @@ String.prototype.range = function() { return UnicodeRange.Unknown; } -/** @summary create a RangeTally object for counting used unicode ranges in string */ String.prototype.rangeTally = function() { var tally = Object.keys(UnicodeRange).reduce((a: any,c) => (a[c] = 0, a), {}) as RangeTally; for (var char of this) tally[char.range()]++; return tally; }; -/** @summary get JapaneseString from this string */ String.prototype.jp = function() { return new JapaneseString(this); } -/** @summary parse concatenated tag string to TokenTags */ String.prototype.parseTags = function() { return parseTags(this as string); } +String.prototype.removeAll = function(searchValue: string | RegExp) { + return this.replaceAll(searchValue, ""); +} + +String.prototype.json = function(fallback?: any) { + if (fallback) { + try { + return JSON.parse(this as string); + } catch { + return fallback; + } + } else { + return JSON.parse(this as string); + } +} + |