1
1
mirror of https://github.com/github/semantic.git synced 2024-12-19 21:01:35 +03:00

Merge pull request #1574 from github/ruby-imports

Eval Ruby require and load
This commit is contained in:
Timothy Clem 2018-03-16 13:32:33 -07:00 committed by GitHub
commit e40a5e7c71
24 changed files with 214 additions and 81 deletions

View File

@ -51,6 +51,7 @@ library
, Data.Abstract.Live
, Data.Abstract.ModuleTable
, Data.Abstract.Number
, Data.Abstract.Path
, Data.Abstract.Type
, Data.Abstract.Value
-- General datatype definitions & generic algorithms
@ -99,6 +100,7 @@ library
, Language.JSON.Assignment
, Language.Ruby.Grammar
, Language.Ruby.Assignment
, Language.Ruby.Syntax
, Language.TypeScript.Assignment
, Language.TypeScript.Grammar
, Language.TypeScript.Syntax
@ -208,6 +210,7 @@ test-suite test
other-modules: Assigning.Assignment.Spec
, Analysis.Go.Spec
, Analysis.Python.Spec
, Analysis.Ruby.Spec
, Analysis.TypeScript.Spec
, Data.Diff.Spec
, Data.Functor.Classes.Generic.Spec

View File

@ -18,6 +18,7 @@ import Data.Abstract.FreeVariables
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Expression as Expression
import qualified Language.Ruby.Syntax as Ruby.Syntax
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Language.Markdown.Syntax as Markdown
@ -98,54 +99,59 @@ instance CustomHasDeclaration whole Declaration.Function where
-- Do not summarize anonymous functions
| isEmpty identifierAnn = Nothing
-- Named functions
| otherwise = Just $ FunctionDeclaration (getSource identifierAnn) (getFunctionSource blob (In ann decl)) blobLanguage
where getSource = toText . flip Source.slice blobSource . getField
isEmpty = (== 0) . rangeLength . getField
| otherwise = Just $ FunctionDeclaration (getSource blobSource identifierAnn) (getFunctionSource blob (In ann decl)) blobLanguage
where isEmpty = (== 0) . rangeLength . getField
-- | Produce a 'MethodDeclaration' for 'Declaration.Method' nodes. If the methods receiver is non-empty (defined as having a non-empty 'Range'), the 'declarationIdentifier' will be formatted as 'receiver.method_name'; otherwise it will be simply 'method_name'.
instance CustomHasDeclaration whole Declaration.Method where
customToDeclaration blob@Blob{..} ann decl@(Declaration.Method _ (Term (In receiverAnn receiverF), _) (Term (In identifierAnn _), _) _ _)
-- Methods without a receiver
| isEmpty receiverAnn = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage Nothing
| isEmpty receiverAnn = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage Nothing
-- Methods with a receiver type and an identifier (e.g. (a *Type) in Go).
| blobLanguage == Just Go
, [ _, Term (In receiverType _) ] <- toList receiverF = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource receiverType))
, [ _, Term (In receiverType _) ] <- toList receiverF = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource blobSource receiverType))
-- Methods with a receiver (class methods) are formatted like `receiver.method_name`
| otherwise = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource receiverAnn))
where getSource = toText . flip Source.slice blobSource . getField
isEmpty = (== 0) . rangeLength . getField
| otherwise = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource blobSource receiverAnn))
where isEmpty = (== 0) . rangeLength . getField
-- | Produce a 'ClassDeclaration' for 'Declaration.Class' nodes.
instance CustomHasDeclaration whole Declaration.Class where
customToDeclaration blob@Blob{..} ann decl@(Declaration.Class _ (Term (In identifierAnn _), _) _ _)
-- Classes
= Just $ ClassDeclaration (getSource identifierAnn) (getClassSource blob (In ann decl)) blobLanguage
where getSource = toText . flip Source.slice blobSource . getField
= Just $ ClassDeclaration (getSource blobSource identifierAnn) (getClassSource blob (In ann decl)) blobLanguage
instance CustomHasDeclaration (Union fs) Declaration.Import where
customToDeclaration Blob{..} _ (Declaration.Import (Term (In fromAnn _), _) symbols _)
= Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) "" (fmap getSymbol symbols) blobLanguage
= Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" (fmap getSymbol symbols) blobLanguage
where
stripQuotes = T.dropAround (`elem` ['"', '\''])
getSource = toText . flip Source.slice blobSource . getField
getSymbol = let f = (T.decodeUtf8 . friendlyName) in bimap f f
instance (Syntax.Identifier :< fs) => CustomHasDeclaration (Union fs) Declaration.QualifiedImport where
customToDeclaration Blob{..} _ (Declaration.QualifiedImport (Term (In fromAnn _), _) (Term (In aliasAnn aliasF), _) symbols)
| Just (Syntax.Identifier alias) <- prj aliasF = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) (toName alias) (fmap getSymbol symbols) blobLanguage
| otherwise = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) (getSource aliasAnn) (fmap getSymbol symbols) blobLanguage
| Just (Syntax.Identifier alias) <- prj aliasF = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) (toName alias) (fmap getSymbol symbols) blobLanguage
| otherwise = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) (getSource blobSource aliasAnn) (fmap getSymbol symbols) blobLanguage
where
stripQuotes = T.dropAround (`elem` ['"', '\''])
getSource = toText . flip Source.slice blobSource . getField
getSymbol = bimap toName toName
toName = T.decodeUtf8 . friendlyName
instance CustomHasDeclaration (Union fs) Declaration.SideEffectImport where
customToDeclaration Blob{..} _ (Declaration.SideEffectImport (Term (In fromAnn _), _) _)
= Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) "" [] blobLanguage
where
stripQuotes = T.dropAround (`elem` ['"', '\''])
getSource = toText . flip Source.slice blobSource . getField
= Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" [] blobLanguage
instance CustomHasDeclaration (Union fs) Ruby.Syntax.Require where
customToDeclaration Blob{..} _ (Ruby.Syntax.Require _ (Term (In fromAnn _), _))
= Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" [] blobLanguage
instance CustomHasDeclaration (Union fs) Ruby.Syntax.Load where
customToDeclaration Blob{..} _ (Ruby.Syntax.Load ((Term (In fromArgs _), _):_))
= Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromArgs) "" [] blobLanguage
customToDeclaration Blob{..} _ (Ruby.Syntax.Load _)
= Nothing
getSource :: HasField fields Range => Source -> Record fields -> Text
getSource blobSource = toText . flip Source.slice blobSource . getField
stripQuotes :: Text -> Text
stripQuotes = T.dropAround (`elem` ['"', '\''])
instance (Syntax.Identifier :< fs, Expression.MemberAccess :< fs) => CustomHasDeclaration (Union fs) Expression.Call where
customToDeclaration Blob{..} _ (Expression.Call _ (Term (In fromAnn fromF), _) _ _)
@ -185,6 +191,7 @@ type family DeclarationStrategy syntax where
DeclarationStrategy Declaration.Import = 'Custom
DeclarationStrategy Declaration.QualifiedImport = 'Custom
DeclarationStrategy Declaration.SideEffectImport = 'Custom
DeclarationStrategy Ruby.Syntax.Require = 'Custom
DeclarationStrategy Declaration.Method = 'Custom
DeclarationStrategy Markdown.Heading = 'Custom
DeclarationStrategy Expression.Call = 'Custom

