diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 8051db6e59f..1f5c2e8342a 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(silversearcher) # Level 3: (only depends on Level 2 and below) add_subdirectory(bookmarks) add_subdirectory(cppeditor) +add_subdirectory(haskell) add_subdirectory(help) add_subdirectory(resourceeditor) add_subdirectory(nim) diff --git a/src/plugins/haskell/CMakeLists.txt b/src/plugins/haskell/CMakeLists.txt new file mode 100644 index 00000000000..8f6cc4f1d85 --- /dev/null +++ b/src/plugins/haskell/CMakeLists.txt @@ -0,0 +1,27 @@ +add_qtc_plugin(Haskell + PLUGIN_DEPENDS + QtCreator::Core QtCreator::TextEditor QtCreator::ProjectExplorer + DEPENDS Qt5::Widgets + SOURCES + haskell.qrc + haskell_global.h + haskellbuildconfiguration.cpp haskellbuildconfiguration.h + haskellconstants.h + haskelleditorfactory.cpp haskelleditorfactory.h + haskellhighlighter.cpp haskellhighlighter.h + haskellmanager.cpp haskellmanager.h + haskellplugin.cpp haskellplugin.h + haskellproject.cpp haskellproject.h + haskellrunconfiguration.cpp haskellrunconfiguration.h + haskelltokenizer.cpp haskelltokenizer.h + optionspage.cpp optionspage.h + stackbuildstep.cpp stackbuildstep.h +) + +qtc_add_resources(Haskell haskell_wizards + PREFIX "/haskell" + BASE share/wizards + FILES + module/file.hs + module/wizard.json +) diff --git a/src/plugins/haskell/Haskell.json.in b/src/plugins/haskell/Haskell.json.in new file mode 100644 index 00000000000..f9062c88b27 --- /dev/null +++ b/src/plugins/haskell/Haskell.json.in @@ -0,0 +1,23 @@ +{ + \"Name\" : \"Haskell\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"DisabledByDefault\" : true, + \"Vendor\" : \"Eike Ziller\", + \"Copyright\" : \"(C) Eike Ziller\", + \"License\" : \"MIT\", + \"Description\" : \"Haskell support\", + \"Url\" : \"https://haskell.org\", + $$dependencyList, + + \"Mimetypes\" : [ + \"\", + \"\", + \" \", + \" \", + \" Haskell Cabal project file\", + \" \", + \" \", + \"\" + ] +} diff --git a/src/plugins/haskell/haskell.qbs b/src/plugins/haskell/haskell.qbs new file mode 100644 index 00000000000..30eb8328f1a --- /dev/null +++ b/src/plugins/haskell/haskell.qbs @@ -0,0 +1,28 @@ +import qbs 1.0 + +QtcPlugin { + name: "Haskell" + + Depends { name: "Qt.widgets" } + Depends { name: "Utils" } + + Depends { name: "Core" } + Depends { name: "TextEditor" } + Depends { name: "ProjectExplorer" } + + files: [ + "haskell.qrc", + "haskellbuildconfiguration.cpp", "haskellbuildconfiguration.h", + "haskellconstants.h", + "haskelleditorfactory.cpp", "haskelleditorfactory.h", + "haskell_global.h", + "haskellhighlighter.cpp", "haskellhighlighter.h", + "haskellmanager.cpp", "haskellmanager.h", + "haskellplugin.cpp", "haskellplugin.h", + "haskellproject.cpp", "haskellproject.h", + "haskellrunconfiguration.cpp", "haskellrunconfiguration.h", + "haskelltokenizer.cpp", "haskelltokenizer.h", + "optionspage.cpp", "optionspage.h", + "stackbuildstep.cpp", "stackbuildstep.h" + ] +} diff --git a/src/plugins/haskell/haskell.qrc b/src/plugins/haskell/haskell.qrc new file mode 100644 index 00000000000..654f45818c3 --- /dev/null +++ b/src/plugins/haskell/haskell.qrc @@ -0,0 +1,5 @@ + + + images/category_haskell.png + + diff --git a/src/plugins/haskell/haskell_global.h b/src/plugins/haskell/haskell_global.h new file mode 100644 index 00000000000..9452e44653c --- /dev/null +++ b/src/plugins/haskell/haskell_global.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#if defined(HASKELL_LIBRARY) +# define HASKELLSHARED_EXPORT Q_DECL_EXPORT +#else +# define HASKELLSHARED_EXPORT Q_DECL_IMPORT +#endif diff --git a/src/plugins/haskell/haskellbuildconfiguration.cpp b/src/plugins/haskell/haskellbuildconfiguration.cpp new file mode 100644 index 00000000000..77bee060a77 --- /dev/null +++ b/src/plugins/haskell/haskellbuildconfiguration.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskellbuildconfiguration.h" + +#include "haskellconstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ProjectExplorer; + +const char C_HASKELL_BUILDCONFIGURATION_ID[] = "Haskell.BuildConfiguration"; + +namespace Haskell { +namespace Internal { + +HaskellBuildConfigurationFactory::HaskellBuildConfigurationFactory() +{ + registerBuildConfiguration(C_HASKELL_BUILDCONFIGURATION_ID); + setSupportedProjectType(Constants::C_HASKELL_PROJECT_ID); + setSupportedProjectMimeTypeName(Constants::C_HASKELL_PROJECT_MIMETYPE); + + setBuildGenerator([](const Kit *k, const Utils::FilePath &projectPath, bool forSetup) { + BuildInfo info; + info.typeName = HaskellBuildConfiguration::tr("Release"); + if (forSetup) { + info.displayName = info.typeName; + info.buildDirectory = projectPath.parentDir().pathAppended(".stack-work"); + } + info.kitId = k->id(); + info.buildType = BuildConfiguration::BuildType::Release; + return QList{info}; + }); +} + +HaskellBuildConfiguration::HaskellBuildConfiguration(Target *target, Utils::Id id) + : BuildConfiguration(target, id) +{ + setInitializer([this](const BuildInfo &info) { + setBuildDirectory(info.buildDirectory); + setBuildType(info.buildType); + setDisplayName(info.displayName); + }); + appendInitialBuildStep(Constants::C_STACK_BUILD_STEP_ID); +} + +NamedWidget *HaskellBuildConfiguration::createConfigWidget() +{ + return new HaskellBuildConfigurationWidget(this); +} + +BuildConfiguration::BuildType HaskellBuildConfiguration::buildType() const +{ + return m_buildType; +} + +void HaskellBuildConfiguration::setBuildType(BuildConfiguration::BuildType type) +{ + m_buildType = type; +} + +HaskellBuildConfigurationWidget::HaskellBuildConfigurationWidget(HaskellBuildConfiguration *bc) + : NamedWidget(tr("General")) + , m_buildConfiguration(bc) +{ + setLayout(new QVBoxLayout); + layout()->setContentsMargins(0, 0, 0, 0); + auto box = new Utils::DetailsWidget; + box->setState(Utils::DetailsWidget::NoSummary); + layout()->addWidget(box); + auto details = new QWidget; + box->setWidget(details); + details->setLayout(new QHBoxLayout); + details->layout()->setContentsMargins(0, 0, 0, 0); + details->layout()->addWidget(new QLabel(tr("Build directory:"))); + + auto buildDirectoryInput = new Utils::PathChooser; + buildDirectoryInput->setExpectedKind(Utils::PathChooser::Directory); + buildDirectoryInput->setFilePath(m_buildConfiguration->buildDirectory()); + details->layout()->addWidget(buildDirectoryInput); + + connect(m_buildConfiguration, + &BuildConfiguration::buildDirectoryChanged, + buildDirectoryInput, + [this, buildDirectoryInput] { + buildDirectoryInput->setFilePath(m_buildConfiguration->buildDirectory()); + }); + connect(buildDirectoryInput, + &Utils::PathChooser::textChanged, + m_buildConfiguration, + [this, buildDirectoryInput](const QString &) { + m_buildConfiguration->setBuildDirectory(buildDirectoryInput->rawFilePath()); + }); +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellbuildconfiguration.h b/src/plugins/haskell/haskellbuildconfiguration.h new file mode 100644 index 00000000000..c93161e5353 --- /dev/null +++ b/src/plugins/haskell/haskellbuildconfiguration.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace Haskell { +namespace Internal { + +class HaskellBuildConfigurationFactory : public ProjectExplorer::BuildConfigurationFactory +{ +public: + HaskellBuildConfigurationFactory(); +}; + +class HaskellBuildConfiguration : public ProjectExplorer::BuildConfiguration +{ + Q_OBJECT + +public: + HaskellBuildConfiguration(ProjectExplorer::Target *target, Utils::Id id); + + ProjectExplorer::NamedWidget *createConfigWidget() override; + BuildType buildType() const override; + void setBuildType(BuildType type); + +private: + BuildType m_buildType = BuildType::Release; +}; + +class HaskellBuildConfigurationWidget : public ProjectExplorer::NamedWidget +{ + Q_OBJECT + +public: + HaskellBuildConfigurationWidget(HaskellBuildConfiguration *bc); + +private: + HaskellBuildConfiguration *m_buildConfiguration; +}; + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellconstants.h b/src/plugins/haskell/haskellconstants.h new file mode 100644 index 00000000000..8b63761ada0 --- /dev/null +++ b/src/plugins/haskell/haskellconstants.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +namespace Haskell { +namespace Constants { + +const char C_HASKELLEDITOR_ID[] = "Haskell.HaskellEditor"; +const char C_HASKELLSNIPPETSGROUP_ID[] = "Haskell"; +const char C_HASKELL_PROJECT_MIMETYPE[] = "text/x-haskell-project"; +const char C_HASKELL_PROJECT_ID[] = "Haskell.Project"; +const char C_HASKELL_RUNCONFIG_ID[] = "Haskell.RunConfiguration"; +const char C_STACK_BUILD_STEP_ID[] = "Haskell.Stack.Build"; +const char OPTIONS_GENERAL[] = "Haskell.A.General"; +const char A_RUN_GHCI[] = "Haskell.RunGHCi"; + +} // namespace Haskell +} // namespace Constants diff --git a/src/plugins/haskell/haskelleditorfactory.cpp b/src/plugins/haskell/haskelleditorfactory.cpp new file mode 100644 index 00000000000..206677915ba --- /dev/null +++ b/src/plugins/haskell/haskelleditorfactory.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskelleditorfactory.h" + +#include "haskellconstants.h" +#include "haskellhighlighter.h" +#include "haskellmanager.h" + +#include +#include +#include +#include + +#include + +namespace Haskell { +namespace Internal { + +static QWidget *createEditorWidget() +{ + auto widget = new TextEditor::TextEditorWidget; + auto ghciButton = new Core::CommandButton(Constants::A_RUN_GHCI, widget); + ghciButton->setText(HaskellManager::tr("GHCi")); + QObject::connect(ghciButton, &QToolButton::clicked, HaskellManager::instance(), [widget] { + HaskellManager::openGhci(widget->textDocument()->filePath()); + }); + widget->insertExtraToolBarWidget(TextEditor::TextEditorWidget::Left, ghciButton); + return widget; +} + +HaskellEditorFactory::HaskellEditorFactory() +{ + setId(Constants::C_HASKELLEDITOR_ID); + setDisplayName(QCoreApplication::translate("OpenWith::Editors", "Haskell Editor")); + addMimeType("text/x-haskell"); + setEditorActionHandlers(TextEditor::TextEditorActionHandler::UnCommentSelection + | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); + setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_HASKELLEDITOR_ID); }); + setIndenterCreator([](QTextDocument *doc) { return new TextEditor::TextIndenter(doc); }); + setEditorWidgetCreator(createEditorWidget); + setCommentDefinition(Utils::CommentDefinition("--", "{-", "-}")); + setParenthesesMatchingEnabled(true); + setMarksVisible(true); + setSyntaxHighlighterCreator([] { return new HaskellHighlighter(); }); +} + +} // Internal +} // Haskell diff --git a/src/plugins/haskell/haskelleditorfactory.h b/src/plugins/haskell/haskelleditorfactory.h new file mode 100644 index 00000000000..a48632d289d --- /dev/null +++ b/src/plugins/haskell/haskelleditorfactory.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Haskell { +namespace Internal { + +class HaskellEditorFactory : public TextEditor::TextEditorFactory +{ +public: + HaskellEditorFactory(); +}; + +} // Internal +} // Haskell diff --git a/src/plugins/haskell/haskellhighlighter.cpp b/src/plugins/haskell/haskellhighlighter.cpp new file mode 100644 index 00000000000..8a5b6cae76e --- /dev/null +++ b/src/plugins/haskell/haskellhighlighter.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskellhighlighter.h" + +#include "haskelltokenizer.h" + +#include +#include +#include + +#include +#include + +Q_GLOBAL_STATIC_WITH_ARGS(QSet, IMPORT_HIGHLIGHTS, ({ + "qualified", + "as", + "hiding" +})); + +using namespace TextEditor; + +namespace Haskell { +namespace Internal { + +HaskellHighlighter::HaskellHighlighter() +{ + setDefaultTextFormatCategories(); + updateFormats(TextEditorSettings::fontSettings()); +} + +void HaskellHighlighter::highlightBlock(const QString &text) +{ + const Tokens tokens = HaskellTokenizer::tokenize(text, previousBlockState()); + setCurrentBlockState(tokens.state); + const Token *firstNonWS = 0; + const Token *secondNonWS = 0; + bool inType = false; + bool inImport = false; + for (const Token & token : tokens) { + switch (token.type) { + case TokenType::Variable: + if (inType) + setTokenFormat(token, C_LOCAL); + else if (inImport && IMPORT_HIGHLIGHTS->contains(token.text.toString())) + setTokenFormat(token, C_KEYWORD); +// else +// setTokenFormat(token, C_TEXT); + break; + case TokenType::Constructor: + case TokenType::OperatorConstructor: + setTokenFormat(token, C_TYPE); + break; + case TokenType::Operator: + setTokenFormat(token, C_OPERATOR); + break; + case TokenType::Whitespace: + setTokenFormat(token, C_VISUAL_WHITESPACE); + break; + case TokenType::Keyword: + if (token.text == QLatin1String("::") && firstNonWS && !secondNonWS) { // toplevel declaration + setFormat(firstNonWS->startCol, firstNonWS->length, m_toplevelDeclFormat); + inType = true; + } else if (token.text == QLatin1String("import")) { + inImport = true; + } + setTokenFormat(token, C_KEYWORD); + break; + case TokenType::Integer: + case TokenType::Float: + setTokenFormat(token, C_NUMBER); + break; + case TokenType::String: + setTokenFormatWithSpaces(text, token, C_STRING); + break; + case TokenType::Char: + setTokenFormatWithSpaces(text, token, C_STRING); + break; + case TokenType::EscapeSequence: + setTokenFormat(token, C_PRIMITIVE_TYPE); + break; + case TokenType::SingleLineComment: + setTokenFormatWithSpaces(text, token, C_COMMENT); + break; + case TokenType::MultiLineComment: + setTokenFormatWithSpaces(text, token, C_COMMENT); + break; + case TokenType::Special: +// setTokenFormat(token, C_TEXT); + break; + case TokenType::StringError: + case TokenType::CharError: + case TokenType::Unknown: + setTokenFormat(token, C_PARENTHESES_MISMATCH); + break; + } + if (token.type != TokenType::Whitespace) { + if (!firstNonWS) + firstNonWS = &token; + else if (!secondNonWS) + secondNonWS = &token; + } + } +} + +void HaskellHighlighter::setFontSettings(const FontSettings &fontSettings) +{ + SyntaxHighlighter::setFontSettings(fontSettings); + updateFormats(fontSettings); +} + +void HaskellHighlighter::updateFormats(const FontSettings &fontSettings) +{ + m_toplevelDeclFormat = fontSettings.toTextCharFormat( + TextStyles::mixinStyle(C_FUNCTION, C_DECLARATION)); +} + +void HaskellHighlighter::setTokenFormat(const Token &token, TextStyle style) +{ + setFormat(token.startCol, token.length, formatForCategory(style)); +} + +void HaskellHighlighter::setTokenFormatWithSpaces(const QString &text, const Token &token, + TextStyle style) +{ + setFormatWithSpaces(text, token.startCol, token.length, formatForCategory(style)); +} + +} // Internal +} // Haskell diff --git a/src/plugins/haskell/haskellhighlighter.h b/src/plugins/haskell/haskellhighlighter.h new file mode 100644 index 00000000000..621333341bc --- /dev/null +++ b/src/plugins/haskell/haskellhighlighter.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include + +namespace Haskell { +namespace Internal { + +class Token; + +class HaskellHighlighter : public TextEditor::SyntaxHighlighter +{ + Q_OBJECT + +public: + HaskellHighlighter(); + +protected: + void highlightBlock(const QString &text) override; + +private: + void setFontSettings(const TextEditor::FontSettings &fontSettings) override; + void updateFormats(const TextEditor::FontSettings &fontSettings); + void setTokenFormat(const Token &token, TextEditor::TextStyle style); + void setTokenFormatWithSpaces(const QString &text, const Token &token, + TextEditor::TextStyle style); + QTextCharFormat m_toplevelDeclFormat; +}; + +} // Internal +} // Haskell diff --git a/src/plugins/haskell/haskellmanager.cpp b/src/plugins/haskell/haskellmanager.cpp new file mode 100644 index 00000000000..dd6a3712b95 --- /dev/null +++ b/src/plugins/haskell/haskellmanager.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskellmanager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +static const char kStackExecutableKey[] = "Haskell/StackExecutable"; + +using namespace Utils; + +namespace Haskell { +namespace Internal { + +class HaskellManagerPrivate +{ +public: + FilePath stackExecutable; +}; + +Q_GLOBAL_STATIC(HaskellManagerPrivate, m_d) +Q_GLOBAL_STATIC(HaskellManager, m_instance) + +HaskellManager *HaskellManager::instance() +{ + return m_instance; +} + +FilePath HaskellManager::findProjectDirectory(const FilePath &filePath) +{ + if (filePath.isEmpty()) + return {}; + + QDir directory(filePath.toFileInfo().isDir() ? filePath.toString() + : filePath.parentDir().toString()); + directory.setNameFilters({"stack.yaml", "*.cabal"}); + directory.setFilter(QDir::Files | QDir::Readable); + do { + if (!directory.entryList().isEmpty()) + return FilePath::fromString(directory.path()); + } while (!directory.isRoot() && directory.cdUp()); + return {}; +} + +FilePath defaultStackExecutable() +{ + // stack from brew or the installer script from https://docs.haskellstack.org + // install to /usr/local/bin. + if (HostOsInfo::isAnyUnixHost()) + return FilePath::fromString("/usr/local/bin/stack"); + return FilePath::fromString("stack"); +} + +FilePath HaskellManager::stackExecutable() +{ + return m_d->stackExecutable; +} + +void HaskellManager::setStackExecutable(const FilePath &filePath) +{ + if (filePath == m_d->stackExecutable) + return; + m_d->stackExecutable = filePath; + emit m_instance->stackExecutableChanged(m_d->stackExecutable); +} + +void HaskellManager::openGhci(const FilePath &haskellFile) +{ + const QList mimeTypes = mimeTypesForFileName(haskellFile.toString()); + const bool isHaskell = Utils::anyOf(mimeTypes, [](const MimeType &mt) { + return mt.inherits("text/x-haskell") || mt.inherits("text/x-literate-haskell"); + }); + const auto args = QStringList{"ghci"} + + (isHaskell ? QStringList{haskellFile.fileName()} : QStringList()); + auto p = new QtcProcess(m_instance); + p->setTerminalMode(TerminalMode::On); + p->setCommand({stackExecutable(), args}); + p->setWorkingDirectory(haskellFile.absolutePath()); + connect(p, &QtcProcess::done, p, [p] { + if (p->result() != ProcessResult::FinishedWithSuccess) { + Core::MessageManager::writeDisrupting( + tr("Failed to run GHCi: \"%1\".").arg(p->errorString())); + } + p->deleteLater(); + }); + p->start(); +} + +void HaskellManager::readSettings(QSettings *settings) +{ + m_d->stackExecutable = FilePath::fromString( + settings->value(kStackExecutableKey, + defaultStackExecutable().toString()).toString()); + emit m_instance->stackExecutableChanged(m_d->stackExecutable); +} + +void HaskellManager::writeSettings(QSettings *settings) +{ + if (m_d->stackExecutable == defaultStackExecutable()) + settings->remove(kStackExecutableKey); + else + settings->setValue(kStackExecutableKey, m_d->stackExecutable.toString()); +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellmanager.h b/src/plugins/haskell/haskellmanager.h new file mode 100644 index 00000000000..361b8f8e966 --- /dev/null +++ b/src/plugins/haskell/haskellmanager.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace Haskell { +namespace Internal { + +class HaskellManager : public QObject +{ + Q_OBJECT + +public: + static HaskellManager *instance(); + + static Utils::FilePath findProjectDirectory(const Utils::FilePath &filePath); + static Utils::FilePath stackExecutable(); + static void setStackExecutable(const Utils::FilePath &filePath); + static void openGhci(const Utils::FilePath &haskellFile); + static void readSettings(QSettings *settings); + static void writeSettings(QSettings *settings); + +signals: + void stackExecutableChanged(const Utils::FilePath &filePath); +}; + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellplugin.cpp b/src/plugins/haskell/haskellplugin.cpp new file mode 100644 index 00000000000..c3ae400629c --- /dev/null +++ b/src/plugins/haskell/haskellplugin.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskellplugin.h" + +#include "haskellbuildconfiguration.h" +#include "haskellconstants.h" +#include "haskelleditorfactory.h" +#include "haskellmanager.h" +#include "haskellproject.h" +#include "haskellrunconfiguration.h" +#include "optionspage.h" +#include "stackbuildstep.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace Haskell { +namespace Internal { + +class HaskellPluginPrivate +{ +public: + HaskellEditorFactory editorFactory; + OptionsPage optionsPage; + HaskellBuildConfigurationFactory buildConfigFactory; + StackBuildStepFactory stackBuildStepFactory; + HaskellRunConfigurationFactory runConfigFactory; + ProjectExplorer::SimpleTargetRunnerFactory runWorkerFactory{{Constants::C_HASKELL_RUNCONFIG_ID}}; +}; + +HaskellPlugin::~HaskellPlugin() +{ + delete d; +} + +static void registerGhciAction() +{ + QAction *action = new QAction(HaskellManager::tr("Run GHCi"), HaskellManager::instance()); + Core::ActionManager::registerAction(action, Constants::A_RUN_GHCI); + QObject::connect(action, &QAction::triggered, HaskellManager::instance(), [] { + if (Core::IDocument *doc = Core::EditorManager::currentDocument()) + HaskellManager::openGhci(doc->filePath()); + }); +} + +bool HaskellPlugin::initialize(const QStringList &arguments, QString *errorString) +{ + Q_UNUSED(arguments) + Q_UNUSED(errorString) + + d = new HaskellPluginPrivate; + + ProjectExplorer::ProjectManager::registerProjectType( + Constants::C_HASKELL_PROJECT_MIMETYPE); + TextEditor::SnippetProvider::registerGroup(Constants::C_HASKELLSNIPPETSGROUP_ID, + tr("Haskell", "SnippetProvider")); + + connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, this, [] { + HaskellManager::writeSettings(Core::ICore::settings()); + }); + + registerGhciAction(); + + HaskellManager::readSettings(Core::ICore::settings()); + + ProjectExplorer::JsonWizardFactory::addWizardPath(":/haskell/share/wizards/"); + return true; +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellplugin.h b/src/plugins/haskell/haskellplugin.h new file mode 100644 index 00000000000..163f40b959b --- /dev/null +++ b/src/plugins/haskell/haskellplugin.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "haskell_global.h" + +#include + +namespace Haskell { +namespace Internal { + +class HaskellPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Haskell.json") + +public: + HaskellPlugin() = default; + ~HaskellPlugin() final; + +private: + bool initialize(const QStringList &arguments, QString *errorString) final; + void extensionsInitialized() final {} + + class HaskellPluginPrivate *d = nullptr; +}; + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellproject.cpp b/src/plugins/haskell/haskellproject.cpp new file mode 100644 index 00000000000..248f898aeed --- /dev/null +++ b/src/plugins/haskell/haskellproject.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskellproject.h" + +#include "haskellconstants.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace Haskell { +namespace Internal { + +static QVector parseExecutableNames(const FilePath &projectFilePath) +{ + static const QString EXECUTABLE = "executable"; + static const int EXECUTABLE_LEN = EXECUTABLE.length(); + QVector result; + QFile file(projectFilePath.toString()); + if (file.open(QFile::ReadOnly)) { + QTextStream stream(&file); + while (!stream.atEnd()) { + const QString line = stream.readLine().trimmed(); + if (line.length() > EXECUTABLE_LEN && line.startsWith(EXECUTABLE) + && line.at(EXECUTABLE_LEN).isSpace()) + result.append(line.mid(EXECUTABLE_LEN + 1).trimmed()); + } + } + return result; +} + +HaskellProject::HaskellProject(const Utils::FilePath &fileName) + : Project(Constants::C_HASKELL_PROJECT_MIMETYPE, fileName) +{ + setId(Constants::C_HASKELL_PROJECT_ID); + setDisplayName(fileName.toFileInfo().completeBaseName()); + setBuildSystemCreator([](Target *t) { return new HaskellBuildSystem(t); }); +} + +bool HaskellProject::isHaskellProject(Project *project) +{ + return project && project->id() == Constants::C_HASKELL_PROJECT_ID; +} + +HaskellBuildSystem::HaskellBuildSystem(Target *t) + : BuildSystem(t) +{ + connect(&m_scanner, &TreeScanner::finished, this, [this] { + auto root = std::make_unique(projectDirectory()); + root->setDisplayName(target()->project()->displayName()); + std::vector> nodePtrs + = Utils::transform(m_scanner.release().allFiles, [](FileNode *fn) { + return std::unique_ptr(fn); + }); + root->addNestedNodes(std::move(nodePtrs)); + setRootProjectNode(std::move(root)); + + updateApplicationTargets(); + + m_parseGuard.markAsSuccess(); + m_parseGuard = {}; + + emitBuildSystemUpdated(); + }); + + connect(target()->project(), + &Project::projectFileIsDirty, + this, + &BuildSystem::requestDelayedParse); + + requestDelayedParse(); +} + +void HaskellBuildSystem::triggerParsing() +{ + m_parseGuard = guardParsingRun(); + m_scanner.asyncScanForFiles(target()->project()->projectDirectory()); +} + +void HaskellBuildSystem::updateApplicationTargets() +{ + const QVector executables = parseExecutableNames(projectFilePath()); + const Utils::FilePath projFilePath = projectFilePath(); + const QList appTargets + = Utils::transform(executables, [projFilePath](const QString &executable) { + BuildTargetInfo bti; + bti.displayName = executable; + bti.buildKey = executable; + bti.targetFilePath = FilePath::fromString(executable); + bti.projectFilePath = projFilePath; + bti.isQtcRunnable = true; + return bti; + }); + setApplicationTargets(appTargets); + target()->updateDefaultRunConfigurations(); +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellproject.h b/src/plugins/haskell/haskellproject.h new file mode 100644 index 00000000000..0ee05d90333 --- /dev/null +++ b/src/plugins/haskell/haskellproject.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +namespace Haskell { +namespace Internal { + +class HaskellProject : public ProjectExplorer::Project +{ + Q_OBJECT + +public: + explicit HaskellProject(const Utils::FilePath &fileName); + + static bool isHaskellProject(Project *project); +}; + +class HaskellBuildSystem : public ProjectExplorer::BuildSystem +{ + Q_OBJECT + +public: + HaskellBuildSystem(ProjectExplorer::Target *t); + + void triggerParsing() override; + QString name() const final { return QLatin1String("haskell"); } + +private: + void updateApplicationTargets(); + void refresh(); + +private: + ParseGuard m_parseGuard; + ProjectExplorer::TreeScanner m_scanner; +}; + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellrunconfiguration.cpp b/src/plugins/haskell/haskellrunconfiguration.cpp new file mode 100644 index 00000000000..74574710f6f --- /dev/null +++ b/src/plugins/haskell/haskellrunconfiguration.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskellrunconfiguration.h" + +#include "haskellconstants.h" +#include "haskellmanager.h" +#include "haskellproject.h" + +#include +#include +#include +#include +#include + +using namespace ProjectExplorer; + +namespace Haskell { +namespace Internal { + +HaskellRunConfigurationFactory::HaskellRunConfigurationFactory() +{ + registerRunConfiguration(Constants::C_HASKELL_RUNCONFIG_ID); + addSupportedProjectType(Constants::C_HASKELL_PROJECT_ID); + addSupportedTargetDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE); +} + +HaskellExecutableAspect::HaskellExecutableAspect() +{ + setSettingsKey("Haskell.Executable"); + setLabelText(tr("Executable")); +} + +HaskellRunConfiguration::HaskellRunConfiguration(Target *target, Utils::Id id) + : RunConfiguration(target, id) +{ + auto envAspect = addAspect(target); + + addAspect(); + addAspect(macroExpander()); + + auto workingDirAspect = addAspect(macroExpander(), envAspect); + workingDirAspect->setDefaultWorkingDirectory(target->project()->projectDirectory()); + workingDirAspect->setVisible(false); + + addAspect(); + + setUpdater([this] { aspect()->setValue(buildTargetInfo().buildKey); }); + connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); + update(); +} + +Runnable HaskellRunConfiguration::runnable() const +{ + const Utils::FilePath projectDirectory = target()->project()->projectDirectory(); + Runnable r; + QStringList args; + if (BuildConfiguration *buildConfiguration = target()->activeBuildConfiguration()) { + args << "--work-dir" + << QDir(projectDirectory.toString()).relativeFilePath( + buildConfiguration->buildDirectory().toString()); + } + args << "exec" << aspect()->value(); + const QString arguments = aspect()->arguments(); + if (!arguments.isEmpty()) + args << "--" << arguments; + + r.workingDirectory = projectDirectory; + r.environment = aspect()->environment(); + r.command = {r.environment.searchInPath(HaskellManager::stackExecutable().toString()), args}; + return r; +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskellrunconfiguration.h b/src/plugins/haskell/haskellrunconfiguration.h new file mode 100644 index 00000000000..27e4a83f9ad --- /dev/null +++ b/src/plugins/haskell/haskellrunconfiguration.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +namespace Haskell { +namespace Internal { + +class HaskellRunConfiguration : public ProjectExplorer::RunConfiguration +{ + Q_OBJECT + +public: + HaskellRunConfiguration(ProjectExplorer::Target *target, Utils::Id id); + +private: + ProjectExplorer::Runnable runnable() const final; +}; + +class HaskellRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory +{ +public: + HaskellRunConfigurationFactory(); +}; + +class HaskellExecutableAspect : public Utils::StringAspect +{ + Q_OBJECT + +public: + HaskellExecutableAspect(); +}; + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/haskelltokenizer.cpp b/src/plugins/haskell/haskelltokenizer.cpp new file mode 100644 index 00000000000..6f2556d2475 --- /dev/null +++ b/src/plugins/haskell/haskelltokenizer.cpp @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskelltokenizer.h" + +#include + +#include +#include + +Q_GLOBAL_STATIC_WITH_ARGS(QSet, RESERVED_OP, ({ + "..", + ":", + "::", + "=", + "\\", + "|", + "<-", + "->", + "@", + "~", + "=>", + + // Arrows GHC extension + "-<", + "-<<", + ">-", + ">>-", + "(|", + "|)" +})); + +Q_GLOBAL_STATIC_WITH_ARGS(QSet, RESERVED_ID, ({ + "case", + "class", + "data", + "default", + "deriving", + "do", + "else", + "foreign", + "if", + "import", + "in", + "infix", + "infixl", + "infixr", + "instance", + "let", + "module", + "newtype", + "of", + "then", + "type", + "where", + "_", + + // from GHC extensions + "family", + "forall", + "mdo", + "proc", + "rec" +})); + +Q_GLOBAL_STATIC_WITH_ARGS(QSet, SPECIAL, ({ + '(', + ')', + ',', + ';', + '[', + ']', + '`', + '{', + '}', +})); + +Q_GLOBAL_STATIC_WITH_ARGS(QSet, CHAR_ESCAPES, + ({'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '&'})); + +Q_GLOBAL_STATIC_WITH_ARGS(QVector, ASCII_ESCAPES, ({ + "NUL", + "SOH", // must be before "SO" to match + "STX", + "ETX", + "EOT", + "ENQ", + "ACK", + "BEL", + "BS", + "HT", + "LF", + "VT", + "FF", + "CR", + "SO", + "SI", + "DLE", + "DC1", + "DC2", + "DC3", + "DC4", + "NAK", + "SYN", + "ETB", + "CAN", + "EM", + "SUB", + "ESC", + "FS", + "GS", + "RS", + "US", + "SP", + "DEL" +})); + +namespace Haskell { +namespace Internal { + +Token token(TokenType type, std::shared_ptr line, int start, int end) +{ + return {type, start, end - start, QStringView(*line).mid(start, end - start), line}; +} + +Tokens::Tokens(std::shared_ptr source) + : source(source) +{ +} + +Token Tokens::tokenAtColumn(int col) const +{ + auto it = std::upper_bound(begin(), end(), col, [](int c, const Token &i) { + return c < i.startCol; + }); + if (it == begin()) + return Token(); + --it; + if (it->startCol + it->length > col) + return *it; + return Token(); +} + +static int grab(const QString &line, int begin, + const std::function &test) +{ + const int length = line.length(); + int current = begin; + while (current < length && test(line.at(current))) + ++current; + return current - begin; +}; + +static bool isIdentifierChar(const QChar &c) +{ + return c.isLetterOrNumber() || c == '\'' || c == '_'; +} + +static bool isVariableIdentifierStart(const QChar &c) +{ + return c == '_' || c.isLower(); +} + +static bool isAscSymbol(const QChar &c) +{ + return c == '!' + || c == '#' + || c == '$' + || c == '%' + || c == '&' + || c == '*' + || c == '+' + || c == '.' + || c == '/' + || c == '<' + || c == '=' + || c == '>' + || c == '?' + || c == '@' + || c == '\\' + || c == '^' + || c == '|' + || c == '-' + || c == '~' + || c == ':'; +} + +static bool isSymbol(const QChar &c) +{ + return isAscSymbol(c) + || ((c.isSymbol() || c.isPunct()) && c != '_' && c != '"' && c != '\'' + && !SPECIAL->contains(c)); +} + +static bool isDigit(const QChar &c) +{ + return c.isDigit(); +} + +static bool isOctit(const QChar &c) +{ + return c >= '0' && c <= '7'; +} + +static bool isHexit(const QChar &c) +{ + return c.isDigit() + || (c >= 'A' && c <= 'F') + || (c >= 'a' && c <= 'f'); +} + +static bool isCntrl(const QChar &c) +{ + return (c >= 'A' && c <= 'Z') + || c == '@' + || c == '[' + || c == '\\' + || c == ']' + || c == '^' + || c == '_'; +} + +static QVector getSpace(std::shared_ptr line, int start) +{ + const auto lineEnd = line->cend(); + const auto tokenStart = line->cbegin() + start; + auto current = tokenStart; + while (current != lineEnd && (*current).isSpace()) + ++current; + const int length = int(std::distance(tokenStart, current)); + if (current > tokenStart) + return {{TokenType::Whitespace, start, length, QStringView(*line).mid(start, length), line}}; + return {}; +} + +static QVector getNumber(std::shared_ptr line, int start) +{ + const QChar &startC = line->at(start); + if (!startC.isDigit()) + return {}; + const int length = line->length(); + int current = start + 1; + TokenType type = TokenType::Integer; + if (current < length) { + if (startC == '0') { + // check for octal or hexadecimal + const QChar &secondC = line->at(current); + if (secondC == 'o' || secondC == 'O') { + const int numLen = grab(*line, current + 1, isOctit); + if (numLen > 0) + return {token(TokenType::Integer, line, start, current + numLen + 1)}; + } else if (secondC == 'x' || secondC == 'X') { + const int numLen = grab(*line, current + 1, isHexit); + if (numLen > 0) + return {token(TokenType::Integer, line, start, current + numLen + 1)}; + } + } + // starts with decimal + const int numLen = grab(*line, start, isDigit); + current = start + numLen; + // check for floating point + if (current < length && line->at(current) == '.') { + const int numLen = grab(*line, current + 1, isDigit); + if (numLen > 0) { + current += numLen + 1; + type = TokenType::Float; + } + } + // check for exponent + if (current + 1 < length /*for at least 'e' and digit*/ + && (line->at(current) == 'e' || line->at(current) == 'E')) { + int expEnd = current + 1; + if (line->at(expEnd) == '+' || line->at(expEnd) == '-') + ++expEnd; + const int numLen = grab(*line, expEnd, isDigit); + if (numLen > 0) { + current = expEnd + numLen; + type = TokenType::Float; + } + } + } + return {token(type, line, start, current)}; +} + +static QVector getIdOrOpOrSingleLineComment(std::shared_ptr line, int start) +{ + const int length = line->length(); + if (start >= length) + return {}; + int current = start; + // check for {conid.}conid + int conidEnd = start; + bool canOnlyBeConstructor = false; + while (current < length && line->at(current).isUpper()) { + current += grab(*line, current, isIdentifierChar); + conidEnd = current; + // it is definitely a constructor id if it is not followed by a '.' + canOnlyBeConstructor = current >= length || line->at(current) != '.'; + // otherwise it might be a module id, and we skip the dot to check for qualified thing + if (!canOnlyBeConstructor) + ++current; + } + if (canOnlyBeConstructor) + return {token(TokenType::Constructor, line, start, conidEnd)}; + + // check for variable or reserved id + if (current < length && isVariableIdentifierStart(line->at(current))) { + const int varLen = grab(*line, current, isIdentifierChar); + // check for reserved id + if (RESERVED_ID->contains(line->mid(current, varLen))) { + QVector result; + // possibly add constructor + op '.' + if (conidEnd > start) { + result.append(token(TokenType::Constructor, line, start, conidEnd)); + result.append(token(TokenType::Operator, line, conidEnd, current)); + } + result.append(token(TokenType::Keyword, line, current, current + varLen)); + return result; + } + return {token(TokenType::Variable, line, start, current + varLen)}; + } + // check for operator + if (current < length && isSymbol(line->at(current))) { + const int opLen = grab(*line, current, isSymbol); + // check for reserved op + if (RESERVED_OP->contains(line->mid(current, opLen))) { + // because of the case of F... (constructor + op '...') etc + // we only add conid if we have one, handling the rest in next iteration + if (conidEnd > start) + return {token(TokenType::Constructor, line, start, conidEnd)}; + return {token(TokenType::Keyword, line, start, current + opLen)}; + } + // check for single line comment + if (opLen >= 2 && std::all_of(line->begin() + current, line->begin() + current + opLen, + [](const QChar c) { return c == '-'; })) { + QVector result; + // possibly add constructor + op '.' + if (conidEnd > start) { + result.append(token(TokenType::Constructor, line, start, conidEnd)); + result.append(token(TokenType::Operator, line, conidEnd, current)); + } + // rest is comment + result.append(token(TokenType::SingleLineComment, line, current, length)); + return result; + } + // check for (qualified?) operator constructor + if (line->at(current) == ':') + return {token(TokenType::OperatorConstructor, line, start, current + opLen)}; + return {token(TokenType::Operator, line, start, current + opLen)}; + } + // Foo.Blah. + if (conidEnd > start) + return {token(TokenType::Constructor, line, start, conidEnd)}; + return {}; +} + +static int getEscape(const QString &line, int start) +{ + if (CHAR_ESCAPES->contains(line.at(start))) + return 1; + + // decimal + if (line.at(start).isDigit()) + return grab(line, start + 1, isDigit) + 1; + // octal + if (line.at(start) == 'o') { + const int count = grab(line, start + 1, isOctit); + if (count < 1) // no octal number after 'o' + return 0; + return count + 1; + } + // hexadecimal + if (line.at(start) == 'x') { + const int count = grab(line, start + 1, isHexit); + if (count < 1) // no octal number after 'o' + return 0; + return count + 1; + } + // ascii cntrl + if (line.at(start) == '^') { + const int count = grab(line, start + 1, isCntrl); + if (count < 1) // no octal number after 'o' + return 0; + return count + 1; + } + const QStringView s = QStringView(line).mid(start); + for (const QString &esc : *ASCII_ESCAPES) { + if (s.startsWith(esc)) + return esc.length(); + } + return 0; +} + +static QVector getString(std::shared_ptr line, int start, bool *inStringGap/*in-out*/) +{ + // Haskell has the specialty of using \\ within strings for multiline strings + const int length = line->length(); + if (start >= length) + return {}; + QVector result; + int tokenStart = start; + int current = tokenStart; + bool inString = *inStringGap; + do { + const QChar c = line->at(current); + if (*inStringGap && !c.isSpace() && c != '\\') { + // invalid non-whitespace in string gap + // add previous string as token, this is at least a whitespace + result.append(token(TokenType::String, line, tokenStart, current)); + // then add wrong non-whitespace + tokenStart = current; + do { ++current; } while (current < length && !line->at(current).isSpace()); + result.append(token(TokenType::StringError, line, tokenStart, current)); + tokenStart = current; + } else if (c == '"') { + inString = !inString; + ++current; + } else if (inString) { + if (c == '\\') { + ++current; + if (*inStringGap) { + // ending string gap + *inStringGap = false; + } else if (current >= length || line->at(current).isSpace()) { + // starting string gap + *inStringGap = true; + current = std::min(current + 1, length); + } else { // there is at least one character after current + const int escapeLength = getEscape(*line, current); + if (escapeLength > 0) { + // valid escape + // add previous string as token without backslash, if necessary + if (tokenStart < current - 1/*backslash*/) + result.append(token(TokenType::String, line, tokenStart, current - 1)); + tokenStart = current - 1; // backslash + current += escapeLength; + result.append(token(TokenType::EscapeSequence, line, tokenStart, current)); + tokenStart = current; + } else { // invalid escape sequence + // add previous string as token, this is at least backslash + result.append(token(TokenType::String, line, tokenStart, current)); + result.append(token(TokenType::StringError, line, current, current + 1)); + ++current; + tokenStart = current; + } + } + } else { + ++current; + } + } + } while (current < length && inString); + if (current > tokenStart) + result.append(token(TokenType::String, line, tokenStart, current)); + if (inString && !*inStringGap) { // unterminated string + // mark last character of last token as Unknown as an error hint + if (!result.isEmpty()) { // should actually never be different + Token &lastRef = result.last(); + if (lastRef.length == 1) { + lastRef.type = TokenType::StringError; + } else { + --lastRef.length; + lastRef.text = QStringView(*line).mid(lastRef.startCol, lastRef.length); + result.append(token(TokenType::StringError, line, current - 1, current)); + } + } + } + return result; +} + +static QVector getMultiLineComment(std::shared_ptr line, int start, + int *commentLevel/*in_out*/) +{ + // Haskell multiline comments can be nested {- foo {- bar -} blah -} + const int length = line->length(); + int current = start; + do { + const QStringView test = QStringView(*line).mid(current, 2); + if (test == QLatin1String("{-")) { + ++(*commentLevel); + current += 2; + } else if (test == QLatin1String("-}") && *commentLevel > 0) { + --(*commentLevel); + current += 2; + } else if (*commentLevel > 0) { + ++current; + } + } while (current < length && *commentLevel > 0); + if (current > start) { + return {token(TokenType::MultiLineComment, line, start, current)}; + } + return {}; +} + +static QVector getChar(std::shared_ptr line, int start) +{ + if (line->at(start) != '\'') + return {}; + QVector result; + const int length = line->length(); + int tokenStart = start; + int current = tokenStart + 1; + bool inChar = true; + int count = 0; + while (current < length && inChar) { + if (line->at(current) == '\'') { + inChar = false; + ++current; + } else if (count == 1) { + // we already have one character, so start Unknown token + if (current > tokenStart) + result.append(token(TokenType::Char, line, tokenStart, current)); + tokenStart = current; + ++count; + ++current; + } else if (count > 1) { + ++count; + ++current; + } else if (line->at(current) == '\\') { + if (current + 1 < length) { + ++current; + ++count; + const int escapeLength = getEscape(*line, current); + if (line->at(current) != '&' && escapeLength > 0) { // no & escape for chars + // valid escape + // add previous string as token without backslash, if necessary + if (tokenStart < current - 1/*backslash*/) + result.append(token(TokenType::Char, line, tokenStart, current - 1)); + tokenStart = current - 1; // backslash + current += escapeLength; + result.append(token(TokenType::EscapeSequence, line, tokenStart, current)); + tokenStart = current; + } else { // invalid escape sequence + // add previous string as token, this is at least backslash + result.append(token(TokenType::Char, line, tokenStart, current)); + result.append(token(TokenType::CharError, line, current, current + 1)); + ++current; + tokenStart = current; + } + } else { + ++current; + } + } else { + ++count; + ++current; + } + } + if (count > 1 && inChar) { + // too long and unterminated, just add Unknown token till end + result.append(token(TokenType::CharError, line, tokenStart, current)); + } else if (count > 1) { + // too long but terminated, add Unknown up to ending quote, then quote + result.append(token(TokenType::CharError, line, tokenStart, current - 1)); + result.append(token(TokenType::Char, line, current - 1, current)); + } else if (inChar || count < 1) { + // unterminated, or no character inside, mark last character as error + if (current > tokenStart + 1) + result.append(token(TokenType::Char, line, tokenStart, current - 1)); + result.append(token(TokenType::CharError, line, current - 1, current)); + } else { + result.append(token(TokenType::Char, line, tokenStart, current)); + } + return result; +} + +static QVector getSpecial(std::shared_ptr line, int start) +{ + if (SPECIAL->contains(line->at(start))) + return {{TokenType::Special, start, 1, QStringView(*line).mid(start, 1), line}}; + return {}; +} + +Tokens HaskellTokenizer::tokenize(const QString &line, int startState) +{ + Tokens result(std::make_shared(line)); + const int length = result.source->length(); + bool inStringGap = startState == int(Tokens::State::StringGap); + int multiLineCommentLevel = std::max(startState - int(Tokens::State::MultiLineCommentGuard), 0); + int currentStart = 0; + QVector tokens; + while (currentStart < length) { + if (multiLineCommentLevel <= 0 && + !(tokens = getString(result.source, currentStart, &inStringGap)).isEmpty()) { + result.append(tokens); + } else if (!(tokens = getMultiLineComment(result.source, currentStart, + &multiLineCommentLevel)).isEmpty()) { + result.append(tokens); + } else if (!(tokens = getChar(result.source, currentStart)).isEmpty()) { + result.append(tokens); + } else if (!(tokens = getSpace(result.source, currentStart)).isEmpty()) { + result.append(tokens); + } else if (!(tokens = getNumber(result.source, currentStart)).isEmpty()) { + result.append(tokens); + } else if (!(tokens = getIdOrOpOrSingleLineComment(result.source, currentStart)).isEmpty()) { + result.append(tokens); + } else if (!(tokens = getSpecial(result.source, currentStart)).isEmpty()) { + result.append(tokens); + } else { + tokens = {{TokenType::Unknown, + currentStart, + 1, + QStringView(*result.source).mid(currentStart, 1), + result.source}}; + result.append(tokens); + } + currentStart += std::accumulate(tokens.cbegin(), tokens.cend(), 0, + [](int s, const Token &t) { return s + t.length; }); + } + if (inStringGap) + result.state = int(Tokens::State::StringGap); + else if (multiLineCommentLevel > 0) + result.state = int(Tokens::State::MultiLineCommentGuard) + multiLineCommentLevel; + return result; +} + +bool Token::isValid() const +{ + return type != TokenType::Unknown; +} + +} // Internal +} // Haskell diff --git a/src/plugins/haskell/haskelltokenizer.h b/src/plugins/haskell/haskelltokenizer.h new file mode 100644 index 00000000000..63d82d960b0 --- /dev/null +++ b/src/plugins/haskell/haskelltokenizer.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include + +namespace Haskell { +namespace Internal { + +enum class TokenType { + Variable, + Constructor, + Operator, + OperatorConstructor, + Whitespace, + String, + StringError, + Char, + CharError, + EscapeSequence, + Integer, + Float, + Keyword, + Special, + SingleLineComment, + MultiLineComment, + Unknown +}; + +class Token { +public: + bool isValid() const; + + TokenType type = TokenType::Unknown; + int startCol = -1; + int length = -1; + QStringView text; + + std::shared_ptr source; // keep the string ref alive +}; + +class Tokens : public QVector +{ +public: + enum class State { + None = -1, + StringGap = 0, // gap == two backslashes enclosing only whitespace + MultiLineCommentGuard // nothing may follow that + }; + + Tokens(std::shared_ptr source); + + Token tokenAtColumn(int col) const; + + std::shared_ptr source; + int state = int(State::None); +}; + +class HaskellTokenizer +{ +public: + static Tokens tokenize(const QString &line, int startState); +}; + +} // Internal +} // Haskell diff --git a/src/plugins/haskell/images/category_haskell.png b/src/plugins/haskell/images/category_haskell.png new file mode 100644 index 00000000000..947f948bd91 Binary files /dev/null and b/src/plugins/haskell/images/category_haskell.png differ diff --git a/src/plugins/haskell/optionspage.cpp b/src/plugins/haskell/optionspage.cpp new file mode 100644 index 00000000000..63d159443b0 --- /dev/null +++ b/src/plugins/haskell/optionspage.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "optionspage.h" + +#include "haskellconstants.h" +#include "haskellmanager.h" + +#include +#include +#include +#include +#include + +namespace Haskell { +namespace Internal { + +OptionsPage::OptionsPage() +{ + setId(Constants::OPTIONS_GENERAL); + setDisplayName(tr("General")); + setCategory("J.Z.Haskell"); + setDisplayCategory(tr("Haskell")); + setCategoryIcon(Utils::Icon(":/haskell/images/category_haskell.png")); +} + +QWidget *OptionsPage::widget() +{ + using namespace Utils; + if (!m_widget) { + m_widget = new QWidget; + auto topLayout = new QVBoxLayout; + m_widget->setLayout(topLayout); + auto generalBox = new QGroupBox(tr("General")); + topLayout->addWidget(generalBox); + topLayout->addStretch(10); + auto boxLayout = new QHBoxLayout; + generalBox->setLayout(boxLayout); + boxLayout->addWidget(new QLabel(tr("Stack executable:"))); + m_stackPath = new PathChooser(); + m_stackPath->setExpectedKind(PathChooser::ExistingCommand); + m_stackPath->setPromptDialogTitle(tr("Choose Stack Executable")); + m_stackPath->setFilePath(HaskellManager::stackExecutable()); + m_stackPath->setCommandVersionArguments({"--version"}); + boxLayout->addWidget(m_stackPath); + } + return m_widget; +} + +void OptionsPage::apply() +{ + if (!m_widget) + return; + HaskellManager::setStackExecutable(m_stackPath->rawFilePath()); +} + +void OptionsPage::finish() +{ +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/optionspage.h b/src/plugins/haskell/optionspage.h new file mode 100644 index 00000000000..c1663c7a47e --- /dev/null +++ b/src/plugins/haskell/optionspage.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include + +namespace Haskell { +namespace Internal { + +class OptionsPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + OptionsPage(); + + QWidget *widget() override; + void apply() override; + void finish() override; + +private: + QPointer m_widget; + QPointer m_stackPath; +}; + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/share/wizards/module/file.hs b/src/plugins/haskell/share/wizards/module/file.hs new file mode 100644 index 00000000000..e3f85b3f775 --- /dev/null +++ b/src/plugins/haskell/share/wizards/module/file.hs @@ -0,0 +1 @@ +module %{BaseName} where diff --git a/src/plugins/haskell/share/wizards/module/wizard.json b/src/plugins/haskell/share/wizards/module/wizard.json new file mode 100644 index 00000000000..37f6ebf975b --- /dev/null +++ b/src/plugins/haskell/share/wizards/module/wizard.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "supportedProjectTypes": [ ], + "id": "C.Module", + "category": "S.Haskell", + "trDescription": "Creates a Haskell module.", + "trDisplayName": "Module", + "trDisplayCategory": "Haskell", + "iconText": "hs", + "enabled": "%{JS: value('Plugins').indexOf('Haskell') >= 0}", + + "options": [ + { "key": "FileName", "value": "%{JS: Util.fileName(value('TargetPath'), 'hs')}" }, + { "key": "BaseName", "value": "%{JS: Util.baseName(value('FileName'))}" } + ], + + "pages" : + [ + { + "trDisplayName": "Location", + "trShortTitle": "Location", + "typeId": "File" + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators" : + [ + { + "typeId": "File", + "data": + { + "source": "file.hs", + "target": "%{FileName}", + "openInEditor": true + } + } + ] +} diff --git a/src/plugins/haskell/stackbuildstep.cpp b/src/plugins/haskell/stackbuildstep.cpp new file mode 100644 index 00000000000..52ed64f2834 --- /dev/null +++ b/src/plugins/haskell/stackbuildstep.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "stackbuildstep.h" + +#include "haskellconstants.h" +#include "haskellmanager.h" + +#include +#include +#include +#include + +using namespace ProjectExplorer; + +namespace Haskell { +namespace Internal { + +StackBuildStep::StackBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id) + : AbstractProcessStep(bsl, id) +{ + setDefaultDisplayName(trDisplayName()); +} + +QWidget *StackBuildStep::createConfigWidget() +{ + return new QWidget; +} + +QString StackBuildStep::trDisplayName() +{ + return tr("Stack Build"); +} + +bool StackBuildStep::init() +{ + if (AbstractProcessStep::init()) { + const auto projectDir = QDir(project()->projectDirectory().toString()); + processParameters()->setCommandLine( + {HaskellManager::stackExecutable(), + {"build", "--work-dir", projectDir.relativeFilePath(buildDirectory().toString())}}); + processParameters()->setEnvironment(buildEnvironment()); + } + return true; +} + +StackBuildStepFactory::StackBuildStepFactory() +{ + registerStep(Constants::C_STACK_BUILD_STEP_ID); + setDisplayName(StackBuildStep::StackBuildStep::trDisplayName()); + setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); +} + +} // namespace Internal +} // namespace Haskell diff --git a/src/plugins/haskell/stackbuildstep.h b/src/plugins/haskell/stackbuildstep.h new file mode 100644 index 00000000000..d74d155b708 --- /dev/null +++ b/src/plugins/haskell/stackbuildstep.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Haskell { +namespace Internal { + +class StackBuildStep : public ProjectExplorer::AbstractProcessStep +{ + Q_OBJECT + +public: + StackBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); + + QWidget *createConfigWidget() override; + + static QString trDisplayName(); + +protected: + bool init() override; +}; + +class StackBuildStepFactory : public ProjectExplorer::BuildStepFactory +{ +public: + StackBuildStepFactory(); +}; + +} // namespace Internal +} // namespace Haskell diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 36c9c2c59cf..c71cbafdb2d 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(environment) add_subdirectory(extensionsystem) add_subdirectory(externaltool) add_subdirectory(filesearch) +add_subdirectory(haskell) add_subdirectory(json) add_subdirectory(languageserverprotocol) add_subdirectory(mapreduce) diff --git a/tests/auto/haskell/CMakeLists.txt b/tests/auto/haskell/CMakeLists.txt new file mode 100644 index 00000000000..cdcccae5edb --- /dev/null +++ b/tests/auto/haskell/CMakeLists.txt @@ -0,0 +1,8 @@ +add_qtc_test(tst_tokenizer + DEPENDS Qt::Core Qt::Test + INCLUDES ../../../src/plugins/haskell + SOURCES + tst_tokenizer.cpp + ../../../src/plugins/haskell/haskelltokenizer.cpp + ../../../src/plugins/haskell/haskelltokenizer.h +) diff --git a/tests/auto/haskell/tst_tokenizer.cpp b/tests/auto/haskell/tst_tokenizer.cpp new file mode 100644 index 00000000000..ffa34b205b8 --- /dev/null +++ b/tests/auto/haskell/tst_tokenizer.cpp @@ -0,0 +1,730 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include +#include + +using namespace Haskell::Internal; + +const QSet escapes{'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '&'}; + +struct TokenInfo +{ + TokenType type; + int column; + QString text; +}; + +Q_DECLARE_METATYPE(TokenInfo) + +bool operator==(const TokenInfo &info, const Token &token) +{ + return info.type == token.type + && info.column == token.startCol + && info.text.length() == token.length + && info.text == token.text.toString(); +} + +bool operator==(const Token &token, const TokenInfo &info) +{ + return info == token; +} + +class tst_Tokenizer : public QObject +{ + Q_OBJECT + +private slots: + void singleLineComment_data(); + void singleLineComment(); + + void multiLineComment_data(); + void multiLineComment(); + + void string_data(); + void string(); + + void character_data(); + void character(); + + void number_data(); + void number(); + + void keyword_data(); + void keyword(); + + void variable_data(); + void variable(); + + void constructor_data(); + void constructor(); + + void op_data(); + void op(); + +private: + void setupData(); + void addRow(const char *name, + const QString &input, + const QList &tokens, + Tokens::State startState = Tokens::State::None, + Tokens::State endState = Tokens::State::None); + void checkData(); +}; + +void tst_Tokenizer::setupData() +{ + QTest::addColumn("input"); + QTest::addColumn>("output"); + QTest::addColumn("startState"); + QTest::addColumn("endState"); +} + +void tst_Tokenizer::addRow(const char *name, + const QString &input, + const QList &tokens, + Tokens::State startState, + Tokens::State endState) +{ + QTest::newRow(name) << input << tokens << int(startState) << int(endState); +} + +void tst_Tokenizer::checkData() +{ + QFETCH(QString, input); + QFETCH(QList, output); + QFETCH(int, startState); + QFETCH(int, endState); + const Tokens tokens = HaskellTokenizer::tokenize(input, startState); + QCOMPARE(tokens.length(), output.length()); + QCOMPARE(tokens.state, endState); + for (int i = 0; i < tokens.length(); ++i) { + const Token t = tokens.at(i); + const TokenInfo ti = output.at(i); + QVERIFY2(t == ti, QString("Token at index %1 does not match, {%2, %3, \"%4\"} != {%5, %6, \"%7\"}") + .arg(i) + .arg(int(t.type)).arg(t.startCol).arg(t.text.toString()) + .arg(int(ti.type)).arg(ti.column).arg(ti.text) + .toUtf8().constData()); + } +} + +void tst_Tokenizer::singleLineComment_data() +{ + setupData(); + + addRow("simple", " -- foo", { + {TokenType::Whitespace, 0, " "}, + {TokenType::SingleLineComment, 1, "-- foo"} + }); + addRow("dash, id", "--foo", { + {TokenType::SingleLineComment, 0, "--foo"} + }); + addRow("dash, space, op", "-- |foo", { + {TokenType::SingleLineComment, 0, "-- |foo"} + }); + addRow("multi-dash, space", "---- foo", { + {TokenType::SingleLineComment, 0, "---- foo"} + }); + addRow("dash, op", "--| foo", { + {TokenType::Operator, 0, "--|"}, + {TokenType::Whitespace, 3, " "}, + {TokenType::Variable, 4, "foo"} + }); + addRow("dash, special", "--(foo", { + {TokenType::SingleLineComment, 0, "--(foo"} + }); + addRow("not a qualified varsym", "F.-- foo", { + {TokenType::Constructor, 0, "F"}, + {TokenType::Operator, 1, "."}, + {TokenType::SingleLineComment, 2, "-- foo"} + }); +} + +void tst_Tokenizer::singleLineComment() +{ + checkData(); +} + +void tst_Tokenizer::multiLineComment_data() +{ + setupData(); + + addRow("trailing dashes", "{---foo -}", { + {TokenType::MultiLineComment, 0, "{---foo -}"} + }); + addRow("multiline", "{- foo", { + {TokenType::MultiLineComment, 0, "{- foo"} + }, + Tokens::State::None, + Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1)); + addRow("multiline2", "bar -}", { + {TokenType::MultiLineComment, 0, "bar -}"} + }, + Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1), + Tokens::State::None); + addRow("nested", "{- fo{-o", { + {TokenType::MultiLineComment, 0, "{- fo{-o"} + }, + Tokens::State::None, + Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 2)); + addRow("nested2", "bar -}", { + {TokenType::MultiLineComment, 0, "bar -}"} + }, + Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 2), + Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1)); + addRow("nested3", "bar -}", { + {TokenType::MultiLineComment, 0, "bar -}"} + }, + Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1), + Tokens::State::None); +} + +void tst_Tokenizer::multiLineComment() +{ + checkData(); +} + +void tst_Tokenizer::string_data() +{ + setupData(); + + addRow("simple", "\"foo\"", { + {TokenType::String, 0, "\"foo\""} + }); + + addRow("unterminated", "\"", { + {TokenType::StringError, 0, "\""} + }); + addRow("unterminated2", "\"foo", { + {TokenType::String, 0, "\"fo"}, + {TokenType::StringError, 3, "o"} + }); + addRow("unterminated with escape", "\"\\\\", { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, "\\"}, + {TokenType::StringError, 2, "\\"} + }); + + // gaps + addRow("gap", "\" \\ \\\"", { + {TokenType::String, 0, "\" \\ \\\""} + }); + addRow("gap over endline", "\"foo\\", { + {TokenType::String, 0, "\"foo\\"} + }, + Tokens::State::None, Tokens::State::StringGap); + addRow("gap over endline2", "\\foo\"", { + {TokenType::String, 0, "\\foo\""} + }, + Tokens::State::StringGap, Tokens::State::None); + addRow("gap error", "\"\\ ab \\\"", { + {TokenType::String, 0, "\"\\ "}, + {TokenType::StringError, 3, "ab"}, + {TokenType::String, 5, " \\\""} + }); + addRow("gap error with quote", "\"\\ \"", { + {TokenType::String, 0, "\"\\ "}, + {TokenType::StringError, 3, "\""} + }, + Tokens::State::None, Tokens::State::StringGap); + + // char escapes (including wrong ones) + for (char c = '!'; c <= '~'; ++c) { + // skip uppercase and '^', since these can be part of ascii escapes + // and 'o' and 'x' since they start octal and hex escapes + // and digits as part of decimal escape + if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '^' || c == 'o' || c == 'x') + continue; + const QChar qc(c); + const QByteArray name = QString("charesc '%1'").arg(qc).toUtf8(); + const QString input = QString("\"\\%1\"").arg(qc); + if (escapes.contains(c)) { + addRow(name.constData(), input, { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, QLatin1String("\\") + qc}, + {TokenType::String, 3, "\""} + }); + } else { + addRow(name.constData(), input, { + {TokenType::String, 0, "\"\\"}, + {TokenType::StringError, 2, qc}, + {TokenType::String, 3, "\""} + }); + } + } + + addRow("decimal escape", "\"\\1234a\"", { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, "\\1234"}, + {TokenType::String, 6, "a\""} + }); + + addRow("octal escape", "\"\\o0678a\"", { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, "\\o067"}, + {TokenType::String, 6, "8a\""} + }); + addRow("octal escape error", "\"\\o8a\"", { + {TokenType::String, 0, "\"\\"}, + {TokenType::StringError, 2, "o"}, + {TokenType::String, 3, "8a\""} + }); + + addRow("hexadecimal escape", "\"\\x0678Abg\"", { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, "\\x0678Ab"}, + {TokenType::String, 9, "g\""} + }); + addRow("hexadecimal escape error", "\"\\xg\"", { + {TokenType::String, 0, "\"\\"}, + {TokenType::StringError, 2, "x"}, + {TokenType::String, 3, "g\""} + }); + + // ascii cntrl escapes (including wrong ones) + for (char c = '!'; c <= '~'; ++c) { + if (c == '"') // is special because it also ends the string + continue; + const QChar qc(c); + const QByteArray name = QString("ascii cntrl '^%1'").arg(qc).toUtf8(); + const QString input = QString("\"\\^%1\"").arg(qc); + if ((qc >= 'A' && qc <= 'Z') || qc == '@' || qc == '[' || qc == '\\' || qc == ']' + || qc == '^' || qc == '_') { + addRow(name.constData(), input, { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, QLatin1String("\\^") + qc}, + {TokenType::String, 4, "\""} + }); + } else { + addRow(name.constData(), input, { + {TokenType::String, 0, "\"\\"}, + {TokenType::StringError, 2, "^"}, + {TokenType::String, 3, QString(qc) + "\""} + }); + } + } + + addRow("ascii escape SOH", "\"\\SOHN\"", { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, "\\SOH"}, + {TokenType::String, 5, "N\""} + }); + addRow("ascii escape SO", "\"\\SON\"", { + {TokenType::String, 0, "\""}, + {TokenType::EscapeSequence, 1, "\\SO"}, + {TokenType::String, 4, "N\""} + }); + addRow("ascii escape error", "\"\\TON\"", { + {TokenType::String, 0, "\"\\"}, + {TokenType::StringError, 2, "T"}, + {TokenType::String, 3, "ON\""} + }); + addRow("ascii escape error 2", "\"\\STO\"", { + {TokenType::String, 0, "\"\\"}, + {TokenType::StringError, 2, "S"}, + {TokenType::String, 3, "TO\""} + }); +} + +void tst_Tokenizer::string() +{ + checkData(); +} + +void tst_Tokenizer::character_data() +{ + setupData(); + + addRow("simple", "'a'", { + {TokenType::Char, 0, "'a'"} + }); + addRow("too many", "'abc'", { + {TokenType::Char, 0, "'a"}, + {TokenType::CharError, 2, "bc"}, + {TokenType::Char, 4, "'"} + }); + addRow("too few", "''", { + {TokenType::Char, 0, "'"}, + {TokenType::CharError, 1, "'"} + }); + addRow("only quote", "'", { + {TokenType::CharError, 0, "'"} + }); + addRow("unterminated", "'a", { + {TokenType::Char, 0, "'"}, + {TokenType::CharError, 1, "a"} + }); + addRow("unterminated too many", "'abc", { + {TokenType::Char, 0, "'a"}, + {TokenType::CharError, 2, "bc"} + }); + addRow("unterminated backslash", "'\\", { + {TokenType::Char, 0, "'"}, + {TokenType::CharError, 1, "\\"} + }); + + // char escapes (including wrong ones) + for (char c = '!'; c <= '~'; ++c) { + // skip uppercase and '^', since these can be part of ascii escapes + // and 'o' and 'x' since they start octal and hex escapes + // and digits as part of decimal escape + if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '^' || c == 'o' || c == 'x') + continue; + const QChar qc(c); + const QByteArray name = QString("charesc '%1'").arg(qc).toUtf8(); + const QString input = QString("'\\%1'").arg(qc); + if (c != '&' && escapes.contains(c)) { + addRow(name.constData(), input, { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, QLatin1String("\\") + qc}, + {TokenType::Char, 3, "'"} + }); + } else { + addRow(name.constData(), input, { + {TokenType::Char, 0, "'\\"}, + {TokenType::CharError, 2, qc}, + {TokenType::Char, 3, "'"} + }); + } + } + + addRow("decimal escape", "'\\1234'", { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, "\\1234"}, + {TokenType::Char, 6, "'"} + }); + addRow("decimal escape too long", "'\\1234a'", { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, "\\1234"}, + {TokenType::CharError, 6, "a"}, + {TokenType::Char, 7, "'"} + }); + + addRow("octal escape", "'\\o067'", { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, "\\o067"}, + {TokenType::Char, 6, "'"} + }); + addRow("octal escape error", "'\\o8'", { + {TokenType::Char, 0, "'\\"}, + {TokenType::CharError, 2, "o"}, + {TokenType::CharError, 3, "8"}, + {TokenType::Char, 4, "'"} + }); + + addRow("hexadecimal escape", "'\\x0678Ab'", { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, "\\x0678Ab"}, + {TokenType::Char, 9, "'"} + }); + addRow("hexadecimal escape error", "'\\xg'", { + {TokenType::Char, 0, "'\\"}, + {TokenType::CharError, 2, "x"}, + {TokenType::CharError, 3, "g"}, + {TokenType::Char, 4, "'"} + }); + + // ascii cntrl escapes (including wrong ones) + for (char c = '!'; c <= '~'; ++c) { + if (c == '\'') // is special because it also ends the string + continue; + const QChar qc(c); + const QByteArray name = QString("ascii cntrl '^%1'").arg(qc).toUtf8(); + const QString input = QString("'\\^%1'").arg(qc); + if ((qc >= 'A' && qc <= 'Z') || qc == '@' || qc == '[' || qc == '\\' || qc == ']' + || qc == '^' || qc == '_') { + addRow(name.constData(), input, { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, QLatin1String("\\^") + qc}, + {TokenType::Char, 4, "'"} + }); + } else { + addRow(name.constData(), input, { + {TokenType::Char, 0, "'\\"}, + {TokenType::CharError, 2, "^"}, + {TokenType::CharError, 3, qc}, + {TokenType::Char, 4, "'"} + }); + } + } + + addRow("ascii escape SOH", "'\\SOH'", { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, "\\SOH"}, + {TokenType::Char, 5, "'"} + }); + addRow("ascii escape SO, too long", "'\\SON'", { + {TokenType::Char, 0, "'"}, + {TokenType::EscapeSequence, 1, "\\SO"}, + {TokenType::CharError, 4, "N"}, + {TokenType::Char, 5, "'"} + }); + addRow("ascii escape error", "'\\TON'", { + {TokenType::Char, 0, "'\\"}, + {TokenType::CharError, 2, "T"}, + {TokenType::CharError, 3, "ON"}, + {TokenType::Char, 5, "'"} + }); +} + +void tst_Tokenizer::character() +{ + checkData(); +} + +void tst_Tokenizer::number_data() +{ + setupData(); + + addRow("decimal", "012345", { + {TokenType::Integer, 0, "012345"} + }); + addRow("single digit decimal", "0", { + {TokenType::Integer, 0, "0"} + }); + addRow("octal", "0o1234", { + {TokenType::Integer, 0, "0o1234"} + }); + // this is a bit weird, but correct: octal 1 followed by decimal 8 + addRow("number after octal", "0O18", { + {TokenType::Integer, 0, "0O1"}, + {TokenType::Integer, 3, "8"} + }); + addRow("not octal", "0o9", { + {TokenType::Integer, 0, "0"}, + {TokenType::Variable, 1, "o9"}, + }); + addRow("hexadecimal", "0x9fA", { + {TokenType::Integer, 0, "0x9fA"} + }); + // hex number followed by identifier 'g' + addRow("hexadecimal", "0X9fg", { + {TokenType::Integer, 0, "0X9f"}, + {TokenType::Variable, 4, "g"} + }); + + // 0 followed by identifier + addRow("decimal followed by identifier", "0z6", { + {TokenType::Integer, 0, "0"}, + {TokenType::Variable, 1, "z6"} + }); + + addRow("float", "0123.45", { + {TokenType::Float, 0, "0123.45"} + }); + addRow("decimal + operator '.'", "0123.", { + {TokenType::Integer, 0, "0123"}, + {TokenType::Operator, 4, "."} + }); + addRow("operator '.' + decimal", ".0123", { + {TokenType::Operator, 0, "."}, + {TokenType::Integer, 1, "0123"} + }); + addRow("without '.', with exp 'e'", "0123e45", { + {TokenType::Float, 0, "0123e45"} + }); + addRow("without '.', with exp 'E'", "0123E45", { + {TokenType::Float, 0, "0123E45"} + }); + addRow("without '.', with '+'", "0123e+45", { + {TokenType::Float, 0, "0123e+45"} + }); + addRow("without '.', with '-'", "0123e-45", { + {TokenType::Float, 0, "0123e-45"} + }); + addRow("without '.', with '+', missing decimal", "0123e+", { + {TokenType::Integer, 0, "0123"}, + {TokenType::Variable, 4, "e"}, + {TokenType::Operator, 5, "+"} + }); + addRow("without '.', missing decimal", "0123e", { + {TokenType::Integer, 0, "0123"}, + {TokenType::Variable, 4, "e"} + }); + addRow("exp 'e'", "01.23e45", { + {TokenType::Float, 0, "01.23e45"} + }); + addRow("exp 'E'", "01.23E45", { + {TokenType::Float, 0, "01.23E45"} + }); + addRow("with '+'", "01.23e+45", { + {TokenType::Float, 0, "01.23e+45"} + }); + addRow("with '-'", "01.23e-45", { + {TokenType::Float, 0, "01.23e-45"} + }); + addRow("with '+', missing decimal", "01.23e+", { + {TokenType::Float, 0, "01.23"}, + {TokenType::Variable, 5, "e"}, + {TokenType::Operator, 6, "+"} + }); + addRow("missing decimal", "01.23e", { + {TokenType::Float, 0, "01.23"}, + {TokenType::Variable, 5, "e"} + }); +} + +void tst_Tokenizer::number() +{ + checkData(); +} + +void tst_Tokenizer::keyword_data() +{ + setupData(); + + addRow("data", "data", { + {TokenType::Keyword, 0, "data"} + }); + addRow("not a qualified varid", "Foo.case", { + {TokenType::Constructor, 0, "Foo"}, + {TokenType::Operator, 3, "."}, + {TokenType::Keyword, 4, "case"} + }); + addRow(":", ":", { + {TokenType::Keyword, 0, ":"} + }); + addRow("->", "->", { + {TokenType::Keyword, 0, "->"} + }); + addRow("not a qualified varsym", "Foo...", { + {TokenType::Constructor, 0, "Foo"}, + {TokenType::Operator, 3, "..."} + }); +} + +void tst_Tokenizer::keyword() +{ + checkData(); +} + +void tst_Tokenizer::variable_data() +{ + setupData(); + + addRow("simple", "fOo_1'", { + {TokenType::Variable, 0, "fOo_1'"} + }); + addRow("start with '_'", "_1", { + {TokenType::Variable, 0, "_1"} + }); + addRow("not a keyword", "cases", { + {TokenType::Variable, 0, "cases"} + }); + addRow("not a keyword 2", "qualified", { + {TokenType::Variable, 0, "qualified"} + }); + addRow("not a keyword 3", "as", { + {TokenType::Variable, 0, "as"} + }); + addRow("not a keyword 4", "hiding", { + {TokenType::Variable, 0, "hiding"} + }); + addRow(".variable", ".foo", { + {TokenType::Operator, 0, "."}, + {TokenType::Variable, 1, "foo"} + }); + addRow("variable.", "foo.", { + {TokenType::Variable, 0, "foo"}, + {TokenType::Operator, 3, "."} + }); + addRow("variable.variable", "blah.foo", { + {TokenType::Variable, 0, "blah"}, + {TokenType::Operator, 4, "."}, + {TokenType::Variable, 5, "foo"} + }); + addRow("qualified", "Blah.foo", { + {TokenType::Variable, 0, "Blah.foo"} + }); + addRow("qualified2", "Goo.Blah.foo", { + {TokenType::Variable, 0, "Goo.Blah.foo"} + }); + addRow("variable + op '..'", "foo..", { + {TokenType::Variable, 0, "foo"}, + {TokenType::Keyword, 3, ".."} + }); + addRow("variable + op '...'", "foo...", { + {TokenType::Variable, 0, "foo"}, + {TokenType::Operator, 3, "..."} + }); +} + +void tst_Tokenizer::variable() +{ + checkData(); +} + +void tst_Tokenizer::constructor_data() +{ + setupData(); + + addRow("simple", "Foo", { + {TokenType::Constructor, 0, "Foo"} + }); + addRow("qualified", "Foo.Bar", { + {TokenType::Constructor, 0, "Foo.Bar"} + }); + addRow("followed by op '.'", "Foo.Bar.", { + {TokenType::Constructor, 0, "Foo.Bar"}, + {TokenType::Operator, 7, "."} + }); + +} + +void tst_Tokenizer::constructor() +{ + checkData(); +} + +void tst_Tokenizer::op_data() +{ + setupData(); + + addRow("simple", "+-=", { + {TokenType::Operator, 0, "+-="} + }); + addRow("qualified", "Foo.+-=", { + {TokenType::Operator, 0, "Foo.+-="} + }); + addRow("qualified '.'", "Foo..", { + {TokenType::Operator, 0, "Foo.."} + }); + addRow("constructor plus op", "Foo+", { + {TokenType::Constructor, 0, "Foo"}, + {TokenType::Operator, 3, "+"} + }); +} + +void tst_Tokenizer::op() +{ + checkData(); +} + +QTEST_MAIN(tst_Tokenizer) + +#include "tst_tokenizer.moc" diff --git a/tests/manual/haskell/simple/README.md b/tests/manual/haskell/simple/README.md new file mode 100644 index 00000000000..7011e6ed4e9 --- /dev/null +++ b/tests/manual/haskell/simple/README.md @@ -0,0 +1 @@ +# simple test project diff --git a/tests/manual/haskell/simple/Setup.hs b/tests/manual/haskell/simple/Setup.hs new file mode 100644 index 00000000000..9a994af677b --- /dev/null +++ b/tests/manual/haskell/simple/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/tests/manual/haskell/simple/simple.cabal b/tests/manual/haskell/simple/simple.cabal new file mode 100644 index 00000000000..dd2a85e6001 --- /dev/null +++ b/tests/manual/haskell/simple/simple.cabal @@ -0,0 +1,14 @@ +name: simple +version: 0.1.0.0 +-- synopsis: +-- description: +category: Test +build-type: Simple +cabal-version: >=1.10 +extra-source-files: README.md + +executable simple + hs-source-dirs: src + main-is: Main.hs + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 diff --git a/tests/manual/haskell/simple/src/Main.hs b/tests/manual/haskell/simple/src/Main.hs new file mode 100644 index 00000000000..9cd992d9e53 --- /dev/null +++ b/tests/manual/haskell/simple/src/Main.hs @@ -0,0 +1,5 @@ +module Main where + +main :: IO () +main = do + putStrLn "hello world" diff --git a/tests/manual/haskell/simple/stack.yaml b/tests/manual/haskell/simple/stack.yaml new file mode 100644 index 00000000000..9f5c8c628b2 --- /dev/null +++ b/tests/manual/haskell/simple/stack.yaml @@ -0,0 +1,66 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# resolver: ghcjs-0.1.0_ghc-7.10.2 +# resolver: +# name: custom-snapshot +# location: "./custom-snapshot.yaml" +resolver: lts-7.24 + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# - location: +# git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# extra-dep: true +# subdirs: +# - auto-update +# - wai +# +# A package marked 'extra-dep: true' will only be built if demanded by a +# non-dependency (i.e. a user package), and its test suites and benchmarks +# will not be run. This is useful for tweaking upstream packages. +packages: +- . +# Dependency packages to be pulled from upstream that are not in the resolver +# (e.g., acme-missiles-0.3) +# extra-deps: [] + +# Override default flag values for local packages and extra-deps +# flags: {} + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=1.6" +# +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor