Skip to content

Commit

Permalink
syntaxer2
Browse files Browse the repository at this point in the history
  • Loading branch information
srpathen committed Jul 31, 2024
1 parent 131e035 commit 2811bbc
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 0 deletions.
6 changes: 6 additions & 0 deletions openfasoc/generators/glayout/glayout/syntaxer2/Command.py
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 openfasoc/generators/glayout/glayout/syntaxer2/dynamic_load.py
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 openfasoc/generators/glayout/glayout/syntaxer2/process_input.py
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

0 comments on commit 2811bbc

Please sign in to comment.