View File

@ -56,7 +56,7 @@ class (Monad m, Show value) => MonadValue value m where
-- necessary to satisfy implementation details of Haskell left/right shift,
-- but it's fine, since these are only ever operating on integral values.
liftBitwise2 :: (forall a . (Integral a, Bits a) => a -> a -> a)
-> (value -> value -> m value)
-> (value -> value -> m value)
-- | Construct an abstract boolean value.
boolean :: Bool -> m value
@ -80,6 +80,9 @@ class (Monad m, Show value) => MonadValue value m where
-- | Construct an array of zero or more values.
array :: [value] -> m value
-- | Extract a 'ByteString' from a given value.
asString :: value -> m ByteString
-- | Eliminate boolean values. TODO: s/boolean/truthy
ifthenelse :: value -> m a -> m a -> m a
@ -143,9 +146,12 @@ instance ( Monad m
rational = pure . injValue . Value.Rational . Ratio
multiple = pure . injValue . Value.Tuple
array = pure . injValue . Value.Array
asString v
| Just (Value.String n) <- prjValue v = pure n
| otherwise = fail ("expected " <> show v <> " to be a string")
ifthenelse cond if' else'
| Just (Boolean b) <- prjValue cond = if b then if' else else'
| otherwise = fail ("not defined for non-boolean conditions: " <> show cond)

View File

@ -5,16 +5,24 @@ import Prologue
import Data.Term
import Data.ByteString (intercalate)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Abstract.Path
-- | The type of variable names.
type Name = NonEmpty ByteString
-- | Construct a qualified name from a 'ByteString'
name :: ByteString -> Name
name x = x :| []
-- | Construct a qualified name from a list of 'ByteString's
qualifiedName :: [ByteString] -> Name
qualifiedName = NonEmpty.fromList
-- | Construct a qualified 'Name' from a `/` delimited path.
pathToQualifiedName :: ByteString -> Name
pathToQualifiedName = qualifiedName . splitOnPathSeparator
-- | User friendly 'ByteString' of a qualified 'Name'.
friendlyName :: Name -> ByteString
friendlyName xs = intercalate "." (NonEmpty.toList xs)

24
src/Data/Abstract/Path.hs Normal file
View File

@ -0,0 +1,24 @@
module Data.Abstract.Path where
import Prologue
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString as B
import Data.Char (ord)
-- | Split a 'ByteString' path on `/`, stripping quotes and any `./` prefix.
splitOnPathSeparator :: ByteString -> [ByteString]
splitOnPathSeparator = splitOnPathSeparator' id
splitOnPathSeparator' :: (ByteString -> ByteString) -> ByteString -> [ByteString]
splitOnPathSeparator' f = BC.split '/' . f . dropRelativePrefix . stripQuotes
stripQuotes :: ByteString -> ByteString
stripQuotes = B.filter (/= fromIntegral (ord '\"'))
dropRelativePrefix :: ByteString -> ByteString
dropRelativePrefix = BC.dropWhile (== '/') . BC.dropWhile (== '.')
dropExtension :: ByteString -> ByteString
dropExtension path = case BC.split '.' path of
[] -> path
xs -> BC.intercalate "." (Prelude.init xs)

View File

@ -107,7 +107,7 @@ instance Ord1 Identifier where liftCompare = genericLiftCompare
instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable Identifier where
eval (Identifier name) = lookupWith deref name >>= maybe (fail ("free variable: " <> show name)) pure
eval (Identifier name) = lookupWith deref name >>= maybe (fail ("free variable: " <> show (friendlyName name))) pure
instance FreeVariables1 Identifier where
liftFreeVariables _ (Identifier x) = Set.singleton x

View File

@ -8,9 +8,7 @@ module Language.Go.Assignment
import Assigning.Assignment hiding (Assignment, Error)
import Data.Abstract.FreeVariables
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString as B
import Data.Char (ord)
import Data.Abstract.Path
import Data.Record
import Data.Syntax (contextualize, emptyTerm, parseError, handleError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1)
import Language.Go.Grammar as Grammar
@ -395,7 +393,7 @@ importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTe
namedImport = inj <$> (flip Declaration.QualifiedImport <$> packageIdentifier <*> importFromPath <*> pure [])
-- `import "lib/Math"`
plainImport = inj <$> (symbol InterpretedStringLiteral >>= \loc -> do
names <- pathToNames <$> source
names <- splitOnPathSeparator <$> source
let from = makeTerm loc (Syntax.Identifier (qualifiedName names))
let alias = makeTerm loc (Syntax.Identifier (name (last names))) -- Go takes `import "lib/Math"` and uses `Math` as the qualified name (e.g. `Math.Sin()`)
Declaration.QualifiedImport <$> pure from <*> pure alias <*> pure [])
@ -406,9 +404,6 @@ importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTe
importSpec = makeTerm' <$> symbol ImportSpec <*> children (sideEffectImport <|> dotImport <|> namedImport <|> plainImport)
importSpecList = makeTerm <$> symbol ImportSpecList <*> children (manyTerm (importSpec <|> comment))
importFromPath = makeTerm <$> symbol InterpretedStringLiteral <*> (Syntax.Identifier <$> (pathToQualifiedName <$> source))
pathToQualifiedName = qualifiedName . pathToNames
pathToNames = BC.split '/' . (BC.dropWhile (== '/')) . (BC.dropWhile (== '.')) . stripQuotes
stripQuotes = B.filter (/= (fromIntegral (ord '\"')))
indexExpression :: Assignment
indexExpression = makeTerm <$> symbol IndexExpression <*> children (Expression.Subscript <$> expression <*> manyTerm expression)

View File

@ -21,13 +21,13 @@ import qualified Data.Syntax.Expression as Expression
import qualified Data.Syntax.Literal as Literal
import qualified Data.Syntax.Statement as Statement
import qualified Data.Term as Term
import qualified Language.Ruby.Syntax as Ruby.Syntax
-- | The type of Ruby syntax.
type Syntax = '[
Comment.Comment
, Declaration.Class
, Declaration.Function
, Declaration.Import
, Declaration.Method
, Declaration.Module
, Expression.Arithmetic
@ -74,6 +74,8 @@ type Syntax = '[
, Syntax.Error
, Syntax.Identifier
, Syntax.Program
, Ruby.Syntax.Require
, Ruby.Syntax.Load
, []
]
@ -295,15 +297,20 @@ pair :: Assignment
pair = makeTerm <$> symbol Pair <*> children (Literal.KeyValue <$> expression <*> (expression <|> emptyTerm))
methodCall :: Assignment
methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> regularCall)
methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> load <|> regularCall)
where
regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm))
require = inj <$> (symbol Identifier *> do
s <- source
guard (elem s ["autoload", "load", "require", "require_relative"])
Declaration.Import <$> args' <*> pure [] <*> emptyTerm)
guard (elem s ["require", "require_relative"])
Ruby.Syntax.Require (s == "require_relative") <$> nameExpression)
load = inj <$> (symbol Identifier *> do
s <- source
guard (elem s ["load"])
Ruby.Syntax.Load <$> loadArgs)
args = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (many expression) <|> pure []
args' = makeTerm'' <$> (symbol ArgumentList <|> symbol ArgumentListWithParens) <*> children (many expression) <|> emptyTerm
loadArgs = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (some expression)
nameExpression = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children expression
call :: Assignment
call = makeTerm <$> symbol Call <*> children (Expression.MemberAccess <$> expression <*> (expression <|> args))

