diff --git a/Experiments/ExConsole/main.py b/Experiments/ExConsole/main.py index 606fe1e..7907823 100644 --- a/Experiments/ExConsole/main.py +++ b/Experiments/ExConsole/main.py @@ -140,8 +140,8 @@ def list_variables() -> None: @exp_register('r') def list_histories() -> None: - print_history('') - print_history('1') + print_history_in('') + print_history_in('1') @exp_register('help') @@ -369,7 +369,7 @@ def load_JSON() -> None: 'Please enter a new interface name:') # accept the reasoner with a new interface interface: NARSInterface = NARSInterface( NARS=decoded_NAR) # create interface - reasoners[interface_name] = interface # directly add + interfaces[interface_name] = interface # directly add reasoner_goto(interface_name) # goto print( f"Import a reasoner named {interface_name}, silent {'on' if interface.silent_output else 'off'}.") @@ -428,7 +428,7 @@ def load_pickle() -> None: 'Please enter a new interface name: ') # create interface interface: NARSInterface = NARSInterface(NARS=decoded_NAR) - reasoners[interface_name] = interface # directly add + interfaces[interface_name] = interface # directly add reasoner_goto(interface_name) # goto print( f"Import a reasoner named {interface_name}, silent {'on' if interface.silent_output else 'off'}.") diff --git a/pynars/Console.py b/pynars/Console.py index b4e4c40..8505138 100644 --- a/pynars/Console.py +++ b/pynars/Console.py @@ -114,9 +114,9 @@ def handle_lines(nars: Reasoner, lines: str): for task in tasks_derived: print_out(PrintType.OUT, task.sentence.repr(), *task.budget) # while revising a judgement - if judgement_revised is not None: print_out(PrintType.OUT, judgement_revised.sentence.repr(), - *judgement_revised.budget) - # while revising a goal *judgement_revised.budget) + if judgement_revised is not None: print_out(PrintType.OUT, judgement_revised.sentence.repr(),*judgement_revised.budget) + + # while revising a goal if goal_revised is not None: print_out(PrintType.OUT, goal_revised.sentence.repr(), *goal_revised.budget) # while answering a question for truth value diff --git a/pynars/ConsolePlus.py b/pynars/ConsolePlus.py index f12fa6d..05f146a 100644 --- a/pynars/ConsolePlus.py +++ b/pynars/ConsolePlus.py @@ -244,14 +244,25 @@ def read_file(*args: List[str]) -> None: current_NARS_interface.execute_file(path) -@cmd_register('history') -def print_history(*args: List[str]) -> None: +@cmd_register(('history', 'history-input')) +def print_history_in(*args: List[str]) -> None: '''Format: history [... placeholder] Output the user's input history Default: The Narsese seen at the bottom of the system, not the actual input User actual input cmd: input any parameters can be obtained''' - global _input_history - print('\n'.join(_input_history if args else current_NARS_interface.input_history)) + global input_history + print('\n'.join(input_history if args else current_NARS_interface.input_history)) + + +@cmd_register('history-output') +def print_history_out(*args: List[str]) -> None: + '''Format: history-output [... placeholder] + Output the console's output history + Default: The Narsese seen at the bottom of the system, not the actual input + User actual input cmd: input any parameters can be obtained''' + global output_history + for (interface_name, output_type, content) in output_history: + print(f'{interface_name}: [{output_type}]\t{content}') @cmd_register(('execute', 'exec')) @@ -409,21 +420,50 @@ def macro_repeat(name: str, num_executes: int) -> None: for _ in range(num_executes): macro_exec1(name=name) + +interfaces: Dict[str, Reasoner] = {} +'The dictionary contains all registered NARS interfaces.' + # Reasoner management # -current_NARS_interface: NARSInterface = NARSInterface.construct_interface( +def register_interface(name: str, seed: int = -1, memory: int = 100, capacity: int = 100, silent: bool = False) -> NARSInterface: + ''' + Wrapped from NARSInterface.construct_interface. + - It will auto register the new interface to `reasoners` + - It will add a output handler(uses lambda) to catch its output into output_history + ''' + # create interface + interface = NARSInterface.construct_interface( + seed=seed, + memory=memory, + capacity=capacity, + silent=silent) + # add handler to catch outputs + global output_history + interface.output_handlers.append( + lambda out: output_history.append( + (name, # Name of interface e.g. 'initial' + out.type.name, # Type of output e.g. 'ANSWER' + out.content) # Content of output e.g. ' B>.' + )) + # register in dictionary + interfaces[name] = interface + # return the interface + return interface + + +current_NARS_interface: NARSInterface = register_interface( + 'initial', 137, 500, 500, silent=False) -reasoners: Dict[str, Reasoner] = {'initial': current_NARS_interface} - def current_nar_name() -> str: global current_NARS_interface - for name in reasoners: - if reasoners[name] is current_NARS_interface: + for name in interfaces: + if interfaces[name] is current_NARS_interface: return name return None @@ -442,11 +482,11 @@ def reasoner_list(*keywords: List[str]) -> None: Enumerate existing reasoners; It can be retrieved with parameters''' keywords = keywords if keywords else [''] # Search for a matching interface name - reasoner_names: List[str] = prefix_browse(reasoners, *keywords) + reasoner_names: List[str] = prefix_browse(interfaces, *keywords) # Displays information about "matched interface" if reasoner_names: for name in reasoner_names: # match the list of all cmds, as long as they match the search results - not necessarily in order - interface: NARSInterface = reasoners[name] + interface: NARSInterface = interfaces[name] information: str = '\n\t'+"\n\t".join(f"{name}: {repr(inf)}" for name, inf in [ ('Memory', interface.reasoner.memory), ('Channels', interface.reasoner.channels), @@ -470,16 +510,17 @@ def reasoner_new(name: str, n_memory: int = 100, capacity: int = 100, silent: bo Create a new reasoner and go to the existing reasoner if it exists If an empty name is encountered, the default name is "unnamed"''' global current_NARS_interface - if name in reasoners: + if name in interfaces: print( f'The reasoner exists! Now automatically go to the reasoner "{name}"!') - return (current_NARS_interface := reasoners[name]) + return (current_NARS_interface := interfaces[name]) - reasoners[name] = (current_NARS_interface := NARSInterface.construct_interface( + current_NARS_interface = register_interface( + name=name, + seed=current_NARS_interface.seed, # keep the seed until directly change memory=n_memory, capacity=capacity, - silent=silent - )) + silent=silent) print( f'A reasoner named "{name}" with memory capacity {n_memory}, buffer capacity {capacity}, silent output {" on " if silent else" off "} has been created!') return current_NARS_interface @@ -490,9 +531,9 @@ def reasoner_goto(name: str) -> NARSInterface: '''Format: reasoner-goto < name > Transfers the current reasoner of the program to the specified reasoner''' global current_NARS_interface - if name in reasoners: + if name in interfaces: print(f"Gone to reasoner named '{name}'!") - return (current_NARS_interface := reasoners[name]) + return (current_NARS_interface := interfaces[name]) print(f"There is no reasoner named '{name}'!") @@ -501,12 +542,12 @@ def reasoner_delete(name: str) -> None: '''Format: reasoner-select < name > Deletes the reasoner with the specified name, but cannot delete the current reasoner''' global current_NARS_interface - if name in reasoners: - if reasoners[name] is current_NARS_interface: + if name in interfaces: + if interfaces[name] is current_NARS_interface: print( f'Unable to delete reasoner "{name}", it is the current reasoner!') return - del reasoners[name] + del interfaces[name] print(f'the reasoner named "{name}" has been deleted!') return print(f'There is no reasoner named "{name}"!') @@ -519,11 +560,79 @@ def random_seed(seed: int) -> None: NARSInterface.change_random_seed(seed=seed) +@cmd_register(('server', 'server-ws', 'server-websocket'), (str, 'localhost'), (int, 8765)) +def server_websocket(host: str = 'localhost', port: int = 8765) -> None: + '''Format: server [host: str = 'localhost'] [port: integer = 8765] + Launch a websocket server uses certain host(ip) and port, enables the console to receive input from WS messages. + - format of receiving: same as user input + - format of sending [{"interface_name": XXX, "output_type": XXX, "content": XXX}, ...] + + The default address is ws://localhost:8765. + ! The server blocks the main process, and the service can only be stopped using Ctrl+C.''' + import websockets + from json import dumps + + async def handler(websocket, path): + print(f"Connected with path={path}!") + messages2send:List[str] = [] + len_outputs:int = 0 + last_output_json_objs:list = [] + last_output_json_obj:dict = {} + async for message in websocket: + try: + messages2send.clear() + # execute + execute_input(message) + # handle output using json + if len_outputs < len(output_history): + # clear last sent + last_output_json_objs.clear() + # traverse newer outputs + for i in range(len_outputs, len(output_history)): + # data: (interface_name, output_type, content) + # format: [{"interface_name": XXX, "output_type": XXX, "content": XXX}, ...] + (last_output_json_obj['interface_name'], + last_output_json_obj['output_type'], + last_output_json_obj['content']) = output_history[i] + # append + last_output_json_objs.append(last_output_json_obj.copy()) + # to be sent + messages2send.append(dumps(last_output_json_objs)) + # send result if have + for message2send in messages2send: + print(f"send: {message} -> {message2send}") + await websocket.send(message2send) + # refresh output length + len_outputs = len(output_history) + # when keyboard interrupt + except KeyboardInterrupt as e: + raise e + # it cannot be interrupted by internal exceptions + except BaseException as e: + print(f"Error: {e}") + + # Launch + import asyncio + try: + async def main(): + async with websockets.serve(handler, host, port): + print(f'WS server launched on ws://{host}:{port}.') + await asyncio.Future() + asyncio.run(main()) + except BaseException as e: + print(f'WS server error: {e}') + + # Total index and other variables # _parse_need_slash: bool = False +'Determines whether the last input is a command' + +input_history: List[str] = [] +'History of inputs' -_input_history: List[str] = [] +output_history: List[Tuple[str, str, str]] = [] +'History of outputs. The inner part is (Reasoner Name, PRINT_TYPE, Content)' # Special grammar parser # @@ -610,7 +719,7 @@ def execute_input(inp: str, *other_input: List[str]) -> None: # add to history - _input_history.append(inp) + input_history.append(inp) # pre-jump cmd if inp.startswith('/'): diff --git a/pynars/Interface.py b/pynars/Interface.py index 0d1dbdc..81d9ded 100644 --- a/pynars/Interface.py +++ b/pynars/Interface.py @@ -42,8 +42,9 @@ def narsese_parse_safe(narsese: str) -> Union[None, Task]: class NARSOutput: type: PrintType - content: any + content: str p: float + d: float q: float comment_title: str end: str @@ -214,7 +215,7 @@ def reasoner(self) -> Reasoner: # NARS constructor & initialization # @staticmethod - def construct_interface(seed=-1, memory=100, capacity=100, silent: bool = False): + def construct_interface(seed:int=-1, memory:int=100, capacity:int=100, silent: bool = False): '''Construct the reasoner using specific construction parameters instead of having to construct the reasoner itself in each constructor''' return NARSInterface(seed=seed, NARS=Reasoner( @@ -263,10 +264,10 @@ def print_output(self, type: PrintType, content: any, p: float = None, d: float end=end) # _event_handlers: List[function] = [] # ! error: name 'function' is not defined - _event_handlers: list = [] + _output_handlers: list = [] @property # read only - def event_handlers(self): + def output_handlers(self): '''Registry of event handlers Standard format: ``` @@ -274,11 +275,11 @@ def handler(out: NARSOutput): # your code ``` ''' - return self._event_handlers + return self._output_handlers - def _handle_NARS_output(self, out: NARSOutput): + def _handle_NARS_output(self, out: NARSOutput) -> None: '''Internally traverses the event handler registry table, running its internal functions one by one''' - for handler in self._event_handlers: + for handler in self._output_handlers: try: handler(out) except BaseException as e: @@ -308,7 +309,7 @@ def execute_file(self, path: Union[Path, str]) -> None: _input_history: List[str] = [] @property # readonly - def input_history(self): + def input_history(self) -> List[str]: '''Records texts (statements) entered into the interface''' return self._input_history @@ -410,7 +411,7 @@ def _handle_lines(self, lines: str) -> List[NARSOutput]: return outs # run line - def run_line(self, reasoner: reasoner, line: str): + def run_line(self, reasoner: reasoner, line: str) -> Union[None, List[Task]]: '''Run one line of input''' line = line.strip(' \r\n\t') # ignore spaces # special notations @@ -426,7 +427,7 @@ def run_line(self, reasoner: reasoner, line: str): self.print_output( PrintType.ERROR, f'parse "{line}" failed!' ) - return + return None # empty or comments elif len(line) == 0 or line.startswith("//") or line.startswith("'"): return None