@@ -14,6 +14,7 @@ import {
1414} from "@claudiu-ceia/combine" ;
1515import { recognizeAt , step } from "@claudiu-ceia/combine/nondeterministic" ;
1616import { __ , dot , word } from "./src/common.ts" ;
17+ import type { Entity } from "./src/Entity.ts" ;
1718import { Quantity , type QuantityEntity } from "./src/Quantity.ts" ;
1819import { Range , type RangeEntity } from "./src/Range.ts" ;
1920import { Temperature , type TemperatureEntity } from "./src/Temperature.ts" ;
@@ -54,42 +55,71 @@ export type AnyEntity =
5455 | UUIDEntity
5556 | ApiKeyEntity ;
5657
57- type DucklingLanguage = {
58- Entity : ( ) => Parser < AnyEntity [ ] > ;
58+ type DucklingLanguage < E extends Entity < string , unknown > > = {
59+ Entity : ( ) => Parser < E [ ] > ;
5960 Unstructured : ( ) => Parser < string > ;
60- extract : ( ) => Parser < AnyEntity [ ] > ;
61+ extract : ( ) => Parser < E [ ] > ;
6162} ;
6263
6364/**
64- * Creates a Duckling-like extractor language .
65+ * A Duckling-like extractor.
6566 *
66- * It parses arbitrary text and returns entity matches produced by the provided
67+ * This is a convenience wrapper around the internal parser combinator grammar,
68+ * so callers can simply pass a string.
69+ */
70+ export type DucklingExtractor < E extends Entity < string , unknown > > = {
71+ /**
72+ * Extract entities from `text` (starting at index 0).
73+ */
74+ extract : ( text : string ) => E [ ] ;
75+ /**
76+ * Extract entities starting at `index` in `text`.
77+ */
78+ extractAt : ( input : { text : string ; index : number } ) => E [ ] ;
79+ /**
80+ * Low-level parser for advanced use.
81+ *
82+ * Note: this parser expects a `{ text, index }` context input.
83+ */
84+ extractParser : Parser < E [ ] > ;
85+ } ;
86+
87+ /**
88+ * Creates a Duckling-like extractor.
89+ *
90+ * It scans arbitrary text and returns entity matches produced by the provided
6791 * entity parsers.
6892 */
69- export const Duckling : (
70- parsers ?: Parser < AnyEntity > [ ] ,
71- ) => ReturnType < typeof createLanguageThis < DucklingLanguage > > = (
72- parsers : Parser < AnyEntity > [ ] = [
73- Range . parser ,
74- Email . parser ,
75- URL . parser ,
76- UUID . parser ,
77- Phone . parser ,
78- IPAddress . parser ,
79- SSN . parser ,
80- CreditCard . parser ,
81- Time . parser ,
82- Temperature . parser ,
83- Quantity . parser ,
84- Location . parser ,
85- Institution . parser ,
86- Language . parser ,
87- ApiKey . parser ,
88- ] ,
89- ) =>
90- createLanguageThis < DucklingLanguage > ( {
93+ const DefaultParsers : Parser < AnyEntity > [ ] = [
94+ Range . parser ,
95+ Email . parser ,
96+ URL . parser ,
97+ UUID . parser ,
98+ Phone . parser ,
99+ IPAddress . parser ,
100+ SSN . parser ,
101+ CreditCard . parser ,
102+ Time . parser ,
103+ Temperature . parser ,
104+ Quantity . parser ,
105+ Location . parser ,
106+ Institution . parser ,
107+ Language . parser ,
108+ ApiKey . parser ,
109+ ] ;
110+
111+ export function Duckling ( ) : DucklingExtractor < AnyEntity > ;
112+ export function Duckling < E extends Entity < string , unknown > > (
113+ parsers : Parser < E > [ ] ,
114+ ) : DucklingExtractor < E > ;
115+ export function Duckling < E extends Entity < string , unknown > > (
116+ parsers ?: Parser < E > [ ] ,
117+ ) : DucklingExtractor < E > {
118+ const ps = parsers ?? ( DefaultParsers as unknown as Parser < E > [ ] ) ;
119+
120+ const lang = createLanguageThis < DucklingLanguage < E > > ( {
91121 Entity ( ) {
92- if ( parsers . length === 0 ) {
122+ if ( ps . length === 0 ) {
93123 return ( ctx ) => failure ( ctx , "entity" ) ;
94124 }
95125
@@ -98,7 +128,7 @@ export const Duckling: (
98128 // overlapping matches (useful for debug/analysis and the demo UI).
99129 const p = step (
100130 recognizeAt (
101- ...( parsers as [ Parser < AnyEntity > , ...Parser < AnyEntity > [ ] ] ) ,
131+ ...( ps as [ Parser < E > , ...Parser < E > [ ] ] ) ,
102132 ) ,
103133 "shortest" ,
104134 ) ;
@@ -117,14 +147,28 @@ export const Duckling: (
117147 skip1 ( eof ( ) ) ,
118148 ) ,
119149 ( [ ...matches ] ) =>
120- matches . filter ( ( m ) : m is AnyEntity [ ] => Array . isArray ( m ) ) . flat ( ) ,
150+ matches . filter ( ( m ) : m is E [ ] => Array . isArray ( m ) ) . flat ( ) ,
121151 ) ,
122152 ) ,
123153 ( [ , res ] ) => res ,
124154 ) ;
125155 } ,
126156 } ) ;
127157
158+ const extractAt = ( input : { text : string ; index : number } ) : E [ ] => {
159+ const res = lang . extract ( input ) ;
160+ return res . success ? res . value : [ ] ;
161+ } ;
162+
163+ const extract = ( text : string ) : E [ ] => extractAt ( { text, index : 0 } ) ;
164+
165+ return {
166+ extract,
167+ extractAt,
168+ extractParser : lang . extract ,
169+ } ;
170+ }
171+
128172export * from "./src/Entity.ts" ;
129173export * from "./src/Quantity.ts" ;
130174export * from "./src/Range.ts" ;
0 commit comments