View File

@ -0,0 +1,54 @@
{-# LANGUAGE DeriveAnyClass #-}
module Language.Ruby.Syntax where
import Control.Monad (unless)
import Control.Abstract.Value (MonadValue)
import Data.Abstract.Environment
import Data.Abstract.Evaluatable
import Data.Abstract.Path
import Data.Abstract.Value (LocationFor)
import Diffing.Algorithm
import Prelude hiding (fail)
import Prologue
import qualified Data.Map as Map
data Require a = Require { requireRelative :: Bool, requirePath :: !a }
deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1)
instance Eq1 Require where liftEq = genericLiftEq
instance Ord1 Require where liftCompare = genericLiftCompare
instance Show1 Require where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable Require where
eval (Require _ x) = do
name <- pathToQualifiedName <$> (subtermValue x >>= asString)
importedEnv <- isolate (require name)
modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv))
unit
newtype Load a = Load { loadArgs :: [a] }
deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1)
instance Eq1 Load where liftEq = genericLiftEq
instance Ord1 Load where liftCompare = genericLiftCompare
instance Show1 Load where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable Load where
eval (Load [x]) = do
path <- subtermValue x >>= asString
doLoad path False
eval (Load [x, wrap]) = do
path <- subtermValue x >>= asString
shouldWrap <- subtermValue wrap >>= toBool
doLoad path shouldWrap
eval (Load _) = fail "invalid argument supplied to load, path is required"
doLoad :: (MonadAnalysis term value m, MonadValue value m, Ord (LocationFor value)) => ByteString -> Bool -> m value
doLoad path shouldWrap = do
let name = pathToQualifiedName path
importedEnv <- isolate (load name)
unless shouldWrap $ modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv))
unit
where pathToQualifiedName = qualifiedName . splitOnPathSeparator' dropExtension
-- TODO: autoload

