From 73c0601d5d2213a55e754eee07ed2832db7d0949 Mon Sep 17 00:00:00 2001 From: smdn Date: Tue, 16 Aug 2022 16:36:35 +0900 Subject: [PATCH 01/12] add C++ implementation --- README.md | 1 + src/impls/cpp/.editorconfig | 15 + src/impls/cpp/.gitignore | 2 + src/impls/cpp/Makefile | 23 + src/impls/cpp/README.md | 45 ++ src/impls/cpp/polish.cpp | 433 ++++++++++++++++++ tests/impls/implementations.jsonc | 10 + .../testcases/calculable-expressions.jsonc | 16 +- 8 files changed, 537 insertions(+), 8 deletions(-) create mode 100644 src/impls/cpp/.editorconfig create mode 100644 src/impls/cpp/.gitignore create mode 100644 src/impls/cpp/Makefile create mode 100644 src/impls/cpp/README.md create mode 100644 src/impls/cpp/polish.cpp diff --git a/README.md b/README.md index 5539f26..a772258 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ 現在、以下の言語の実装があります。 - [C](src/impls/c/) +- [C++](src/impls/cpp/) - [C#](src/impls/csharp/) - [Java](src/impls/java/) - [JavaScript](src/impls/javascript/) diff --git a/src/impls/cpp/.editorconfig b/src/impls/cpp/.editorconfig new file mode 100644 index 0000000..3e0a09b --- /dev/null +++ b/src/impls/cpp/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +[*.cpp] +indent_style = space +indent_size = 4 + +# ref: https://docs.microsoft.com/ja-jp/visualstudio/ide/cpp-editorconfig-properties +cpp_new_line_before_open_brace_type = same_line +cpp_new_line_before_open_brace_function = new_line +cpp_new_line_before_open_brace_block = same_line +cpp_new_line_before_else = true +cpp_indent_preserve_comments = true +cpp_indent_case_labels = true +cpp_space_around_assignment_operator = ignore +cpp_space_pointer_reference_alignment = left diff --git a/src/impls/cpp/.gitignore b/src/impls/cpp/.gitignore new file mode 100644 index 0000000..57016be --- /dev/null +++ b/src/impls/cpp/.gitignore @@ -0,0 +1,2 @@ +polish +polish.o \ No newline at end of file diff --git a/src/impls/cpp/Makefile b/src/impls/cpp/Makefile new file mode 100644 index 0000000..98368f0 --- /dev/null +++ b/src/impls/cpp/Makefile @@ -0,0 +1,23 @@ +CC = g++ +#CC = clang++ +CFLAGS = -std=c++2a -O4 -Wall -I/usr/local/include + +all: polish + +polish: polish.o + $(CC) polish.o -o polish + +polish.o: polish.cpp + $(CC) $(CFLAGS) -c polish.cpp + +clean: + rm -f *.o polish + +run: polish + @if [ -z "${INPUT}" ]; then \ + ./polish; \ + fi + + @if [ -n "${INPUT}" ]; then \ + echo ${INPUT} | ./polish; \ + fi diff --git a/src/impls/cpp/README.md b/src/impls/cpp/README.md new file mode 100644 index 0000000..6538b0a --- /dev/null +++ b/src/impls/cpp/README.md @@ -0,0 +1,45 @@ +[二分木を使った数式の逆ポーランド記法化と計算](https://smdn.jp/programming/tips/polish/)のC++での実装。 + +# 前提条件・依存関係 +C++コンパイラが必要です。 + +`Makefile`を使用してビルド・実行する場合は、`g++`が必要です。 + +# ビルドおよび実行 +コマンド`make run INPUT=(式)`を実行することで、`INPUT`に与えられた式に対して逆ポーランド記法化・計算を行うことができます。 + +実行例: +```sh +$ make run INPUT='x=1+2' +input expression: expression: x=1+2 +reverse polish notation: x 1 2 + = +infix notation: (x = (1 + 2)) +polish notation: = x + 1 2 +calculated expression: (x = 3) +``` + +その他、`make`コマンドで以下の操作を行うことができます。 + +```sh +make # ソースファイルをコンパイルする +make run # ソースファイルをコンパイルして実行する +make clean # 成果物ファイルを削除する +``` + +## g++でのコンパイル・実行方法 +```sh +g++ -std=c++2a polish.cpp -o polish # ソースファイルをコンパイルする +./polish # コンパイルした実行可能ファイルを実行する +``` + +## Clangでのコンパイル・実行方法 +```sh +clang++ -std=c++2a polish.cpp -o polish # ソースファイルをコンパイルする +./polish # コンパイルした実行可能ファイルを実行する +``` + +### Clangのインストール方法 +Ubuntuの場合: +```sh +sudo apt install clang +``` diff --git a/src/impls/cpp/polish.cpp b/src/impls/cpp/polish.cpp new file mode 100644 index 0000000..23f7c33 --- /dev/null +++ b/src/impls/cpp/polish.cpp @@ -0,0 +1,433 @@ +// SPDX-FileCopyrightText: 2022 smdn +// SPDX-License-Identifier: MIT +#include +#include +#include +#include +#include +#include + +// ノードを構成するデータ構造 +class Node { +private: + std::string expression; // このノードが表す式(二分木への分割後は演算子または項となる) + std::unique_ptr left = nullptr; // 左の子ノード + std::unique_ptr right = nullptr; // 右の子ノード + +public: + inline const std::string& get_expression() const { return expression; } + + // コンストラクタ(与えられた式expressionを持つノードを構成する) + Node(const std::string& expression) + : expression(expression) + { + } + + // 式expressionを二分木へと分割するメソッド + void parse(); + + // 後行順序訪問(帰りがけ順)で二分木を巡回して + // すべてのノードの演算子または項を表示するメソッド + void traverse_postorder() const; + + // 中間順序訪問(通りがけ順)で二分木を巡回して + // すべてのノードの演算子または項を表示するメソッド + void traverse_inorder() const; + + // 先行順序訪問(行きがけ順)で二分木を巡回して + // すべてのノードの演算子または項を表示するメソッド + void traverse_preorder() const; + + // 現在のノードの演算子と左右の子ノードの値から、ノードの値を計算するメソッド + // ノードの値が計算できた場合はtrue、そうでない場合(記号を含む場合など)はfalseを返す + // 計算結果はexpressionに文字列として代入する + bool calculate(); + + // 式expression内の括弧の対応を検証するメソッド + // 開き括弧と閉じ括弧が同数でない場合はエラーとする + static void validate_bracket_balance(const std::string& expression); + +private: + // 式expressionから最も外側にある丸括弧を取り除いて返すメソッド + static std::string remove_outermost_bracket(const std::string& expression); + + // 式expressionから最も右側にあり、かつ優先順位が低い演算子を探して位置を返す関数 + // (演算子がない場合はstring::nposを返す) + static std::string::size_type get_operator_position(std::string_view expression) noexcept; + + // 演算結果の数値を文字列化するためのメソッド + static std::string format_number(const double& number) noexcept; +}; + +// 二分木への分割時に発生したエラーを報告するための例外クラス +class ExpressionParserException : public std::exception { +public: + ExpressionParserException(const std::string& message) + : message(message) + { + } + + virtual const char* what() const noexcept { return message.c_str(); } + +protected: + std::string message; +}; + +void Node::validate_bracket_balance(const std::string& expression) noexcept(false) +{ + auto nest = 0; // 丸括弧の深度(くくられる括弧の数を計上するために用いる) + + // 1文字ずつ検証する + for (auto& ch : expression) { + if ('(' == ch) { + // 開き丸括弧なので深度を1増やす + nest++; + } + else if (')' == ch) { + // 閉じ丸括弧なので深度を1減らす + nest--; + + // 深度が負になった場合 + if (nest < 0) + // 式中で開かれた括弧よりも閉じ括弧が多いため、その時点でエラーとする + // 例:"(1+2))"などの場合 + break; + } + } + + // 深度が0でない場合 + if (0 != nest) + // 式中に開かれていない/閉じられていない括弧があるので、エラーとする + // 例:"((1+2)"などの場合 + throw ExpressionParserException("unbalanced bracket: " + expression); +} + +void Node::parse() noexcept(false) +{ + // 式expressionから最も外側にある丸括弧を取り除く + expression = remove_outermost_bracket(expression); + + // 式expressionから演算子を探して位置を取得する + auto pos_operator = get_operator_position(expression); + + if (std::string::npos == pos_operator) { + // 式expに演算子が含まれない場合、expは項であるとみなす + // (左右に子ノードを持たないノードとする) + left = nullptr; + right = nullptr; + return; + } + + if (0 == pos_operator || (expression.length() - 1) == pos_operator) + // 演算子の位置が式の先頭または末尾の場合は不正な式とする + throw ExpressionParserException("invalid expression: " + expression); + + // 以下、演算子の位置をもとに左右の部分式に分割する + + // 演算子の左側を左の部分式としてノードを作成する + left = std::make_unique(expression.substr(0, pos_operator)); + // 左側のノード(部分式)について、再帰的に二分木へと分割する + left->parse(); + + // 演算子の右側を右の部分式としてノードを作成する + right = std::make_unique(expression.substr(pos_operator + 1)); + // 右側のノード(部分式)について、再帰的に二分木へと分割する + right->parse(); + + // 残った演算子部分をこのノードに設定する + expression = expression.substr(pos_operator, 1); +} + +std::string Node::remove_outermost_bracket(const std::string& expression) noexcept(false) +{ + auto has_outermost_bracket = false; // 最も外側に括弧を持つかどうか + auto nest = 0; // 丸括弧の深度(式中で開かれた括弧が閉じられたかどうか調べるために用いる) + + if ('(' == expression.front()) { + // 0文字目が開き丸括弧の場合、最も外側に丸括弧があると仮定する + has_outermost_bracket = true; + nest = 1; + } + + // 1文字目以降を1文字ずつ検証 + for (auto it = expression.begin() + 1; it != expression.end(); it++) { + if ('(' == *it) { + // 開き丸括弧なので深度を1増やす + nest++; + } + else if (')' == *it) { + // 閉じ丸括弧なので深度を1減らす + nest--; + + // 最後の文字以外で開き丸括弧がすべて閉じられた場合、最も外側には丸括弧がないと判断する + // 例:"(1+2)+(3+4)"などの場合 + if (0 == nest && (it + 1) != expression.end()) { + has_outermost_bracket = false; + break; + } + } + } + + // 最も外側に丸括弧がない場合は、与えられた文字列をそのまま返す + if (!has_outermost_bracket) + return std::string(expression); + + // 文字列の長さが2未満の場合は、つまり空の丸括弧"()"なのでエラーとする + if (expression.length() <= 2) + throw ExpressionParserException("empty bracket: " + expression); + + // 最初と最後の文字を取り除く(最も外側の丸括弧を取り除く) + auto expr = expression.substr(1, expression.length() - 2); + + // 取り除いた後の文字列の最も外側に括弧が残っている場合 + // 例:"((1+2))"などの場合 + if ('(' == expr.front() && ')' == expr.back()) + // 再帰的に呼び出して取り除く + expr = remove_outermost_bracket(expr); + + // 取り除いた結果を返す + return expr; +} + +std::string::size_type Node::get_operator_position(std::string_view expression) noexcept +{ + // 現在見つかっている演算子の位置(初期値としてstring::npos=演算子なしを設定) + auto pos_operator = std::string::npos; + // 現在見つかっている演算子の優先順位(初期値としてintの最大値を設定) + auto priority_current = std::numeric_limits::max(); + // 丸括弧の深度(括弧でくくられていない部分の演算子を「最も優先順位が低い」と判断するために用いる) + auto nest = 0; + + // 与えられた文字列を先頭から1文字ずつ検証する + for (auto it = expression.begin(); it < expression.end(); it++) { + int priority; // 演算子の優先順位(値が低いほど優先順位が低いものとする) + + switch (*it) { + // 文字が演算子かどうか検証し、演算子の場合は演算子の優先順位を設定する + case '=': priority = 1; break; + case '+': priority = 2; break; + case '-': priority = 2; break; + case '*': priority = 3; break; + case '/': priority = 3; break; + // 文字が丸括弧の場合は、括弧の深度を設定する + case '(': nest++; continue; + case ')': nest--; continue; + // それ以外の文字の場合は何もしない + default: continue; + } + + // 括弧の深度が0(丸括弧でくくられていない部分)かつ、 + // 現在見つかっている演算子よりも優先順位が同じか低い場合 + // (優先順位が同じ場合は、より右側に同じ優先順位の演算子があることになる) + if (0 == nest && priority <= priority_current) { + // 最も優先順位が低い演算子とみなし、その位置を保存する + priority_current = priority; + pos_operator = std::distance(expression.begin(), it); + } + } + + // 見つかった演算子の位置を返す + return pos_operator; +} + +void Node::traverse_postorder() const +{ + // 左右に子ノードをもつ場合、表示する前にノードを再帰的に巡回する + if (left) + left->traverse_postorder(); + if (right) + right->traverse_postorder(); + + // 巡回を終えた後でノードの演算子または項を表示する + // (読みやすさのために項の後に空白を補って表示する) + std::cout << expression << ' '; +} + +void Node::traverse_inorder() const +{ + // 左右に項を持つ場合、読みやすさのために項の前に開き括弧を補う + if (left && right) + std::cout << '('; + + // 表示する前に左の子ノードを再帰的に巡回する + if (left) { + left->traverse_inorder(); + + // 読みやすさのために空白を補う + std::cout << ' '; + } + + // 左の子ノードの巡回を終えた後でノードの演算子または項を表示する + std::cout << expression; + + // 表示した後に右の子ノードを再帰的に巡回する + if (right) { + // 読みやすさのために空白を補う + std::cout << ' '; + + right->traverse_inorder(); + } + + // 左右に項を持つ場合、読みやすさのために項の後に閉じ括弧を補う + if (left && right) + std::cout << ')'; +} + +void Node::traverse_preorder() const +{ + // 巡回を始める前にノードの演算子または項を表示する + // (読みやすさのために項の後に空白を補って表示する) + std::cout << expression << ' '; + + // 左右に子ノードをもつ場合、表示した後にノードを再帰的に巡回する + if (left) + left->traverse_preorder(); + if (right) + right->traverse_preorder(); +} + +bool Node::calculate() +{ + // 左右に子ノードを持たない場合、現在のノードは部分式ではなく項であり、 + // それ以上計算できないのでtrueを返す + if (!left || !right) + return true; + + // 左右の子ノードについて、再帰的にノードの値を計算する + left->calculate(); + right->calculate(); + + // 計算した左右の子ノードの値を数値型(double)に変換する + // 変換できない場合(左右の子ノードが記号を含む式などの場合)は、 + // ノードの値が計算できないものとして、falseを返す + double left_operand, right_operand; + + try { + size_t pos_invalid; // std::stodで変換できない文字の位置を検出するための変数 + + // 左ノードの値を数値に変換して演算子の左項left_operandの値とする + left_operand = std::stod(left->expression, &pos_invalid); + + if (pos_invalid < left->expression.length()) + return false; // 途中に変換できない文字があるため、計算できないものとして扱う + + // 右ノードの値を数値に変換して演算子の右項right_operandの値とする + right_operand = std::stod(right->expression, &pos_invalid); + + if (pos_invalid < right->expression.length()) + return false; // 途中に変換できない文字があるため、計算できないものとして扱う + } + catch (std::out_of_range&) { + return false;// doubleで扱える範囲外のため、計算できないものとして扱う + } + catch (std::invalid_argument&) { + return false; // doubleに変換できないため、計算できないものとして扱う + } + + // 現在のノードの演算子に応じて左右の子ノードの値を演算し、 + // 演算した結果を文字列に変換して再度expressionに代入することで現在のノードの値とする + switch (expression.front()) { + case '+': expression = format_number(left_operand + right_operand); break; + case '-': expression = format_number(left_operand - right_operand); break; + case '*': expression = format_number(left_operand * right_operand); break; + case '/': expression = format_number(left_operand / right_operand); break; + // 上記以外の演算子の場合は計算できないものとして、falseを返す + default: return false; + } + + // 左右の子ノードの値から現在のノードの値が求まったため、 + // このノードは左右に子ノードを持たない値のみのノードとする + left = nullptr; + right = nullptr; + + // 計算できたため、trueを返す + return true; +} + +// 演算結果の数値を文字列化するためのメソッド +std::string Node::format_number(const double& number) noexcept +{ + std::ostringstream stream; + + // %.17g + stream.precision(17); // %.17 + stream + << std::defaultfloat // %g + << number; + + return stream.str(); +} + +// main関数。 結果によって次の値を返す。 +// 0: 正常終了 (二分木への分割、および式全体の値の計算に成功した場合) +// 1: 入力のエラーによる終了 (二分木への分割に失敗した場合) +// 2: 計算のエラーによる終了 (式全体の値の計算に失敗した場合) +int main() +{ + std::cout << "input expression: "; + + // 標準入力から二分木に分割したい式を入力する + std::string expression; + + if (!std::getline(std::cin, expression)) + // 入力が得られなかった場合は、処理を終了する + return 1; + + // 入力された式から空白を除去する + expression.erase( + std::remove(expression.begin(), expression.end(), ' '), + expression.end() + ); + + if (0 == expression.length()) + // 空白を除去した結果、空の文字列となった場合は、処理を終了する + return 1; + + std::unique_ptr root = nullptr; + + try { + // 入力された式における括弧の対応数をチェックする + Node::validate_bracket_balance(expression); + + // 二分木の根(root)ノードを作成し、式全体を格納する + root = std::make_unique(expression); + + std::cout << "expression: " << root->get_expression() << std::endl; + + // 根ノードに格納した式を二分木へと分割する + root->parse(); + + // 分割した二分木を帰りがけ順で巡回して表示する(前置記法/逆ポーランド記法で表示される) + std::cout << "reverse polish notation: "; + root->traverse_postorder(); + std::cout << std::endl; + + // 分割した二分木を通りがけ順で巡回して表示する(中置記法で表示される) + std::cout << "infix notation: "; + root->traverse_inorder(); + std::cout << std::endl; + + // 分割した二分木を行きがけ順で巡回して表示する(後置記法/ポーランド記法で表示される) + std::cout << "polish notation: "; + root->traverse_preorder(); + std::cout << std::endl; + } + catch (const ExpressionParserException& err) { + std::cerr << err.what() << std::endl; + return 1; + } + + // 分割した二分木から式全体の値を計算する + if (root->calculate()) { + // 計算できた場合はその値を表示する + std::cout << "calculated result: " << root->get_expression() << std::endl; + return 0; + } + else { + // (式の一部あるいは全部が)計算できなかった場合は、計算結果の式を中置記法で表示する + std::cout << "calculated expression: "; + root->traverse_inorder(); + std::cout << std::endl; + return 2; + } +} diff --git a/tests/impls/implementations.jsonc b/tests/impls/implementations.jsonc index 276389b..b19c42f 100644 --- a/tests/impls/implementations.jsonc +++ b/tests/impls/implementations.jsonc @@ -19,6 +19,16 @@ "Run": { "Command": "polish", "ResolveCommandPath": true }, } }, + { + "ID": "cpp", + "DisplayName": "C++", + "Directory": "src/impls/cpp/", + "Commands": { + "Build": [ { "Command": "make" } ], + "Clean": [ { "Command": "make", "Arguments": [ "clean" ] } ], + "Run": { "Command": "polish", "ResolveCommandPath": true }, + } + }, { "ID": "python", "DisplayName": "Python 3", diff --git a/tests/impls/testcases/calculable-expressions.jsonc b/tests/impls/testcases/calculable-expressions.jsonc index 7c308d7..e2986af 100644 --- a/tests/impls/testcases/calculable-expressions.jsonc +++ b/tests/impls/testcases/calculable-expressions.jsonc @@ -39,14 +39,14 @@ { "Input": "1/8", "ExpectedCalculationResult": "0.125" }, // test cases for floating point formatting equivalent to '%.17g' of C printf - { "Input": "1/3", "ExpectedCalculationResult": "0.33333333333333331", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "2/3", "ExpectedCalculationResult": "0.66666666666666663", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "1/7", "ExpectedCalculationResult": "0.14285714285714285", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "10000000000000000/1", "ExpectedCalculationResult": "10000000000000000", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "1/10000000000000000", "ExpectedCalculationResult": "9.9999999999999998e-17", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "99999999999999999/1", "ExpectedCalculationResult": "1e+17", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "(3/2)*(10000000000000000/1)", "ExpectedCalculationResult": "15000000000000000", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, - { "Input": "(3/2)/(10000000000000000/1)", "ExpectedCalculationResult": "1.5e-16", "TargetImplementations": [ "c", "csharp", "python", "visualbasic" ] }, + { "Input": "1/3", "ExpectedCalculationResult": "0.33333333333333331", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "2/3", "ExpectedCalculationResult": "0.66666666666666663", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "1/7", "ExpectedCalculationResult": "0.14285714285714285", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "10000000000000000/1", "ExpectedCalculationResult": "10000000000000000", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "1/10000000000000000", "ExpectedCalculationResult": "9.9999999999999998e-17", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "99999999999999999/1", "ExpectedCalculationResult": "1e+17", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "(3/2)*(10000000000000000/1)", "ExpectedCalculationResult": "15000000000000000", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, + { "Input": "(3/2)/(10000000000000000/1)", "ExpectedCalculationResult": "1.5e-16", "TargetImplementations": [ "c", "cpp", "csharp", "python", "visualbasic" ] }, // test cases for floating point formatting specific to Intl.NumberFormat and/or java.text.NumberFormat (maximumSignificantDigits = 17) { "Input": "1/3", "ExpectedCalculationResult": "0.3333333333333333", "TargetImplementations": [ "java", "javascript" ] }, From ee0b19eb014a831fa4a8ebdc8260743179f48fe0 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 03:10:28 +0900 Subject: [PATCH 02/12] add support for building with CL.exe and MSBuild.exe --- .editorconfig | 2 +- src/impls/cpp/.gitignore | 4 +++- src/impls/cpp/README.md | 33 ++++++++++++++++++++++++++++++ src/impls/cpp/polish.vcxproj | 39 ++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/impls/cpp/polish.vcxproj diff --git a/.editorconfig b/.editorconfig index 5d7a248..e2c1f82 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,7 +14,7 @@ trim_trailing_whitespace = true charset = utf-8-bom end_of_line = crlf -[*.{csproj,vbproj,proj,props,targets}] +[*.{csproj,vbproj,vcxproj,proj,props,targets}] charset = utf-8-bom indent_size = 2 end_of_line = crlf diff --git a/src/impls/cpp/.gitignore b/src/impls/cpp/.gitignore index 57016be..3905b44 100644 --- a/src/impls/cpp/.gitignore +++ b/src/impls/cpp/.gitignore @@ -1,2 +1,4 @@ polish -polish.o \ No newline at end of file +polish.o +/Debug/ +/Release/ \ No newline at end of file diff --git a/src/impls/cpp/README.md b/src/impls/cpp/README.md index 6538b0a..ad07d30 100644 --- a/src/impls/cpp/README.md +++ b/src/impls/cpp/README.md @@ -6,6 +6,7 @@ C++コンパイラが必要です。 `Makefile`を使用してビルド・実行する場合は、`g++`が必要です。 # ビルドおよび実行 +## `make`コマンドを使用する場合 コマンド`make run INPUT=(式)`を実行することで、`INPUT`に与えられた式に対して逆ポーランド記法化・計算を行うことができます。 実行例: @@ -26,6 +27,38 @@ make run # ソースファイルをコンパイルして実行する make clean # 成果物ファイルを削除する ``` +## MSBuildを使用する場合 +コマンド`msbuild`を実行することでビルドすることができます。 `Debug\polish.exe`が生成されるので、それを実行することで逆ポーランド記法化・計算を行うことができます。 + +```bat +> msbuild + ︙ + (中略) + ︙ +ビルドに成功しました。 + 0 個の警告 + 0 エラー + +経過時間 00:00:01.31 + +> Debug\polish.exe +input expression: x=1+2 +expression: x=1+2 +reverse polish notation: x 1 2 + = +infix notation: (x = (1 + 2)) +polish notation: = x + 1 2 +calculated expression: (x = 3) +``` + +その他、`msbuild`コマンドの`/t`オプションで以下の操作を行うことができます。 + +```bat +msbuild /t:Build # ソースファイルをコンパイルする(/t:Buildは省略できます) +msbuild /t:Clean # 成果物ファイルを削除する +``` + +`msbuild`コマンドを使用する場合は、`PATH`環境変数に`msbuild.exe`のあるディレクトリを追加しておく必要があります。 + ## g++でのコンパイル・実行方法 ```sh g++ -std=c++2a polish.cpp -o polish # ソースファイルをコンパイルする diff --git a/src/impls/cpp/polish.vcxproj b/src/impls/cpp/polish.vcxproj new file mode 100644 index 0000000..df0ff00 --- /dev/null +++ b/src/impls/cpp/polish.vcxproj @@ -0,0 +1,39 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + Application + v142 + x64 + + + + + + + stdcpp20 + /utf-8 + + + + + + \ No newline at end of file From 988e02204f25af51233ba9f80505255e53ef7b27 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 03:13:33 +0900 Subject: [PATCH 03/12] add find-msbuild.ps1 --- src/impls/cpp/README.md | 7 +++++ src/impls/cpp/find-msbuild.ps1 | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/impls/cpp/find-msbuild.ps1 diff --git a/src/impls/cpp/README.md b/src/impls/cpp/README.md index ad07d30..51d23d1 100644 --- a/src/impls/cpp/README.md +++ b/src/impls/cpp/README.md @@ -59,6 +59,13 @@ msbuild /t:Clean # 成果物ファイルを削除する `msbuild`コマンドを使用する場合は、`PATH`環境変数に`msbuild.exe`のあるディレクトリを追加しておく必要があります。 +スクリプト`find-msbuild.ps1`を実行することで、`msbuild.exe`のパスを検索することができます。 + +```bat +> .\find-msbuild.ps1 +C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe +``` + ## g++でのコンパイル・実行方法 ```sh g++ -std=c++2a polish.cpp -o polish # ソースファイルをコンパイルする diff --git a/src/impls/cpp/find-msbuild.ps1 b/src/impls/cpp/find-msbuild.ps1 new file mode 100644 index 0000000..fec3e1e --- /dev/null +++ b/src/impls/cpp/find-msbuild.ps1 @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2022 smdn +# SPDX-License-Identifier: MIT +# +# This script is based on 'MSBuilsSearch_impl.ps1' from https://github.com/rot-z/MSBuildSearch, published under the MIT Lisence. +# + +# folder paths of Visual Studio +$MSBUILD_17_ENTERPRISE_PATH = "Microsoft Visual Studio`\2022`\Enterprise`\MSBuild`\Current`\Bin" # Visual Studio 2022 Enterprise +$MSBUILD_16_COMMUNITY_PATH = "Microsoft Visual Studio`\2019`\Community`\MSBuild`\Current`\Bin" # Visual Studio 2019 Community (not tested) +$MSBUILD_16_PROFESSIONAL_PATH = "Microsoft Visual Studio`\2019`\Professional`\MSBuild`\Current`\Bin" # Visual Studio 2019 Professional +$MSBUILD_15_COMMUNITY_PATH = "Microsoft Visual Studio`\2017`\Community`\MSBuild`\15.0`\Bin" # Visual Studio 2017 Community +$MSBUILD_15_PROFESSIONAL_PATH = "Microsoft Visual Studio`\2017`\Professional`\MSBuild`\15.0`\Bin" # Visual Studio 2017 Professional + +# target paths for MSBuild +# sort by priority +[array]$SEARCH_PATHS = @( + $MSBUILD_17_ENTERPRISE_PATH, + $MSBUILD_16_COMMUNITY_PATH, + $MSBUILD_16_PROFESSIONAL_PATH, + $MSBUILD_15_COMMUNITY_PATH, + $MSBUILD_15_PROFESSIONAL_PATH +) + +# get full path of "Program Files" folder from OS archtechture +$programFilesDir = "" +if ($([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture) -eq 'X64') +{ + $programFilesDir = ${env:ProgramFiles(x86)} +} +else +{ + $programFilesDir = ${env:ProgramFiles} +} + +# search MSBuild.exe +$msbuildPath = "" +foreach($p in $SEARCH_PATHS) +{ + # is folder exists? + $targetPath = Join-Path $programFilesDir $p + if (!(Test-Path $targetPath)) + { + continue + } + + # select the most shortest (shallowest) path + $results = (Get-ChildItem $targetPath -Include MSBuild.exe -Recurse).FullName | Sort-Object -Property Length + if ($results.Length -gt 0) + { + $msbuildPath = $results[0] + Write-Output $msbuildPath + exit 0 + } +} + +# not found +exit 1 \ No newline at end of file From 0fbf5d3bab921fb66b3c0c0556bccc35688f1294 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 03:15:20 +0900 Subject: [PATCH 04/12] add support for testing implementations with the specific condition --- tests/impls/run-tests.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/impls/run-tests.ps1 b/tests/impls/run-tests.ps1 index 6c5ea2c..6ba0754 100755 --- a/tests/impls/run-tests.ps1 +++ b/tests/impls/run-tests.ps1 @@ -313,6 +313,11 @@ if ($TargetImplementationId) { $implementations = $implementations | where ID -eq $TargetImplementationId } +$implementations = $implementations | Where-Object { + # '-or' operator does not performs short-circuit evaluation + ($_.Condition -eq $null) -or (($_.Condition -ne $null) -and (Invoke-Expression $_.Condition)) +} + $testsuites = Get-ChildItem -Path $([System.IO.Path]::Combine($PSScriptRoot, "testcases/*.jsonc")) -File | # where Name -eq 'should-be-fail.jsonc' | ForEach-Object { From 7250396d52a6d5dbdd76550d2380149c18c91c52 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 03:32:37 +0900 Subject: [PATCH 05/12] add definitions for C++ with CI environment and Windows platform --- tests/impls/implementations.jsonc | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/impls/implementations.jsonc b/tests/impls/implementations.jsonc index b19c42f..165f401 100644 --- a/tests/impls/implementations.jsonc +++ b/tests/impls/implementations.jsonc @@ -21,8 +21,39 @@ }, { "ID": "cpp", - "DisplayName": "C++", + "DisplayName": "C++ (CI)", "Directory": "src/impls/cpp/", + "Condition": "$([bool]$env:GITHUB_ACTIONS -and [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows))", + "Commands": { + "Build": [ + { "Command": "$path_msbuild = .\\find-msbuild.ps1", "UseInvokeExpression": true }, + { "Command": "$env:Path += \";$([System.IO.Path]::GetDirectoryName($path_msbuild))\"", "UseInvokeExpression": true }, + { "Command": "MSBuild", "Arguments": [ "/t:Build", "/p:Configuration=Release" ] } + ], + "Clean": [ + { "Command": "$path_msbuild = .\\find-msbuild.ps1", "UseInvokeExpression": true }, + { "Command": "$env:Path += \";$([System.IO.Path]::GetDirectoryName($path_msbuild))\"", "UseInvokeExpression": true }, + { "Command": "MSBuild", "Arguments": [ "/t:Clean", "/p:Configuration=Release" ] } + ], + "Run": { "Command": "Release\\polish.exe", "ResolveCommandPath": true }, + } + }, + { + "ID": "cpp", + "DisplayName": "C++ (MSBuild/Windows)", + "Directory": "src/impls/cpp/", + "Condition": "$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) -and [bool](Get-Command msbuild -errorAction SilentlyContinue))", + "Commands": { + "Build": [ { "Command": "MSBuild", "Arguments": [ "/t:Build", "/p:Configuration=Release" ] } ], + "Clean": [ { "Command": "MSBuild", "Arguments": [ "/t:Clean", "/p:Configuration=Release" ] } ], + "Run": { "Command": "Release\\polish.exe", "ResolveCommandPath": true }, + } + }, + { + "ID": "cpp", + "DisplayName": "C++ (make)", + "Directory": "src/impls/cpp/", + "Condition": "[bool]$(Get-Command make -errorAction SilentlyContinue)", "Commands": { "Build": [ { "Command": "make" } ], "Clean": [ { "Command": "make", "Arguments": [ "clean" ] } ], From eaa1455e7bc19749e60585e175080fc351f5cf98 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 03:35:13 +0900 Subject: [PATCH 06/12] remove duplicates from the target implementation IDs --- .github/workflows/run-tests-impls.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests-impls.yml b/.github/workflows/run-tests-impls.yml index bfab1d4..7981793 100644 --- a/.github/workflows/run-tests-impls.yml +++ b/.github/workflows/run-tests-impls.yml @@ -88,7 +88,7 @@ jobs: shell: pwsh run: | $verbose = '${{ inputs.verbose }}' -ieq 'true' - $target_impls = Get-Content ./tests/impls/implementations.jsonc | ConvertFrom-Json | Select-Object -ExpandProperty ID + $target_impls = Get-Content ./tests/impls/implementations.jsonc | ConvertFrom-Json | Select-Object -ExpandProperty ID | Get-Unique if ( ! [string]::IsNullOrEmpty( '${{ inputs.target-impl }}' ) ) { $target_impls = @('${{ inputs.target-impl }}') From 1dde5a906f256c7f97117a3505e0d351bf9a1338 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 04:01:41 +0900 Subject: [PATCH 07/12] fix not to use 'make' on Windows CI runner --- tests/impls/implementations.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/impls/implementations.jsonc b/tests/impls/implementations.jsonc index 165f401..c3d94db 100644 --- a/tests/impls/implementations.jsonc +++ b/tests/impls/implementations.jsonc @@ -53,7 +53,7 @@ "ID": "cpp", "DisplayName": "C++ (make)", "Directory": "src/impls/cpp/", - "Condition": "[bool]$(Get-Command make -errorAction SilentlyContinue)", + "Condition": "$([bool](Get-Command make -errorAction SilentlyContinue) -and !([bool]$env:GITHUB_ACTIONS -and [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)))", "Commands": { "Build": [ { "Command": "make" } ], "Clean": [ { "Command": "make", "Arguments": [ "clean" ] } ], From 6a1f58001212052b8f782afe46a62a54a70a3274 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 04:01:59 +0900 Subject: [PATCH 08/12] add candidate paths --- src/impls/cpp/find-msbuild.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/impls/cpp/find-msbuild.ps1 b/src/impls/cpp/find-msbuild.ps1 index fec3e1e..d1893c4 100644 --- a/src/impls/cpp/find-msbuild.ps1 +++ b/src/impls/cpp/find-msbuild.ps1 @@ -5,6 +5,8 @@ # # folder paths of Visual Studio +$MSBUILD_VS2022_BUILDTOOLS_PATH = "Microsoft Visual Studio`\2022`\BuildTools`\MSBuild`\Current`\Bin" +$MSBUILD_VS2019_BUILDTOOLS_PATH = "Microsoft Visual Studio`\2019`\BuildTools`\MSBuild`\Current`\Bin" $MSBUILD_17_ENTERPRISE_PATH = "Microsoft Visual Studio`\2022`\Enterprise`\MSBuild`\Current`\Bin" # Visual Studio 2022 Enterprise $MSBUILD_16_COMMUNITY_PATH = "Microsoft Visual Studio`\2019`\Community`\MSBuild`\Current`\Bin" # Visual Studio 2019 Community (not tested) $MSBUILD_16_PROFESSIONAL_PATH = "Microsoft Visual Studio`\2019`\Professional`\MSBuild`\Current`\Bin" # Visual Studio 2019 Professional @@ -14,6 +16,8 @@ $MSBUILD_15_PROFESSIONAL_PATH = "Microsoft Visual Studio`\2017`\Professional`\ # target paths for MSBuild # sort by priority [array]$SEARCH_PATHS = @( + $MSBUILD_VS2022_BUILDTOOLS_PATH, + $MSBUILD_VS2019_BUILDTOOLS_PATH, $MSBUILD_17_ENTERPRISE_PATH, $MSBUILD_16_COMMUNITY_PATH, $MSBUILD_16_PROFESSIONAL_PATH, From 19b999910a75b40afef7ba1240eb96a7e352b4e6 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 04:11:57 +0900 Subject: [PATCH 09/12] ensure MSBuild to be added to PATH envvar --- .github/workflows/run-tests-impls.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/run-tests-impls.yml b/.github/workflows/run-tests-impls.yml index 7981793..b6f38eb 100644 --- a/.github/workflows/run-tests-impls.yml +++ b/.github/workflows/run-tests-impls.yml @@ -153,6 +153,10 @@ jobs: with: node-version: ${{ env.NODEJS_MINIMUM_VERSION }} + - name: Setup MSBuild + if: ${{ startswith(runner, 'windows-') && matrix.target-impl == 'cpp' }} + uses: microsoft/setup-msbuild@v1.1 + - name: Run tests with ${{ matrix.target-impl }} on ${{ matrix.os }} shell: pwsh run: | From 8924e7fea7648bd4f6adc931ce6764e34cb21478 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 04:18:15 +0900 Subject: [PATCH 10/12] fix expression --- .github/workflows/run-tests-impls.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests-impls.yml b/.github/workflows/run-tests-impls.yml index b6f38eb..83697a9 100644 --- a/.github/workflows/run-tests-impls.yml +++ b/.github/workflows/run-tests-impls.yml @@ -154,7 +154,7 @@ jobs: node-version: ${{ env.NODEJS_MINIMUM_VERSION }} - name: Setup MSBuild - if: ${{ startswith(runner, 'windows-') && matrix.target-impl == 'cpp' }} + if: ${{ startsWith(runner.os, 'windows-') && matrix.target-impl == 'cpp' }} uses: microsoft/setup-msbuild@v1.1 - name: Run tests with ${{ matrix.target-impl }} on ${{ matrix.os }} From 201d4525b132827a1af8a513f2aa68d3f92b8eaa Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 04:25:22 +0900 Subject: [PATCH 11/12] fix to compare with OS type name instead of 'runs-on' name --- .github/workflows/run-tests-impls.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests-impls.yml b/.github/workflows/run-tests-impls.yml index 83697a9..1fa0373 100644 --- a/.github/workflows/run-tests-impls.yml +++ b/.github/workflows/run-tests-impls.yml @@ -154,7 +154,7 @@ jobs: node-version: ${{ env.NODEJS_MINIMUM_VERSION }} - name: Setup MSBuild - if: ${{ startsWith(runner.os, 'windows-') && matrix.target-impl == 'cpp' }} + if: ${{ runner.os == 'Windows' && matrix.target-impl == 'cpp' }} uses: microsoft/setup-msbuild@v1.1 - name: Run tests with ${{ matrix.target-impl }} on ${{ matrix.os }} From a6aaa469134fc45252d983a93ab83a9db17656a2 Mon Sep 17 00:00:00 2001 From: smdn Date: Wed, 17 Aug 2022 04:31:12 +0900 Subject: [PATCH 12/12] avoid duplicate test running on CI env --- tests/impls/implementations.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/impls/implementations.jsonc b/tests/impls/implementations.jsonc index c3d94db..49c9eab 100644 --- a/tests/impls/implementations.jsonc +++ b/tests/impls/implementations.jsonc @@ -42,7 +42,7 @@ "ID": "cpp", "DisplayName": "C++ (MSBuild/Windows)", "Directory": "src/impls/cpp/", - "Condition": "$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) -and [bool](Get-Command msbuild -errorAction SilentlyContinue))", + "Condition": "$(![bool]$env:GITHUB_ACTIONS -and [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) -and [bool](Get-Command msbuild -errorAction SilentlyContinue))", "Commands": { "Build": [ { "Command": "MSBuild", "Arguments": [ "/t:Build", "/p:Configuration=Release" ] } ], "Clean": [ { "Command": "MSBuild", "Arguments": [ "/t:Clean", "/p:Configuration=Release" ] } ],