diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f9e97c8..1e678ec 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,464 +1,468 @@ 2, 'class' => array( 'AASTNode' => 'parser/aast/api/AASTNode.php', 'AASTNodeList' => 'parser/aast/api/AASTNodeList.php', 'AASTToken' => 'parser/aast/api/AASTToken.php', 'AASTTree' => 'parser/aast/api/AASTTree.php', 'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php', 'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php', 'AphrontDatabaseConnection' => 'aphront/storage/connection/AphrontDatabaseConnection.php', 'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php', 'AphrontIsolatedDatabaseConnection' => 'aphront/storage/connection/AphrontIsolatedDatabaseConnection.php', 'AphrontMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php', 'AphrontMySQLDatabaseConnectionBase' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnectionBase.php', 'AphrontMySQLiDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php', 'AphrontQueryAccessDeniedException' => 'aphront/storage/exception/AphrontQueryAccessDeniedException.php', 'AphrontQueryConnectionException' => 'aphront/storage/exception/AphrontQueryConnectionException.php', 'AphrontQueryConnectionLostException' => 'aphront/storage/exception/AphrontQueryConnectionLostException.php', 'AphrontQueryCountException' => 'aphront/storage/exception/AphrontQueryCountException.php', 'AphrontQueryDeadlockException' => 'aphront/storage/exception/AphrontQueryDeadlockException.php', 'AphrontQueryDuplicateKeyException' => 'aphront/storage/exception/AphrontQueryDuplicateKeyException.php', 'AphrontQueryException' => 'aphront/storage/exception/AphrontQueryException.php', 'AphrontQueryNotSupportedException' => 'aphront/storage/exception/AphrontQueryNotSupportedException.php', 'AphrontQueryObjectMissingException' => 'aphront/storage/exception/AphrontQueryObjectMissingException.php', 'AphrontQueryParameterException' => 'aphront/storage/exception/AphrontQueryParameterException.php', 'AphrontQueryRecoverableException' => 'aphront/storage/exception/AphrontQueryRecoverableException.php', 'AphrontQuerySchemaException' => 'aphront/storage/exception/AphrontQuerySchemaException.php', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php', 'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php', 'BaseHTTPFuture' => 'future/http/BaseHTTPFuture.php', 'CommandException' => 'future/exec/CommandException.php', 'ConduitClient' => 'conduit/ConduitClient.php', 'ConduitClientException' => 'conduit/ConduitClientException.php', 'ConduitFuture' => 'conduit/ConduitFuture.php', 'ExecFuture' => 'future/exec/ExecFuture.php', 'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php', 'FileFinder' => 'filesystem/FileFinder.php', 'FileList' => 'filesystem/FileList.php', 'Filesystem' => 'filesystem/Filesystem.php', 'FilesystemException' => 'filesystem/FilesystemException.php', 'Future' => 'future/Future.php', 'FutureIterator' => 'future/FutureIterator.php', 'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php', 'FutureProxy' => 'future/FutureProxy.php', 'HTTPFuture' => 'future/http/HTTPFuture.php', 'HTTPFutureResponseStatus' => 'future/http/status/HTTPFutureResponseStatus.php', 'HTTPFutureResponseStatusCURL' => 'future/http/status/HTTPFutureResponseStatusCURL.php', 'HTTPFutureResponseStatusHTTP' => 'future/http/status/HTTPFutureResponseStatusHTTP.php', 'HTTPFutureResponseStatusParse' => 'future/http/status/HTTPFutureResponseStatusParse.php', 'HTTPFutureResponseStatusTransport' => 'future/http/status/HTTPFutureResponseStatusTransport.php', 'HTTPSFuture' => 'future/http/HTTPSFuture.php', 'ImmediateFuture' => 'future/ImmediateFuture.php', 'LinesOfALarge' => 'filesystem/linesofalarge/LinesOfALarge.php', 'LinesOfALargeExecFuture' => 'filesystem/linesofalarge/LinesOfALargeExecFuture.php', 'LinesOfALargeExecFutureTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php', 'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php', 'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php', 'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php', 'Phobject' => 'object/Phobject.php', 'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php', 'PhutilAWSException' => 'future/aws/PhutilAWSException.php', 'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php', 'PhutilAggregateException' => 'error/PhutilAggregateException.php', 'PhutilArgumentParser' => 'parser/argument/PhutilArgumentParser.php', 'PhutilArgumentParserException' => 'parser/argument/exception/PhutilArgumentParserException.php', 'PhutilArgumentParserTestCase' => 'parser/argument/__tests__/PhutilArgumentParserTestCase.php', 'PhutilArgumentSpecification' => 'parser/argument/PhutilArgumentSpecification.php', 'PhutilArgumentSpecificationException' => 'parser/argument/exception/PhutilArgumentSpecificationException.php', 'PhutilArgumentSpecificationTestCase' => 'parser/argument/__tests__/PhutilArgumentSpecificationTestCase.php', 'PhutilArgumentUsageException' => 'parser/argument/exception/PhutilArgumentUsageException.php', 'PhutilArgumentWorkflow' => 'parser/argument/workflow/PhutilArgumentWorkflow.php', 'PhutilArray' => 'utils/PhutilArray.php', 'PhutilArrayTestCase' => 'utils/__tests__/PhutilArrayTestCase.php', 'PhutilArrayWithDefaultValue' => 'utils/PhutilArrayWithDefaultValue.php', 'PhutilBufferedIterator' => 'utils/PhutilBufferedIterator.php', 'PhutilBufferedIteratorExample' => 'utils/PhutilBufferedIteratorExample.php', 'PhutilBufferedIteratorTestCase' => 'utils/__tests__/PhutilBufferedIteratorTestCase.php', 'PhutilCallbackFilterIterator' => 'utils/PhutilCallbackFilterIterator.php', 'PhutilChannel' => 'channel/PhutilChannel.php', 'PhutilChannelChannel' => 'channel/PhutilChannelChannel.php', 'PhutilChunkedIterator' => 'utils/PhutilChunkedIterator.php', 'PhutilChunkedIteratorTestCase' => 'utils/__tests__/PhutilChunkedIteratorTestCase.php', 'PhutilConsole' => 'console/PhutilConsole.php', 'PhutilConsoleFormatter' => 'console/PhutilConsoleFormatter.php', 'PhutilConsoleMessage' => 'console/PhutilConsoleMessage.php', 'PhutilConsoleServer' => 'console/PhutilConsoleServer.php', 'PhutilConsoleServerChannel' => 'console/PhutilConsoleServerChannel.php', 'PhutilConsoleStdinNotInteractiveException' => 'console/PhutilConsoleStdinNotInteractiveException.php', 'PhutilConsoleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php', 'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php', 'PhutilDaemon' => 'daemon/PhutilDaemon.php', 'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.php', 'PhutilDefaultSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php', 'PhutilDefaultSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'markup/syntax/highlighter/pygments/PhutilDefaultSyntaxHighlighterEnginePygmentsFuture.php', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php', 'PhutilDeferredLog' => 'filesystem/PhutilDeferredLog.php', 'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', 'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php', 'PhutilDocblockParser' => 'parser/PhutilDocblockParser.php', 'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php', 'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php', 'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php', 'PhutilErrorHandler' => 'error/PhutilErrorHandler.php', 'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php', 'PhutilEvent' => 'events/PhutilEvent.php', 'PhutilEventConstants' => 'events/constant/PhutilEventConstants.php', 'PhutilEventEngine' => 'events/PhutilEventEngine.php', 'PhutilEventListener' => 'events/PhutilEventListener.php', 'PhutilEventType' => 'events/constant/PhutilEventType.php', 'PhutilExcessiveServiceCallsDaemon' => 'daemon/torture/PhutilExcessiveServiceCallsDaemon.php', 'PhutilExecChannel' => 'channel/PhutilExecChannel.php', 'PhutilFatalDaemon' => 'daemon/torture/PhutilFatalDaemon.php', 'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', 'PhutilGitURI' => 'parser/PhutilGitURI.php', 'PhutilGitURITestCase' => 'parser/__tests__/PhutilGitURITestCase.php', 'PhutilHangForeverDaemon' => 'daemon/torture/PhutilHangForeverDaemon.php', 'PhutilHelpArgumentWorkflow' => 'parser/argument/workflow/PhutilHelpArgumentWorkflow.php', 'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php', 'PhutilJSON' => 'parser/PhutilJSON.php', 'PhutilJSONProtocolChannel' => 'channel/PhutilJSONProtocolChannel.php', 'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php', 'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php', 'PhutilKeyValueCache' => 'cache/PhutilKeyValueCache.php', 'PhutilKeyValueCacheAPC' => 'cache/PhutilKeyValueCacheAPC.php', 'PhutilKeyValueCacheInRequest' => 'cache/PhutilKeyValueCacheInRequest.php', 'PhutilKeyValueCacheMemcache' => 'cache/PhutilKeyValueCacheMemcache.php', 'PhutilKeyValueCacheOnDisk' => 'cache/PhutilKeyValueCacheOnDisk.php', 'PhutilKeyValueCacheStack' => 'cache/PhutilKeyValueCacheStack.php', 'PhutilKeyValueCacheTestCase' => 'cache/__tests__/PhutilKeyValueCacheTestCase.php', 'PhutilLanguageGuesser' => 'parser/PhutilLanguageGuesser.php', 'PhutilLanguageGuesserTestCase' => 'parser/__tests__/PhutilLanguageGuesserTestCase.php', 'PhutilLexer' => 'lexer/PhutilLexer.php', 'PhutilLexerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php', 'PhutilLock' => 'filesystem/PhutilLock.php', 'PhutilLockException' => 'filesystem/PhutilLockException.php', 'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php', 'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php', 'PhutilMissingSymbolException' => 'symbols/exception/PhutilMissingSymbolException.php', 'PhutilNiceDaemon' => 'daemon/torture/PhutilNiceDaemon.php', 'PhutilOpaqueEnvelope' => 'error/PhutilOpaqueEnvelope.php', 'PhutilOpaqueEnvelopeKey' => 'error/PhutilOpaqueEnvelopeKey.php', 'PhutilOpaqueEnvelopeTestCase' => 'error/__tests__/PhutilOpaqueEnvelopeTestCase.php', 'PhutilPHPFragmentLexer' => 'lexer/PhutilPHPFragmentLexer.php', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php', 'PhutilPHPFragmentLexerTestCase' => 'lexer/__tests__/PhutilPHPFragmentLexerTestCase.php', 'PhutilPHPObjectProtocolChannel' => 'channel/PhutilPHPObjectProtocolChannel.php', 'PhutilPHPObjectProtocolChannelTestCase' => 'channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php', 'PhutilPHTTestCase' => 'internationalization/__tests__/PhutilPHTTestCase.php', 'PhutilPerson' => 'internationalization/PhutilPerson.php', 'PhutilPersonTest' => 'internationalization/__tests__/PhutilPersonTest.php', 'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php', 'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php', 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', 'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php', 'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php', 'PhutilRemarkupBlockStorage' => 'markup/engine/remarkup/PhutilRemarkupBlockStorage.php', 'PhutilRemarkupEngine' => 'markup/engine/PhutilRemarkupEngine.php', 'PhutilRemarkupEngineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php', 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php', 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php', 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php', 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php', 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php', 'PhutilRemarkupEngineTestCase' => 'markup/engine/__tests__/PhutilRemarkupEngineTestCase.php', 'PhutilRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRule.php', 'PhutilRemarkupRuleBold' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleBold.php', 'PhutilRemarkupRuleDel' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php', 'PhutilRemarkupRuleDocumentLink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php', 'PhutilRemarkupRuleEscapeHTML' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeHTML.php', 'PhutilRemarkupRuleEscapeRemarkup' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php', 'PhutilRemarkupRuleHyperlink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php', 'PhutilRemarkupRuleItalic' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php', 'PhutilRemarkupRuleLinebreaks' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php', 'PhutilRemarkupRuleMonospace' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php', 'PhutilSaturateStdoutDaemon' => 'daemon/torture/PhutilSaturateStdoutDaemon.php', 'PhutilServiceProfiler' => 'serviceprofiler/PhutilServiceProfiler.php', + 'PhutilShellLexer' => 'lexer/PhutilShellLexer.php', + 'PhutilShellLexerTestCase' => 'lexer/__tests__/PhutilShellLexerTestCase.php', 'PhutilSimpleOptions' => 'parser/PhutilSimpleOptions.php', 'PhutilSimpleOptionsTestCase' => 'parser/__tests__/PhutilSimpleOptionsTestCase.php', 'PhutilSocketChannel' => 'channel/PhutilSocketChannel.php', 'PhutilSprite' => 'sprites/PhutilSprite.php', 'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php', 'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php', 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php', 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php', 'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php', 'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php', 'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php', 'PhutilTranslator' => 'internationalization/PhutilTranslator.php', 'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php', 'PhutilURI' => 'parser/PhutilURI.php', 'PhutilURITestCase' => 'parser/__tests__/PhutilURITestCase.php', 'PhutilUTF8TestCase' => 'utils/__tests__/PhutilUTF8TestCase.php', 'PhutilUtilsTestCase' => 'utils/__tests__/PhutilUtilsTestCase.php', 'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php', 'TempFile' => 'filesystem/TempFile.php', 'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php', 'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php', 'XHPASTSyntaxErrorException' => 'parser/xhpast/api/XHPASTSyntaxErrorException.php', 'XHPASTToken' => 'parser/xhpast/api/XHPASTToken.php', 'XHPASTTree' => 'parser/xhpast/api/XHPASTTree.php', 'XHPASTTreeTestCase' => 'parser/xhpast/api/__tests__/XHPASTTreeTestCase.php', ), 'function' => array( 'Futures' => 'future/functions.php', '_qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php', '_qsprintf_check_type' => 'xsprintf/qsprintf.php', 'array_mergev' => 'utils/utils.php', 'array_select_keys' => 'utils/utils.php', 'assert_instances_of' => 'utils/utils.php', 'coalesce' => 'utils/utils.php', 'csprintf' => 'xsprintf/csprintf.php', 'exec_manual' => 'future/exec/execx.php', 'execx' => 'future/exec/execx.php', 'head' => 'utils/utils.php', 'head_key' => 'utils/utils.php', 'hsprintf' => 'markup/render.php', 'id' => 'utils/utils.php', 'idx' => 'utils/utils.php', 'ifilter' => 'utils/utils.php', 'igroup' => 'utils/utils.php', 'ipull' => 'utils/utils.php', 'isort' => 'utils/utils.php', 'jsprintf' => 'xsprintf/jsprintf.php', 'last' => 'utils/utils.php', 'last_key' => 'utils/utils.php', 'ldap_sprintf' => 'xsprintf/ldapsprintf.php', 'mfilter' => 'utils/utils.php', 'mgroup' => 'utils/utils.php', 'mpull' => 'utils/utils.php', 'msort' => 'utils/utils.php', 'newv' => 'utils/utils.php', 'nonempty' => 'utils/utils.php', 'phlog' => 'error/phlog.php', 'pht' => 'internationalization/pht.php', 'phutil_console_confirm' => 'console/format.php', 'phutil_console_format' => 'console/format.php', 'phutil_console_get_terminal_width' => 'console/format.php', 'phutil_console_prompt' => 'console/format.php', 'phutil_console_require_tty' => 'console/format.php', 'phutil_console_wrap' => 'console/format.php', 'phutil_deprecated' => 'moduleutils/moduleutils.php', 'phutil_error_listener_example' => 'error/phlog.php', 'phutil_escape_html' => 'markup/render.php', 'phutil_escape_uri' => 'markup/render.php', 'phutil_escape_uri_path_component' => 'markup/render.php', 'phutil_get_library_name_for_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php', 'phutil_is_utf8' => 'utils/utf8.php', 'phutil_passthru' => 'future/exec/execx.php', 'phutil_render_tag' => 'markup/render.php', 'phutil_split_lines' => 'utils/utils.php', 'phutil_unescape_uri_path_component' => 'markup/render.php', 'phutil_utf8_console_strlen' => 'utils/utf8.php', 'phutil_utf8_convert' => 'utils/utf8.php', 'phutil_utf8_hard_wrap_html' => 'utils/utf8.php', 'phutil_utf8_shorten' => 'utils/utf8.php', 'phutil_utf8_strlen' => 'utils/utf8.php', 'phutil_utf8ize' => 'utils/utf8.php', 'phutil_utf8v' => 'utils/utf8.php', 'phutil_utf8v_codepoints' => 'utils/utf8.php', 'ppull' => 'utils/utils.php', 'qsprintf' => 'xsprintf/qsprintf.php', 'queryfx' => 'xsprintf/queryfx.php', 'queryfx_all' => 'xsprintf/queryfx.php', 'queryfx_one' => 'xsprintf/queryfx.php', 'vcsprintf' => 'xsprintf/csprintf.php', 'vjsprintf' => 'xsprintf/jsprintf.php', 'vqsprintf' => 'xsprintf/qsprintf.php', 'vqueryfx' => 'xsprintf/queryfx.php', 'vqueryfx_all' => 'xsprintf/queryfx.php', 'xhp_parser_node_constants' => 'parser/xhpast/parser_nodes.php', 'xhpast_get_binary_path' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_build_instructions' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_parser_future' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_is_available' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_parser_token_constants' => 'parser/xhpast/parser_tokens.php', 'xsprintf' => 'xsprintf/xsprintf.php', 'xsprintf_callback_example' => 'xsprintf/xsprintf.php', 'xsprintf_command' => 'xsprintf/csprintf.php', 'xsprintf_javascript' => 'xsprintf/jsprintf.php', 'xsprintf_ldap' => 'xsprintf/ldapsprintf.php', 'xsprintf_query' => 'xsprintf/qsprintf.php', ), 'xmap' => array( 'AASTNodeList' => array( 0 => 'Iterator', 1 => 'Countable', ), 'AbstractDirectedGraphTestCase' => 'PhutilTestCase', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontMySQLDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontMySQLDatabaseConnectionBase' => 'AphrontDatabaseConnection', 'AphrontMySQLiDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDeadlockException' => 'AphrontQueryRecoverableException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryException' => 'Exception', 'AphrontQueryNotSupportedException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontQuerySchemaException' => 'AphrontQueryException', 'BaseHTTPFuture' => 'Future', 'CommandException' => 'Exception', 'ConduitClientException' => 'Exception', 'ConduitFuture' => 'FutureProxy', 'ExecFuture' => 'Future', 'ExecFutureTestCase' => 'PhutilTestCase', 'FilesystemException' => 'Exception', 'FutureIterator' => 'Iterator', 'FutureIteratorTestCase' => 'PhutilTestCase', 'FutureProxy' => 'Future', 'HTTPFuture' => 'BaseHTTPFuture', 'HTTPFutureResponseStatus' => 'Exception', 'HTTPFutureResponseStatusCURL' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatusHTTP' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatusParse' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatusTransport' => 'HTTPFutureResponseStatus', 'HTTPSFuture' => 'BaseHTTPFuture', 'ImmediateFuture' => 'Future', 'LinesOfALarge' => 'Iterator', 'LinesOfALargeExecFuture' => 'LinesOfALarge', 'LinesOfALargeExecFutureTestCase' => 'PhutilTestCase', 'LinesOfALargeFile' => 'LinesOfALarge', 'LinesOfALargeFileTestCase' => 'PhutilTestCase', 'PhutilAWSEC2Future' => 'PhutilAWSFuture', 'PhutilAWSException' => 'Exception', 'PhutilAWSFuture' => 'FutureProxy', 'PhutilAggregateException' => 'Exception', 'PhutilArgumentParserException' => 'Exception', 'PhutilArgumentParserTestCase' => 'PhutilTestCase', 'PhutilArgumentSpecificationException' => 'PhutilArgumentParserException', 'PhutilArgumentSpecificationTestCase' => 'PhutilTestCase', 'PhutilArgumentUsageException' => 'PhutilArgumentParserException', 'PhutilArray' => array( 0 => 'Phobject', 1 => 'Countable', 2 => 'ArrayAccess', 3 => 'Iterator', ), 'PhutilArrayTestCase' => 'PhutilTestCase', 'PhutilArrayWithDefaultValue' => 'PhutilArray', 'PhutilBufferedIterator' => 'Iterator', 'PhutilBufferedIteratorExample' => 'PhutilBufferedIterator', 'PhutilBufferedIteratorTestCase' => 'PhutilTestCase', 'PhutilCallbackFilterIterator' => 'FilterIterator', 'PhutilChannelChannel' => 'PhutilChannel', 'PhutilChunkedIterator' => 'Iterator', 'PhutilChunkedIteratorTestCase' => 'PhutilTestCase', 'PhutilConsoleServerChannel' => 'PhutilChannelChannel', 'PhutilConsoleStdinNotInteractiveException' => 'Exception', 'PhutilConsoleWrapTestCase' => 'PhutilTestCase', 'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', 'PhutilEmailAddressTestCase' => 'PhutilTestCase', 'PhutilErrorHandlerTestCase' => 'PhutilTestCase', 'PhutilEventType' => 'PhutilEventConstants', 'PhutilExcessiveServiceCallsDaemon' => 'PhutilTortureTestDaemon', 'PhutilExecChannel' => 'PhutilChannel', 'PhutilFatalDaemon' => 'PhutilTortureTestDaemon', 'PhutilFileLock' => 'PhutilLock', 'PhutilFileLockTestCase' => 'PhutilTestCase', 'PhutilGitURITestCase' => 'PhutilTestCase', 'PhutilHangForeverDaemon' => 'PhutilTortureTestDaemon', 'PhutilHelpArgumentWorkflow' => 'PhutilArgumentWorkflow', 'PhutilJSONProtocolChannel' => 'PhutilProtocolChannel', 'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilJSONTestCase' => 'PhutilTestCase', 'PhutilKeyValueCacheAPC' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheInRequest' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheMemcache' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheOnDisk' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheTestCase' => 'ArcanistPhutilTestCase', 'PhutilLanguageGuesserTestCase' => 'PhutilTestCase', 'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter', 'PhutilLockException' => 'Exception', 'PhutilMarkupTestCase' => 'PhutilTestCase', 'PhutilMissingSymbolException' => 'Exception', 'PhutilNiceDaemon' => 'PhutilTortureTestDaemon', 'PhutilOpaqueEnvelopeTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexer' => 'PhutilLexer', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexerTestCase' => 'PhutilTestCase', 'PhutilPHPObjectProtocolChannel' => 'PhutilProtocolChannel', 'PhutilPHPObjectProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilPHTTestCase' => 'PhutilTestCase', 'PhutilPersonTest' => 'PhutilPerson', 'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon', 'PhutilProtocolChannel' => 'PhutilChannelChannel', 'PhutilProxyException' => 'Exception', 'PhutilReadableSerializerTestCase' => 'PhutilTestCase', 'PhutilRemarkupEngine' => 'PhutilMarkupEngine', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineTestCase' => 'PhutilTestCase', 'PhutilRemarkupRuleBold' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleDel' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleDocumentLink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeHTML' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeRemarkup' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleHyperlink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleItalic' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleLinebreaks' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleMonospace' => 'PhutilRemarkupRule', 'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon', + 'PhutilShellLexer' => 'PhutilLexer', + 'PhutilShellLexerTestCase' => 'PhutilTestCase', 'PhutilSimpleOptionsTestCase' => 'PhutilTestCase', 'PhutilSocketChannel' => 'PhutilChannel', 'PhutilSyntaxHighlighterException' => 'Exception', 'PhutilTestCase' => 'ArcanistPhutilTestCase', 'PhutilTortureTestDaemon' => 'PhutilDaemon', 'PhutilTranslatorTestCase' => 'PhutilTestCase', 'PhutilURITestCase' => 'PhutilTestCase', 'PhutilUTF8TestCase' => 'PhutilTestCase', 'PhutilUtilsTestCase' => 'PhutilTestCase', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase', 'TestAbstractDirectedGraph' => 'AbstractDirectedGraph', 'XHPASTNode' => 'AASTNode', 'XHPASTSyntaxErrorException' => 'Exception', 'XHPASTToken' => 'AASTToken', 'XHPASTTree' => 'AASTTree', 'XHPASTTreeTestCase' => 'PhutilTestCase', ), )); diff --git a/src/lexer/PhutilLexer.php b/src/lexer/PhutilLexer.php index cf45b5b..67ce6d9 100644 --- a/src/lexer/PhutilLexer.php +++ b/src/lexer/PhutilLexer.php @@ -1,317 +1,326 @@ array(...), * 'state1' => array(...), * 'state2' => array(...), * ) * * Lexers start at the state named 'start'. Each state should have a list of * rules which can match in that state. A list of rules looks like this: * * array( * array('\s+', 'space'), * array('\d+', 'digit'), * array('\w+', 'word'), * ) * * The lexer operates by processing each rule in the current state in order. * When one matches, it produces a token. For example, the lexer above would * lex this text: * * 3 asdf * * ...to produce these tokens (assuming the rules are for the 'start' state): * * array('digit', '3', null), * array('space', ' ', null), * array('word', 'asdf', null), * * A rule can also cause a state transition: * * array('zebra', 'animal', 'saw_zebra'), * * This would match the text "zebra", emit a token of type "animal", and change * the parser state to "saw_zebra", causing the lexer to start using the rules * from that state. * * To pop the lexer's state, you can use the special state '!pop'. * * Finally, you can provide additional options in the fourth parameter. * Supported options are `case-insensitive` and `context`. * * Possible values for `context` are `push` (push the token value onto the * context stack), `pop` (pop the context stack and use it to provide context * for the token), and `discard` (pop the context stack and throw away the * value). * * For example, to lex text like this: * * Class::CONSTANT * * You can use a rule set like this: * * 'start' => array( * array('\w+(?=::)', 'class', 'saw_class', array('context' => 'push')), * ), * 'saw_class' => array( * array('::', 'operator'), * array('\w+', 'constant, '!pop', array('context' => 'pop')), * ), * * This would parse the above text into this token stream: * * array('class', 'Class', null), * array('operator', '::', null), * array('constant', 'CONSTANT', 'Class'), * * For a concrete implementation, see @{class:PhutilPHPFragmentLexer}. * * @task lexerimpl Lexer Implementation * @task rule Lexer Rules * @task tokens Lexer Tokens * * @group lexer */ abstract class PhutilLexer { private $processedRules; + private $lastState; /* -( Lexer Rules )-------------------------------------------------------- */ /** * Return a set of rules for this lexer. See description in * @{class:PhutilLexer}. * * @return dict Lexer rules. * @task lexerimpl */ abstract protected function getRawRules(); /* -( Lexer Rules )-------------------------------------------------------- */ /** * Process, normalize, and validate the raw lexer rules. * * @task rule */ protected function getRules() { $class = get_class($this); $raw_rules = $this->getRawRules(); if (!is_array($raw_rules)) { $type = gettype($raw_rules); throw new UnexpectedValueException( "Expected {$class}->getRawRules() to return array, got {$type}."); } if (empty($raw_rules['start'])) { throw new UnexpectedValueException( "Expected {$class} rules to define rules for state 'start'."); } $processed_rules = array(); foreach ($raw_rules as $state => $rules) { if (!is_array($rules)) { $type = gettype($rules); throw new UnexpectedValueException( "Expected list of rules for state '{$state}' in {$class}, got ". "{$type}."); } foreach ($rules as $key => $rule) { $n = count($rule); if ($n < 2 || $n > 4) { throw new UnexpectedValueException( "Expected rule '{$key}' in state '{$state}' in {$class} to have ". "2-4 elements (regex, token, [next state], [options]), got {$n}."); } $rule = array_values($rule); if (count($rule) == 2) { $rule[] = null; } if (count($rule) == 3) { $rule[] = array(); } foreach ($rule[3] as $option => $value) { switch ($option) { case 'context': if ($value !== 'push' && $value !== 'pop' && $value !== 'discard' && $value !== null) { throw new UnexpectedValueException( "Rule '{$key}' in state '{$state}' in {$class} has unknown ". "context rule '{$value}', expected 'push', 'pop' or ". "'discard'."); } break; default: throw new UnexpectedValueException( "Rule '{$key}' in state '{$state}' in {$class} has unknown ". "option '{$option}'."); } } $flags = 'sS'; // NOTE: The "\G" assertion is an offset-aware version of "^". $rule[0] = '(\\G'.$rule[0].')'.$flags; if (@preg_match($rule[0], '') === false) { $error = error_get_last(); throw new UnexpectedValueException( "Rule '{$key}' in state '{$state}' in {$class} defines an ". "invalid regular expression ('{$rule[0]}'): ". idx($error, 'message')); } $next_state = $rule[2]; if ($next_state !== null && $next_state !== '!pop') { if (empty($raw_rules[$next_state])) { throw new UnexpectedValueException( "Rule '{$key}' in state '{$state}' in {$class} transitions to ". "state '{$next_state}', but there are no rules for that state."); } } $processed_rules[$state][] = $rule; } } return $processed_rules; } /* -( Lexer Tokens )------------------------------------------------------- */ /** * Lex an input string into tokens. * * @param string Input string. * @param string Initial lexer state. * @return list List of lexer tokens. * @task tokens */ public function getTokens($input, $initial_state = 'start') { if (empty($this->processedRules)) { $this->processedRules = $this->getRules(); } $rules = $this->processedRules; + $this->lastState = null; + $position = 0; $length = strlen($input); $tokens = array(); $states = array(); $states[] = 'start'; if ($initial_state != 'start') { $states[] = $initial_state; } $context = array(); while ($position < $length) { $state_rules = idx($rules, end($states), array()); foreach ($state_rules as $rule) { $matches = null; if (!preg_match($rule[0], $input, $matches, 0, $position)) { continue; } list($regexp, $token_type, $next_state, $options) = $rule; $match_length = strlen($matches[0]); if (!$match_length) { if ($next_state === null) { throw new UnexpectedValueException( "Rule '{$regexp}' matched a zero-length token and causes no ". "state transition."); } } else { $position += $match_length; $token = array($token_type, $matches[0]); $copt = idx($options, 'context'); if ($copt == 'push') { $context[] = $matches[0]; $token[] = null; } else if ($copt == 'pop') { if (empty($context)) { throw new UnexpectedValueException( "Rule '{$regexp}' popped empty context!"); } $token[] = array_pop($context); } else if ($copt == 'discard') { if (empty($context)) { throw new UnexpectedValueException( "Rule '{$regexp}' discarded empty context!"); } array_pop($context); $token[] = null; } else { $token[] = null; } $tokens[] = $token; } if ($next_state !== null) { if ($next_state == '!pop') { array_pop($states); if (empty($states)) { throw new UnexpectedValueException( "Rule '{$regexp}' popped off the last state."); } } else { $states[] = $next_state; } } continue 2; } throw new UnexpectedValueException( "No lexer rule matched input at char {$position}."); } + $this->lastState = $states; + return $tokens; } /** * Merge adjacent tokens of the same type. For example, if a comment is * tokenized as <"//", "comment">, this method will merge the two tokens into * a single combined token. */ public function mergeTokens(array $tokens) { $last = null; $result = array(); foreach ($tokens as $token) { if ($last === null) { $last = $token; continue; } if (($token[0] == $last[0]) && ($token[2] == $last[2])) { $last[1] .= $token[1]; } else { $result[] = $last; $last = $token; } } if ($last !== null) { $result[] = $last; } return $result; } + public function getLexerState() { + return $this->lastState; + } + } diff --git a/src/lexer/PhutilShellLexer.php b/src/lexer/PhutilShellLexer.php new file mode 100644 index 0000000..345f400 --- /dev/null +++ b/src/lexer/PhutilShellLexer.php @@ -0,0 +1,87 @@ +getTokens($string); + if (count($this->getLexerState()) > 1) { + throw new UnexpectedValueException( + "Unterminated string in argument list!"); + } + + foreach ($tokens as $key => $token) { + switch ($token[0]) { + case "'": + case '"': + unset($tokens[$key]); + break; + case 'esc': + $tokens[$key][0] = 'arg'; + $tokens[$key][1] = substr($token[1], 1); + break; + default: + break; + } + } + + $tokens = $this->mergeTokens(array_values($tokens)); + + $argv = array(); + foreach ($tokens as $token) { + if ($token[0] == 'arg') { + $argv[] = $token[1]; + } + } + + return $argv; + } + + protected function getRawRules() { + return array( + 'start' => array( + array('\s+', ' '), + array("'", "'", 'string1'), + array('"', '"', 'string2'), + array('\\\\.', 'esc'), + array('[^\\s\'"\\\\]+', 'arg'), + ), + 'string1' => array( + // NOTE: In a single-quoted string, backslash is not an escape. + array('[^\']+', 'arg'), + array("'", "'", '!pop'), + ), + 'string2' => array( + // NOTE: In a double-quoted string, backslash IS an escape, but only + // for some characters: ", $, `, \ and newline. + array('[^"\\\\]+', 'arg'), + array('"', '"', '!pop'), + array('\\\\["$`\\\\\\n]', 'esc'), + array('\\\\.', 'arg'), + ), + ); + } +} diff --git a/src/lexer/__tests__/PhutilPHPFragmentLexerTestCase.php b/src/lexer/__tests__/PhutilPHPFragmentLexerTestCase.php index caacf05..7036163 100644 --- a/src/lexer/__tests__/PhutilPHPFragmentLexerTestCase.php +++ b/src/lexer/__tests__/PhutilPHPFragmentLexerTestCase.php @@ -1,308 +1,308 @@ runLexer($file, $data); } } private function runLexer($file, $data) { $lexer = new PhutilPHPFragmentLexer(); switch ($file) { case 'pop-from-php.txt': $initial_state = 'php'; break; default: $initial_state = 'start'; break; } $caught = null; $tokens = null; try { $tokens = $lexer->getTokens($data, $initial_state); } catch (Exception $ex) { $caught = $ex; } switch ($file) { case 'basics.txt': $this->assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $tokens = $lexer->mergeTokens($tokens); $this->assertEqual( array( array('cp', '', null), array(null, "\n\nd\n", null), ), $tokens, $file); break; case 'extendsimplements.txt': $this->assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $this->assertEqual( array( array('cp', 'assertEqual(null, $caught); $this->assertEqual( array( array('cp', '?>', null), array(null, "\n", null), ), $tokens, $file); break; default: throw new Exception("No assertion block for test '{$file}'!"); } } } diff --git a/src/lexer/__tests__/PhutilShellLexerTestCase.php b/src/lexer/__tests__/PhutilShellLexerTestCase.php new file mode 100644 index 0000000..2e6dcdd --- /dev/null +++ b/src/lexer/__tests__/PhutilShellLexerTestCase.php @@ -0,0 +1,208 @@ +runLexer($file, $data); + } + } + + private function runLexer($file, $data) { + $lexer = new PhutilShellLexer(); + + $initial_state = 'start'; + + $caught = null; + $tokens = null; + try { + $tokens = $lexer->getTokens($data, $initial_state); + } catch (Exception $ex) { + $caught = $ex; + } + + $argv = null; + try { + $argv = $lexer->splitArguments($data); + } catch (Exception $ex) { + // Ignore; not diagnostically useful. + } + + switch ($file) { + case 'basic.txt': + $this->assertEqual(null, $caught); + $this->assertEqual( + array( + array('arg', 'arg1', null), + array(' ', ' ', null), + array('arg', 'arg2', null), + array(' ', ' ', null), + array('arg', 'arg3', null), + ), + $tokens, + $file); + $this->assertEqual( + array( + 'arg1', + 'arg2', + 'arg3', + ), + $argv, + $file); + break; + case 'escape.txt': + $this->assertEqual(null, $caught); + $this->assertEqual( + array( + array("'", "'", null), + array('arg', '\\', null), + array("'", "'", null), + array(' ', ' ', null), + array('"', '"', null), + array('esc', '\\"', null), + array('"', '"', null), + ), + $tokens, + $file); + $this->assertEqual( + array( + '\\', + '"', + ), + $argv, + $file); + break; + case 'slashes.txt': + $this->assertEqual(null, $caught); + $this->assertEqual( + array( + array('arg', 'a', null), + array('esc', '\\ ', null), + array('arg', 'b', null), + array(' ', ' ', null), + array("'", "'", null), + array('arg', 'a\\b', null), + array("'", "'", null), + array(' ', ' ', null), + array('"', '"', null), + array('arg', 'a', null), + array('arg', '\\b', null), + array('"', '"', null), + array(' ', ' ', null), + array('"', '"', null), + array('esc', '\\$', null), + array('esc', '\\`', null), + array('esc', '\\\\', null), + array('esc', '\\"', null), + array('esc', '\\'."\n", null), + array('arg', 'xyz', null), + array('"', '"', null), + ), + $tokens, + $file); + $this->assertEqual( + array( + 'a b', + 'a\\b', + 'a\\b', + '$`\\"'."\n".'xyz', + ), + $argv, + $file); + break; + case 'spaces.txt': + $this->assertEqual( + array( + array('arg', 'arg1', null), + array(' ', ' ', null), + array('arg', 'arg2', null), + array(' ', ' ', null), + array('arg', 'arg3', null), + ), + $tokens, + $file); + $this->assertEqual( + array( + 'arg1', + 'arg2', + 'arg3', + ), + $argv, + $file); + break; + case 'strings.txt': + $this->assertEqual(null, $caught); + $this->assertEqual( + array( + array('arg', 'a', null), + array(' ', ' ', null), + array("'", "'", null), + array('arg', 'b', null), + array("'", "'", null), + array(' ', ' ', null), + array('"', '"', null), + array('arg', 'c', null), + array('"', '"', null), + array(' ', ' ', null), + array("'", "'", null), + array('arg', 'd', null), + array("'", "'", null), + array("'", "'", null), + array('arg', 'e', null), + array("'", "'", null), + array(' ', ' ', null), + array('"', '"', null), + array('arg', 'f', null), + array('"', '"', null), + array('"', '"', null), + array('arg', 'g', null), + array('"', '"', null), + array(' ', ' ', null), + array('"', '"', null), + array('arg', 'h', null), + array('"', '"', null), + array('"', '"', null), + array('arg', "'", null), + array('"', '"', null), + array('"', '"', null), + array('arg', 'i', null), + array('"', '"', null), + ), + $tokens, + $file); + $this->assertEqual( + array( + 'a', + 'b', + 'c', + 'de', + 'fg', + 'h\'i', + ), + $argv, + $file); + break; + case 'unterminated.txt': + $this->assertEqual(null, $caught); + $this->assertEqual( + array( + 'start', + 'string1', + ), + $lexer->getLexerState(), + $file); + $this->assertEqual( + null, + $argv, + $file); + break; + default: + throw new Exception("No assertion block for test '{$file}'!"); + } + } + + +} diff --git a/src/lexer/__tests__/shell/basic.txt b/src/lexer/__tests__/shell/basic.txt new file mode 100644 index 0000000..16cd558 --- /dev/null +++ b/src/lexer/__tests__/shell/basic.txt @@ -0,0 +1 @@ +arg1 arg2 arg3 diff --git a/src/lexer/__tests__/shell/escape.txt b/src/lexer/__tests__/shell/escape.txt new file mode 100644 index 0000000..1cc4754 --- /dev/null +++ b/src/lexer/__tests__/shell/escape.txt @@ -0,0 +1 @@ +'\' "\"" diff --git a/src/lexer/__tests__/shell/slashes.txt b/src/lexer/__tests__/shell/slashes.txt new file mode 100644 index 0000000..bf2e310 --- /dev/null +++ b/src/lexer/__tests__/shell/slashes.txt @@ -0,0 +1,2 @@ +a\ b 'a\b' "a\b" "\$\`\\\"\ +xyz" diff --git a/src/lexer/__tests__/shell/spaces.txt b/src/lexer/__tests__/shell/spaces.txt new file mode 100644 index 0000000..1665363 --- /dev/null +++ b/src/lexer/__tests__/shell/spaces.txt @@ -0,0 +1 @@ +arg1 arg2 arg3 diff --git a/src/lexer/__tests__/shell/strings.txt b/src/lexer/__tests__/shell/strings.txt new file mode 100644 index 0000000..575a746 --- /dev/null +++ b/src/lexer/__tests__/shell/strings.txt @@ -0,0 +1 @@ +a 'b' "c" 'd''e' "f""g" "h""'""i" diff --git a/src/lexer/__tests__/shell/unterminated.txt b/src/lexer/__tests__/shell/unterminated.txt new file mode 100644 index 0000000..e67d34d --- /dev/null +++ b/src/lexer/__tests__/shell/unterminated.txt @@ -0,0 +1 @@ +a 'b