View File

@ -9,9 +9,6 @@ module Language.TypeScript.Assignment
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import Data.Abstract.FreeVariables
import qualified Data.ByteString as B (filter)
import qualified Data.ByteString.Char8 as BC
import Data.Char (ord)
import Data.Record
import Data.Syntax (emptyTerm, handleError, parseError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1, contextualize, postContextualize)
import qualified Data.Syntax as Syntax
@ -676,12 +673,6 @@ importStatement = makeImportTerm <$> symbol Grammar.ImportStatement <*> childr
fromClause :: Assignment
fromClause = makeTerm <$> symbol Grammar.String <*> (Syntax.Identifier <$> (pathToQualifiedName <$> source))
where
pathToQualifiedName :: ByteString -> Name
pathToQualifiedName = qualifiedName . BC.split '/' . (BC.dropWhile (== '/')) . (BC.dropWhile (== '.')) . stripQuotes
stripQuotes :: ByteString -> ByteString
stripQuotes = B.filter (/= (fromIntegral (ord '\"')))
debuggerStatement :: Assignment
debuggerStatement = makeTerm <$> symbol Grammar.DebuggerStatement <*> (TypeScript.Syntax.Debugger <$ source)

View File

@ -1,4 +1,3 @@
{-# LANGUAGE TypeApplications #-}
module Analysis.Go.Spec (spec) where
import Data.Abstract.Value

View File

@ -1,4 +1,3 @@
{-# LANGUAGE TypeApplications #-}
module Analysis.Python.Spec (spec) where
import Data.Abstract.Value

View File

@ -0,0 +1,35 @@
module Analysis.Ruby.Spec (spec) where
import Data.Abstract.Value
import Data.Map
import SpecHelpers
spec :: Spec
spec = parallel $ do
describe "evalutes Ruby" $ do
it "require_relative" $ do
env <- evaluate "main.rb"
let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ]
env `shouldBe` expectedEnv
it "load" $ do
env <- evaluate "load.rb"
let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ]
env `shouldBe` expectedEnv
it "load wrap" $ do
res <- evaluate' "load-wrap.rb"
fst res `shouldBe` Left "free variable: \"foo\""
snd res `shouldBe` Environment (fromList [ ])
where
addr = Address . Precise
fixtures = "test/fixtures/ruby/analysis/"
evaluate entry = snd <$> evaluate' entry
evaluate' entry = fst . fst . fst . fst <$>
evaluateFiles rubyParser
[ fixtures <> entry
, fixtures <> "foo.rb"
]

