-- Elision_toki_pona_UDP_Server - What would this do besides take in word questions to give answers? -- Copyright (C) 2022,2023 Prince Trippy . -- This program is free software: you can redistribute it and/or modify it under the terms of the -- GNU Affero General Public License version 3 as published by the Free Software Foundation. -- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -- even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -- See the GNU Affero General Public License for more details. -- You should have received a copy of the GNU Affero General Public License along with this program. -- If not, see . -- This program requires Ada 2005 support wholly because of the usage of the Ada.Assertions package. pragma Assertion_Policy(Check); with Interfaces; with Ada.Command_Line, Ada.Strings.Bounded, Ada.Strings.Fixed, Ada.Text_IO, Ada.Assertions; with Usable_Datagram_Package, Trivial_Trie; -- That toki pona constructed language is rather uninteresting, but I went ahead and completed this. -- This program nicely reveals the basic approach I wish to take with a Latin version of the system. -- This program accepts a word and converts it to its Elision representation by a trie of all words. -- With that Elision representation, an integer, the dictionary of words may be accessed, trivially. -- From that and other tables, information about the word may be collected to give an answer simply. procedure Elision_toki_pona_UDP_Server is -- This package is instantiated to help that table of word descriptions and English translations. package Bounded_32 is new Ada.Strings.Bounded.Generic_Bounded_Length(32); -- Ada doesn't make it very pleasant to define new types for characters and strings, in this way. -- I may not use that package for bounded strings, because that exists only for standard strings. type toki_pona_Word_Length is new Integer range 1 .. 16; type toki_pona_Character is ('a', 'e', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 's', 't', 'u', 'w'); type toki_pona_String is array (toki_pona_Word_Length range <>) of toki_pona_Character; type Word is new Interfaces.Unsigned_8; -- The system restricts the number of words to one octet. -- Here is one of the main data structures of this program, so convenient for Elision conversion. -- The trie is traversed according to the letters of the word, with the code as the stored value. -- This is unnecessary since toki pona has no mutated words, but the applications here are clear. package T is new Trivial_Trie (Index_Type => toki_pona_Word_Length, Domain_Type => toki_pona_Character, Input_Type => toki_pona_String, Stored_Type => Word); -- In a larger Elision system, defining the type of the dictionary, Word_Storage, is unnecessary. -- As this program isn't truly part of a larger such system, I just use an access type, for ease. Trie : T.Trie; File : Ada.Text_IO.File_Type; Word_Storage : array (Word) of access toki_pona_String := (others => null); Description_Storage : array (Word) of Bounded_32.Bounded_String := (others => Bounded_32.Null_Bounded_String); -- I didn't realize this neat little trick with Image and Value, but it saves me a little effort. -- In a more serious such Elision system this would likely have an unacceptable time performance. function To_toki_pona_Character (C : Character) return toki_pona_Character is begin return toki_pona_Character'Value(Character'Image(C)); end To_toki_pona_Character; function From_toki_pona_Character (C : toki_pona_Character) return Character is begin return Character'Value(toki_pona_Character'Image(C)); end From_toki_pona_Character; -- These functions are a bother, and written to work with any indices, but aren't very egregious. function To_toki_pona_String (S : String) return toki_pona_String is N : toki_pona_String(1 .. S'Length); begin for I in S'Range loop N(toki_pona_Word_Length(1 + I - S'First)) := To_toki_pona_Character(S(I)); end loop; return N; end To_toki_pona_String; function From_toki_pona_String (S : toki_pona_String) return String is N : String(1 .. S'Length); begin for I in S'Range loop N(Positive(1 + I - S'First)) := From_toki_pona_Character(S(I)); end loop; return N; end From_toki_pona_String; -- I found implementing intake of the numerical toki pona Elision dictionary representation hard. -- I decided to use a simpler format, resembling that used for the descriptions, because of this. -- Unlike the numerical format, this deprives me of word count information, amongst other things. -- Regardless, it suffices, and is carefully written to raise errors, when given some bad inputs. procedure Get_Words is S : String(1 .. 17); -- A filled such string will cause To_toki_pona_String to raise an error. N : Natural; begin Ada.Text_IO.Open(File, Mode => Ada.Text_IO.In_File, Name => Ada.Command_Line.Argument(1)); Ada.Text_IO.Set_Input(File); for I in Word_Storage'Range loop exit when Ada.Text_IO.End_Of_File; Ada.Text_IO.Get_Line(S, N); Word_Storage(I) := new toki_pona_String'(To_toki_pona_String(S(1 .. N))); end loop; Ada.Text_IO.Close(File); end Get_Words; -- This procedure uses the same strategy of failure as in Get_Words; perhaps it could be generic. procedure Get_Descriptions is S : String(1 .. 33); N : Natural; begin Ada.Text_IO.Open(File, Mode => Ada.Text_IO.In_File, Name => Ada.Command_Line.Argument(2)); Ada.Text_IO.Set_Input(File); for I in Description_Storage'Range loop exit when Ada.Text_IO.End_Of_File; Ada.Text_IO.Get_Line(S, N); Description_Storage(I) := Bounded_32.To_Bounded_String(S(1 .. N)); end loop; Ada.Text_IO.Close(File); end Get_Descriptions; -- This procedure cycles across the dictionary to explicitly list all possible words in the trie. -- I use Ada.Assertions purely because I think it looks so nice that their names align perfectly. procedure Build_Trie is Here : Boolean; That : Word; use Ada.Assertions; begin for I in Word_Storage'Range loop exit when Word_Storage(I) = null; -- Once again, a better Elision wouldn't need to do this. T.Find(Path => Word_Storage(I).all, Tree => Trie, Seen => Here, Unto => That); Assert(not Here, "The dictionary contained duplicated words, which is prohibited."); T.Give(Path => Word_Storage(I).all, Tree => Trie, What => I); end loop; end Build_Trie; S : String(1 .. 16); R : Usable_Datagram_Package.System_Resource(2001); begin Ada.Text_IO.Set_Output(Ada.Text_IO.Standard_Error); Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Failure); if Ada.Command_Line.Argument_Count /= 2 then Ada.Text_IO.Put_Line("Supply two files, dictionary and descriptions."); return; end if; -- This part of the program is so nice and pretty. How unfortunate that remainder is so muddied. Get_Words; Get_Descriptions; Build_Trie; -- This is now the basic loop of the program: It waits for a question, and provides to it answer. loop declare A : Usable_Datagram_Package.Address; P : Usable_Datagram_Package.Port; I : Integer; begin Usable_Datagram_Package.Get(R, Data => S, From => A, From_Port => P, Length => I); -- A question must be shorter than the largest allowed word size; otherwise, this is given. if I > Integer(toki_pona_Word_Length'Last) then Usable_Datagram_Package.Hit(R, Data => "suli a", To => A, At_Port => P); else declare Present : Boolean; W : Word; X : String(1 .. Integer(toki_pona_Word_Length'Last)) := (others => ' '); Y : String(1 .. Bounded_32.Max_Length) := (others => ' '); begin -- This conversion may raise Constraint_Error, and if so it couldn't be toki pona. T.Find(Tree => Trie, Seen => Present, Unto => W, Path => To_toki_pona_String(S(1 .. I))); if Present then -- I know I may simplify this code, by use type and the "*" operator. Ada.Strings.Fixed.Move(Source => From_Toki_Pona_String(Word_Storage(W).all), Target => X); Ada.Strings.Fixed.Move(Source => Bounded_32.To_String(Description_Storage(W)), Target => Y, Justify => Ada.Strings.Right); -- A successful request returns the word followed by its description and how nice. Usable_Datagram_Package.Hit(R, Data => X & Y, To => A, At_Port => P); else -- A request for an unknown but seemingly valid word returns this ``not found''. Usable_Datagram_Package.Hit(R, Data => "ala", To => A, At_Port => P); end if; exception -- The declare block would still be needed, since this could also raise error. when Constraint_Error => Usable_Datagram_Package.Hit(R, To => A, At_Port => P, Data => "ike a"); end; end if; exception -- This exception handler prevents anything from disturbing this prime program loop. when others => null; end; end loop; end Elision_toki_pona_UDP_Server; .