forked from idea-fasoc/OpenFASOC
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# This is the template for the Command class, each command for the StrictSyntax will follow the template described in this file | ||
|
||
class Command: | ||
def __init__(self): | ||
self.name = "" | ||
def |
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
205 changes: 205 additions & 0 deletions
205
openfasoc/generators/glayout/glayout/syntaxer2/dynamic_load.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
# this file contains functions which dynamically create and import cells, and run sessions | ||
import importlib.util | ||
import inspect | ||
import os | ||
import re | ||
from typing import Callable, Union | ||
from pathlib import Path | ||
import tempfile | ||
import glayout.syntaxer.relational | ||
|
||
from glayout.flow.pdk.mappedpdk import MappedPDK | ||
from glayout.flow.pdk.util.port_utils import PortTree | ||
import sys | ||
import traceback | ||
import glayout.syntaxer.process_input | ||
|
||
|
||
def reload_saved_convo(convo_file: Union[Path, str]) -> tuple: | ||
"""restores a conversation from a .convo file | ||
Args: | ||
convo_file (Union[Path, str]): path to the .convo file | ||
Returns tuple: | ||
Session: restored conversation object | ||
loop_count: prompt index after restoring the conversation | ||
""" | ||
savedconvo = Path(convo_file).resolve() # reloads a saved convo | ||
if not savedconvo.is_file(): | ||
raise FileNotFoundError("load conversation should be from an existing file") | ||
with savedconvo.open("r") as loadconvo: | ||
lines = loadconvo.readlines() | ||
for i, line in enumerate(lines): | ||
if i == 0: # creates a convo session | ||
convo = glayout.syntaxer.process_input.Session(inputstream=sys.stdin, outputstream=sys.stdout, toplvlname=line) | ||
continue | ||
convo.process_next_input(line) | ||
loop_count = len(lines) - 1 | ||
print("\n\nloaded conversation from " + str(savedconvo)) | ||
return convo, loop_count | ||
|
||
|
||
def run_session(load_conversation: Union[str, Path], restore_and_exit: bool=False) -> str: | ||
"""Manage, interact, and run conversation sessions from command line | ||
Returns: | ||
str: Glayout python code corresponding to the current session | ||
""" | ||
# start convo and intialize loop counter (will be updated if we read from file) | ||
# if saved convo then load everything from save file, else read form stdio | ||
if load_conversation is not None: | ||
convo, loop_count = reload_saved_convo(load_conversation) # reloads a saved convo file | ||
if restore_and_exit: # if we want it to return the code we do this | ||
return convo.code.get_code() | ||
else: # We process the input, and create a command line interface | ||
convo, loop_count = glayout.syntaxer.process_input.Session(inputstream=sys.stdin, outputstream=sys.stdout), int(0) | ||
# enter design loop | ||
session_ongoing = True | ||
while session_ongoing: # the whole command loop | ||
convo.print_to_stream("\ntask " + str(loop_count)) | ||
loop_count += 1 | ||
convo.print_to_stream(convo.generic_prompt) | ||
try: | ||
session_ongoing = convo.process_next_input(convo.read_from_stream()) | ||
except Exception as e: | ||
print(traceback.format_exc()) | ||
print("an exception was encounterd") | ||
print(str(e)) | ||
print("restoring last valid state and resuming regular program execution\n") | ||
convo = convo.backup | ||
loop_count -= 1 | ||
session_ongoing = True | ||
return convo.code.get_code() | ||
|
||
|
||
|
||
def get_default_arguments(func: Callable, pdk: MappedPDK) -> dict: | ||
"""Gets default arguments to a function based on its argument types. | ||
Args: | ||
func (callable): The function to which default arguments will be added. | ||
pdk (MappedPDK): If one of the non default args is of type MappedPDK, then this pdk is used for default value | ||
Returns: | ||
dict: A dictionary containing default arguments with values based on the argument types of the input function. | ||
""" | ||
# get args that dont have a default | ||
argspec = inspect.getfullargspec(func) | ||
args_with_defaults = argspec.defaults or [] | ||
num_args_without_defaults = len(argspec.args) - len(args_with_defaults) | ||
args_without_defaults = argspec.args[:num_args_without_defaults] | ||
# loop through non default args and try to set some value for them | ||
kwargs = {} | ||
for arg, arg_type in zip(args_without_defaults, argspec.annotations.values()): | ||
# pick some default value | ||
if arg_type == int: | ||
default_value = 2 | ||
elif arg_type == float: | ||
default_value = 2.5 | ||
elif arg_type == bool: | ||
default_value = True | ||
elif arg_type == str: | ||
default_value = "met1" | ||
# hard to guess what a person might want with a tuple, but make it a pair of ints (size) is a safe bet | ||
elif arg_type == tuple: | ||
default_value = (1,1) | ||
# hard to guess what a person might want with a list, but make it a long list of ints is a safe bet | ||
elif arg_type == list: | ||
default_value = [1,1,1,1,1,1,1,1,1] | ||
elif arg_type == MappedPDK: | ||
default_value = pdk | ||
else: # for other types, set default to None | ||
default_value = None | ||
# add this argument to the kwargs | ||
kwargs[arg] = default_value | ||
return kwargs | ||
|
||
|
||
def get_funccell_name(glayout_code: str) -> str: | ||
pattern = r"def\s+([a-zA-Z_][a-zA-Z0-9_]*)cell\s*\(" | ||
return re.search(pattern, glayout_code).group().lstrip("def").rstrip("(").strip() | ||
|
||
class CodeImportHandler: | ||
"""create, manage, destroy temporary files created as part of dynamic importing | ||
contains | ||
self.function (Callable): the function handle | ||
self.func_name (str): the name of the function imported | ||
self.temp_module: the imported module handle | ||
""" | ||
|
||
def __init__(self, glayout_code: Union[str, Path]): | ||
"""create temporary file with glayout python code from glayout_code string | ||
and import the module | ||
Args: | ||
glayout_code (str, Path): string containing cell function and imports. | ||
****or, string or pathlib.Path: path to a convo file | ||
""" | ||
# check if this is a code string or a file | ||
precompiled_code = False | ||
if isinstance(glayout_code, str): | ||
if glayout.syntaxer.relational.GlayoutCode.HEAD_MARKER in glayout_code: | ||
precompiled_code = True | ||
# if this is not a code string, convert NLP file to python code | ||
if not precompiled_code: | ||
glayout_code = run_session(glayout_code, restore_and_exit=True) | ||
# figure out what the cell is called | ||
self.func_name = get_funccell_name(glayout_code) | ||
pymodule_name = self.func_name.removesuffix("_cell") + ".py" | ||
# create the cell py module | ||
with tempfile.TemporaryDirectory() as temp_dir: | ||
temp_dir_path = Path(temp_dir).resolve() | ||
pythonfile = temp_dir_path / pymodule_name | ||
with pythonfile.open(mode="w") as pyfile: | ||
pyfile.write(glayout_code) | ||
# import the cell | ||
spec = importlib.util.spec_from_file_location(self.func_name, pythonfile) | ||
self.temp_module = importlib.util.module_from_spec(spec) | ||
spec.loader.exec_module(self.temp_module) | ||
self.function = getattr(self.temp_module, self.func_name) | ||
|
||
|
||
# this creates a component with a certain amount of arguments | ||
def run_glayout_code_cell(pdk: MappedPDK, glayout_code: str) -> bool: | ||
"""Instantiate a layout from the given glayout cell code and returns Component | ||
Args: | ||
pdk (MappedPDK): pdk to instantiate this cell | ||
glayout_code (str): string containg the cell function and all needed imports | ||
Returns: | ||
Component: gdsfactory Component corresponding to the produced layout | ||
""" | ||
testcell = CodeImportHandler(glayout_code).function | ||
return testcell(**get_default_arguments(testcell, pdk)) | ||
|
||
# this shows the cell | ||
def show_glayout_code_cell(pdk: MappedPDK, glayout_code: str): | ||
"""Instantiate and show a layout from the given glayout cell code | ||
Args: | ||
pdk (MappedPDK): pdk to instantiate this cell | ||
glayout_code (str): string containg the cell function and all needed imports | ||
""" | ||
run_glayout_code_cell(pdk, glayout_code).show() | ||
|
||
|
||
def getPortTree_glayout_code_cell(pdk: MappedPDK, glayout_code: str) -> PortTree: | ||
"""return PortTree for a given glayout cell | ||
Args: | ||
pdk (MappedPDK): pdk to instantiate this cell | ||
glayout_code (str): string containg the cell function and all needed imports | ||
Returns PortTree | ||
""" | ||
return PortTree(run_glayout_code_cell(pdk, glayout_code)) | ||
|
||
def printPortTree_glayout_code_cell(pdk: MappedPDK, glayout_code: str): | ||
"""return PortTree for a given glayout cell | ||
Args: | ||
pdk (MappedPDK): pdk to instantiate this cell | ||
glayout_code (str): string containg the cell function and all needed imports | ||
""" | ||
PortTree(run_glayout_code_cell(pdk, glayout_code)).print(depth=6) |
101 changes: 101 additions & 0 deletions
101
openfasoc/generators/glayout/glayout/syntaxer2/process_input.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import copy | ||
import io | ||
from pathlib import Path | ||
from typing import Optional, Union | ||
from pathlib import Path | ||
|
||
import nltk | ||
import glayout.syntaxer.nltk_init_deps | ||
import glayout.syntaxer.dynamic_load | ||
from glayout.syntaxer.relational import GlayoutCode, parse_direction | ||
from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk | ||
|
||
import importlib.util | ||
|
||
class Session: | ||
"""The session stores all relevant information for producing code from a conversation""" | ||
|
||
generic_prompt = """Place a cell, move a cell, route, create a parameter, or define a variable. | ||
You can also dump code or save this conversation, or enter "help" to see supported syntax in detail | ||
What would you like to do?""" | ||
|
||
def __init__( | ||
self, | ||
outputstream: io.IOBase, | ||
inputstream: Optional[io.IOBase] = None, | ||
toplvlname: Optional[str] = None, | ||
): | ||
"""initialize a conversation and greet the user | ||
Args: | ||
outputstream (io.IOBase): used to print outputs | ||
inputstream (io.IOBase): saved for (optionally) reading in user input, also just provide a string | ||
NOTE: if input stream not provided, str input must be provided | ||
toplvlname (str): in string only mode, you can input toplvl name using this arg | ||
""" | ||
self.inputstream = inputstream | ||
self.outputstream = outputstream | ||
# greet the user and get a top level name for the component | ||
if toplvlname is None: | ||
if inputstream is None: | ||
raise RuntimeError( | ||
"you must specify AT LEAST one of inputstream or name" | ||
) | ||
self.print_to_stream("Hello!") | ||
self.print_to_stream( | ||
"Please provide a name for the Component you want to create" | ||
) | ||
self.print_to_stream( | ||
"remember, this will be the name of your top level component: " | ||
) | ||
self.name = self.read_from_stream().strip() | ||
else: | ||
self.name = str(toplvlname).strip() | ||
# save a list of responses to recreate this component from a .conv file | ||
self.conversation_responses = list() | ||
self.conversation_responses.append(self.name) | ||
# init the code object | ||
self.code = GlayoutCode(self.name) | ||
# create a backup that goes back exactly one call to process_next_input | ||
self.backup = self.__backup() | ||
# list of supported commands | ||
self.commands = [ | ||
importlib.import_module("glayout.syntaxer2.Commands.exit"), | ||
importlib.import_module("glayout.syntaxer2.Commands.help"), | ||
importlib.import_module("glayout.syntaxer2.Commands.import"), | ||
importlib.import_module("glayout.syntaxer2.Commands.move"), | ||
importlib.import_module("glayout.syntaxer2.Commands.route"), | ||
] | ||
|
||
def __backup(self): | ||
"""Produce an exact copy of this class to revert after an exception""" | ||
newobj = self.__class__ | ||
backup = newobj.__new__(newobj) | ||
backup.inputstream = self.inputstream | ||
backup.outputstream = self.outputstream | ||
backup.conversation_responses = copy.deepcopy(self.conversation_responses) | ||
backup.code = copy.deepcopy(self.code) | ||
backup.backup = None | ||
return backup | ||
|
||
def add_command(self, command_file: Union[Path, str]): | ||
"""this imports a command and stores it in a list | ||
Args: | ||
command_file (Union[Path, str]): path to the command file | ||
""" | ||
self.commands.append(importlib.import_module(command_file)) | ||
|
||
def process_next_input(self, text_input: str) -> bool: | ||
"""main driver for doing things | ||
returns True if session is ongoing | ||
""" | ||
self.backup = self.__backup() | ||
sentences = nltk.sent_tokenize(text_input) | ||
for sentence in sentences: | ||
saveresponse = True | ||
sentence = sentence.strip().removesuffix(".") | ||
words = nltk.word_tokenize(sentence) | ||
mode_indicator = words[0].strip().replace("-", "").lower() | ||
|
||
if saveresponse: | ||
self.__save_response(sentence) | ||
return True |