View File

@ -1,4 +1,3 @@
{-# LANGUAGE TypeApplications #-}
module Analysis.TypeScript.Spec (spec) where
import Data.Abstract.Value

View File

@ -2,6 +2,7 @@ module Main where
import qualified Analysis.Go.Spec
import qualified Analysis.Python.Spec
import qualified Analysis.Ruby.Spec
import qualified Analysis.TypeScript.Spec
import qualified Assigning.Assignment.Spec
import qualified Data.Diff.Spec
@ -27,6 +28,7 @@ main = hspec $ do
parallel $ do
describe "Analysis.Go" Analysis.Go.Spec.spec
describe "Analysis.Python" Analysis.Python.Spec.spec
describe "Analysis.Ruby" Analysis.Ruby.Spec.spec
describe "Analysis.TypeScript" Analysis.TypeScript.Spec.spec
describe "Assigning.Assignment" Assigning.Assignment.Spec.spec
describe "Data.Diff" Data.Diff.Spec.spec

View File

@ -11,7 +11,7 @@ module SpecHelpers (
import Data.Abstract.Address as X
import Data.Abstract.Environment as X
import Data.Abstract.FreeVariables as X
import Data.Abstract.FreeVariables as X hiding (dropExtension)
import Data.Abstract.Heap as X
import Data.Abstract.ModuleTable as X
import Data.Blob as X

3
test/fixtures/ruby/analysis/foo.rb vendored Normal file
View File

@ -0,0 +1,3 @@
def foo(x)
return x
end

View File

@ -0,0 +1,3 @@
load "./foo.rb", true
foo(1)

3
test/fixtures/ruby/analysis/load.rb vendored Normal file
View File

@ -0,0 +1,3 @@
load "./foo.rb"
foo(1)

3
test/fixtures/ruby/analysis/main.rb vendored Normal file
View File

@ -0,0 +1,3 @@
require_relative "foo"
foo(1)

View File

@ -1,14 +1,11 @@
(Program
(Import
(Require
{ (TextElement)
->(TextElement) }
(Empty))
{+(Import
{+(
{+(Symbol)+}
{+(TextElement)+})+}
{+(Empty)+})+}
{-(Call
->(TextElement) })
(Call
{ (Identifier)
->(Identifier) }
{+(Symbol)+}
{+(TextElement)+}
{-(Identifier)-}
{-(Identifier)-}
{-(Empty)-})-})
(Empty)))

View File

@ -1,14 +1,11 @@
(Program
(Import
(Require
{ (TextElement)
->(TextElement) }
(Empty))
{+(Call
->(TextElement) })
(Call
{ (Identifier)
->(Identifier) }
{+(Identifier)+}
{+(Identifier)+}
{+(Empty)+})+}
{-(Import
{-(
{-(Symbol)-}
{-(TextElement)-})-}
{-(Empty)-})-})
{-(Symbol)-}
{-(TextElement)-}
(Empty)))

View File

@ -1,7 +1,6 @@
(Program
(Import
(TextElement)
(Empty))
(Require
(TextElement))
(Call
(Identifier)
(Identifier)

View File

@ -1,9 +1,8 @@
(Program
(Import
(Require
(TextElement))
(Call
(Identifier)
(Symbol)
(TextElement)
(Empty))
(Import
(
(Symbol)
(TextElement))
(Empty)))