static analysis

This commit is contained in:
mrT23
2024-10-02 08:56:33 +03:00
parent af5a50ac6a
commit c84d84ace2
23 changed files with 1992 additions and 0 deletions

View File

@ -0,0 +1,428 @@
// Taken from 'https://github.com/dolphin-emu/dolphin'
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <bit>
#include <memory>
#include <mbedtls/aes.h>
#include "Common/Assert.h"
#include "Common/CPUDetect.h"
#include "Common/Crypto/AES.h"
#ifdef _MSC_VER
#include <intrin.h>
#else
#if defined(_M_X86_64)
#include <x86intrin.h>
#elif defined(_M_ARM_64)
#include <arm_acle.h>
#include <arm_neon.h>
#endif
#endif
#ifdef _MSC_VER
#define ATTRIBUTE_TARGET(x)
#else
#define ATTRIBUTE_TARGET(x) [[gnu::target(x)]]
#endif
namespace Common::AES
{
// For x64 and arm64, it's very unlikely a user's cpu does not support the accelerated version,
// fallback is just in case.
template <Mode AesMode>
class ContextGeneric final : public Context
{
public:
ContextGeneric(const u8* key)
{
mbedtls_aes_init(&ctx);
if constexpr (AesMode == Mode::Encrypt)
ASSERT(!mbedtls_aes_setkey_enc(&ctx, key, 128));
else
ASSERT(!mbedtls_aes_setkey_dec(&ctx, key, 128));
}
virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out,
size_t len) const override
{
std::array<u8, BLOCK_SIZE> iv_tmp{};
if (iv)
std::memcpy(&iv_tmp[0], iv, BLOCK_SIZE);
constexpr int mode = (AesMode == Mode::Encrypt) ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT;
if (mbedtls_aes_crypt_cbc(const_cast<mbedtls_aes_context*>(&ctx), mode, len, &iv_tmp[0], buf_in,
buf_out))
return false;
if (iv_out)
std::memcpy(iv_out, &iv_tmp[0], BLOCK_SIZE);
return true;
}
private:
mbedtls_aes_context ctx{};
};
#if defined(_M_X86_64)
// Note that (for instructions with same data width) the actual instructions emitted vary depending
// on compiler and flags. The naming is somewhat confusing, because VAES cpuid flag was added after
// VAES(VEX.128):
// clang-format off
// instructions | cpuid flag | #define
// AES(128) | AES | -
// VAES(VEX.128) | AES & AVX | __AVX__
// VAES(VEX.256) | VAES | -
// VAES(EVEX.128) | VAES & AVX512VL | __AVX512VL__
// VAES(EVEX.256) | VAES & AVX512VL | __AVX512VL__
// VAES(EVEX.512) | VAES & AVX512F | __AVX512F__
// clang-format on
template <Mode AesMode>
class ContextAESNI final : public Context
{
static inline __m128i Aes128KeygenAssistFinish(__m128i key, __m128i kga)
{
__m128i tmp = _mm_shuffle_epi32(kga, _MM_SHUFFLE(3, 3, 3, 3));
tmp = _mm_xor_si128(tmp, key);
key = _mm_slli_si128(key, 4);
tmp = _mm_xor_si128(tmp, key);
key = _mm_slli_si128(key, 4);
tmp = _mm_xor_si128(tmp, key);
key = _mm_slli_si128(key, 4);
tmp = _mm_xor_si128(tmp, key);
return tmp;
}
template <size_t RoundIdx>
ATTRIBUTE_TARGET("aes")
inline constexpr void StoreRoundKey(__m128i rk)
{
if constexpr (AesMode == Mode::Encrypt)
round_keys[RoundIdx] = rk;
else
{
constexpr size_t idx = NUM_ROUND_KEYS - RoundIdx - 1;
if constexpr (idx == 0 || idx == NUM_ROUND_KEYS - 1)
round_keys[idx] = rk;
else
round_keys[idx] = _mm_aesimc_si128(rk);
}
}
template <size_t RoundIdx, int Rcon>
ATTRIBUTE_TARGET("aes")
inline constexpr __m128i Aes128Keygen(__m128i rk)
{
rk = Aes128KeygenAssistFinish(rk, _mm_aeskeygenassist_si128(rk, Rcon));
StoreRoundKey<RoundIdx>(rk);
return rk;
}
public:
ContextAESNI(const u8* key)
{
__m128i rk = _mm_loadu_si128((const __m128i*)key);
StoreRoundKey<0>(rk);
rk = Aes128Keygen<1, 0x01>(rk);
rk = Aes128Keygen<2, 0x02>(rk);
rk = Aes128Keygen<3, 0x04>(rk);
rk = Aes128Keygen<4, 0x08>(rk);
rk = Aes128Keygen<5, 0x10>(rk);
rk = Aes128Keygen<6, 0x20>(rk);
rk = Aes128Keygen<7, 0x40>(rk);
rk = Aes128Keygen<8, 0x80>(rk);
rk = Aes128Keygen<9, 0x1b>(rk);
Aes128Keygen<10, 0x36>(rk);
}
ATTRIBUTE_TARGET("aes")
inline void CryptBlock(__m128i* iv, const u8* buf_in, u8* buf_out) const
{
__m128i block = _mm_loadu_si128((const __m128i*)buf_in);
if constexpr (AesMode == Mode::Encrypt)
{
block = _mm_xor_si128(_mm_xor_si128(block, *iv), round_keys[0]);
for (size_t i = 1; i < Nr; ++i)
block = _mm_aesenc_si128(block, round_keys[i]);
block = _mm_aesenclast_si128(block, round_keys[Nr]);
*iv = block;
}
else
{
__m128i iv_next = block;
block = _mm_xor_si128(block, round_keys[0]);
for (size_t i = 1; i < Nr; ++i)
block = _mm_aesdec_si128(block, round_keys[i]);
block = _mm_aesdeclast_si128(block, round_keys[Nr]);
block = _mm_xor_si128(block, *iv);
*iv = iv_next;
}
_mm_storeu_si128((__m128i*)buf_out, block);
}
// Takes advantage of instruction pipelining to parallelize.
template <size_t NumBlocks>
ATTRIBUTE_TARGET("aes")
inline void DecryptPipelined(__m128i* iv, const u8* buf_in, u8* buf_out) const
{
constexpr size_t Depth = NumBlocks;
__m128i block[Depth];
for (size_t d = 0; d < Depth; d++)
block[d] = _mm_loadu_si128(&((const __m128i*)buf_in)[d]);
__m128i iv_next[1 + Depth];
iv_next[0] = *iv;
for (size_t d = 0; d < Depth; d++)
iv_next[1 + d] = block[d];
for (size_t d = 0; d < Depth; d++)
block[d] = _mm_xor_si128(block[d], round_keys[0]);
// The main speedup is here
for (size_t i = 1; i < Nr; ++i)
for (size_t d = 0; d < Depth; d++)
block[d] = _mm_aesdec_si128(block[d], round_keys[i]);
for (size_t d = 0; d < Depth; d++)
block[d] = _mm_aesdeclast_si128(block[d], round_keys[Nr]);
for (size_t d = 0; d < Depth; d++)
block[d] = _mm_xor_si128(block[d], iv_next[d]);
*iv = iv_next[1 + Depth - 1];
for (size_t d = 0; d < Depth; d++)
_mm_storeu_si128(&((__m128i*)buf_out)[d], block[d]);
}
virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out,
size_t len) const override
{
if (len % BLOCK_SIZE)
return false;
__m128i iv_block = iv ? _mm_loadu_si128((const __m128i*)iv) : _mm_setzero_si128();
if constexpr (AesMode == Mode::Decrypt)
{
// On amd zen2...(benchmark, not real-world):
// With AES(128) instructions, BLOCK_DEPTH results in following speedup vs. non-pipelined: 4:
// 18%, 8: 22%, 9: 26%, 10-15: 31%. 16: 8% (register exhaustion). With VAES(VEX.128), 10 gives
// 36% speedup vs. its corresponding baseline. VAES(VEX.128) is ~4% faster than AES(128). The
// result is similar on zen3.
// Zen3 in general is 20% faster than zen2 in aes, and VAES(VEX.256) is 35% faster than
// zen3/VAES(VEX.128).
// It seems like VAES(VEX.256) should be faster?
constexpr size_t BLOCK_DEPTH = 10;
constexpr size_t CHUNK_LEN = BLOCK_DEPTH * BLOCK_SIZE;
while (len >= CHUNK_LEN)
{
DecryptPipelined<BLOCK_DEPTH>(&iv_block, buf_in, buf_out);
buf_in += CHUNK_LEN;
buf_out += CHUNK_LEN;
len -= CHUNK_LEN;
}
}
len /= BLOCK_SIZE;
while (len--)
{
CryptBlock(&iv_block, buf_in, buf_out);
buf_in += BLOCK_SIZE;
buf_out += BLOCK_SIZE;
}
if (iv_out)
_mm_storeu_si128((__m128i*)iv_out, iv_block);
return true;
}
private:
std::array<__m128i, NUM_ROUND_KEYS> round_keys;
};
#endif
#if defined(_M_ARM_64)
template <Mode AesMode>
class ContextNeon final : public Context
{
public:
template <size_t RoundIdx>
inline constexpr void StoreRoundKey(const u32* rk)
{
const uint8x16_t rk_block = vreinterpretq_u8_u32(vld1q_u32(rk));
if constexpr (AesMode == Mode::Encrypt)
round_keys[RoundIdx] = rk_block;
else
{
constexpr size_t idx = NUM_ROUND_KEYS - RoundIdx - 1;
if constexpr (idx == 0 || idx == NUM_ROUND_KEYS - 1)
round_keys[idx] = rk_block;
else
round_keys[idx] = vaesimcq_u8(rk_block);
}
}
ContextNeon(const u8* key)
{
constexpr u8 rcon[]{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
std::array<u32, Nb * NUM_ROUND_KEYS> rk{};
// This uses a nice trick I've seen in wolfssl (not sure original author),
// which uses vaeseq_u8 to assist keygen.
// vaeseq_u8: op1 = SubBytes(ShiftRows(AddRoundKey(op1, op2)))
// given RotWord == ShiftRows for row 1 (rol(x,8))
// Probably not super fast (moves to/from vector regs constantly), but it is nice and simple.
std::memcpy(&rk[0], key, KEY_SIZE);
StoreRoundKey<0>(&rk[0]);
for (size_t i = 0; i < rk.size() - Nk; i += Nk)
{
const uint8x16_t enc = vaeseq_u8(vreinterpretq_u8_u32(vmovq_n_u32(rk[i + 3])), vmovq_n_u8(0));
const u32 temp = vgetq_lane_u32(vreinterpretq_u32_u8(enc), 0);
rk[i + 4] = rk[i + 0] ^ std::rotr(temp, 8) ^ rcon[i / Nk];
rk[i + 5] = rk[i + 4] ^ rk[i + 1];
rk[i + 6] = rk[i + 5] ^ rk[i + 2];
rk[i + 7] = rk[i + 6] ^ rk[i + 3];
// clang-format off
// Not great
const size_t rki = 1 + i / Nk;
switch (rki)
{
case 1: StoreRoundKey< 1>(&rk[Nk * rki]); break;
case 2: StoreRoundKey< 2>(&rk[Nk * rki]); break;
case 3: StoreRoundKey< 3>(&rk[Nk * rki]); break;
case 4: StoreRoundKey< 4>(&rk[Nk * rki]); break;
case 5: StoreRoundKey< 5>(&rk[Nk * rki]); break;
case 6: StoreRoundKey< 6>(&rk[Nk * rki]); break;
case 7: StoreRoundKey< 7>(&rk[Nk * rki]); break;
case 8: StoreRoundKey< 8>(&rk[Nk * rki]); break;
case 9: StoreRoundKey< 9>(&rk[Nk * rki]); break;
case 10: StoreRoundKey<10>(&rk[Nk * rki]); break;
}
// clang-format on
}
}
inline void CryptBlock(uint8x16_t* iv, const u8* buf_in, u8* buf_out) const
{
uint8x16_t block = vld1q_u8(buf_in);
if constexpr (AesMode == Mode::Encrypt)
{
block = veorq_u8(block, *iv);
for (size_t i = 0; i < Nr - 1; ++i)
block = vaesmcq_u8(vaeseq_u8(block, round_keys[i]));
block = vaeseq_u8(block, round_keys[Nr - 1]);
block = veorq_u8(block, round_keys[Nr]);
*iv = block;
}
else
{
uint8x16_t iv_next = block;
for (size_t i = 0; i < Nr - 1; ++i)
block = vaesimcq_u8(vaesdq_u8(block, round_keys[i]));
block = vaesdq_u8(block, round_keys[Nr - 1]);
block = veorq_u8(block, round_keys[Nr]);
block = veorq_u8(block, *iv);
*iv = iv_next;
}
vst1q_u8(buf_out, block);
}
virtual bool Crypt(const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out,
size_t len) const override
{
if (len % BLOCK_SIZE)
return false;
uint8x16_t iv_block = iv ? vld1q_u8(iv) : vmovq_n_u8(0);
len /= BLOCK_SIZE;
while (len--)
{
CryptBlock(&iv_block, buf_in, buf_out);
buf_in += BLOCK_SIZE;
buf_out += BLOCK_SIZE;
}
if (iv_out)
vst1q_u8(iv_out, iv_block);
return true;
}
private:
std::array<uint8x16_t, NUM_ROUND_KEYS> round_keys;
};
#endif
template <Mode AesMode>
std::unique_ptr<Context> CreateContext(const u8* key)
{
if (cpu_info.bAES)
{
#if defined(_M_X86_64)
#if defined(__AVX__)
// If compiler enables AVX, the intrinsics will generate VAES(VEX.128) instructions.
// In the future we may want to compile the code twice and explicitly override the compiler
// flags. There doesn't seem to be much performance difference between AES(128) and
// VAES(VEX.128) at the moment, though.
if (cpu_info.bAVX)
#endif
return std::make_unique<ContextAESNI<AesMode>>(key);
#elif defined(_M_ARM_64)
return std::make_unique<ContextNeon<AesMode>>(key);
#endif
}
return std::make_unique<ContextGeneric<AesMode>>(key);
}
std::unique_ptr<Context> CreateContextEncrypt(const u8* key)
{
return CreateContext<Mode::Encrypt>(key);
}
std::unique_ptr<Context> CreateContextDecrypt(const u8* key)
{
return CreateContext<Mode::Decrypt>(key);
}
// OFB encryption and decryption are the exact same. We don't encrypt though.
void CryptOFB(const u8* key, const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, size_t size)
{
mbedtls_aes_context aes_ctx;
size_t iv_offset = 0;
std::array<u8, 16> iv_tmp{};
if (iv)
std::memcpy(&iv_tmp[0], iv, 16);
ASSERT(!mbedtls_aes_setkey_enc(&aes_ctx, key, 128));
mbedtls_aes_crypt_ofb(&aes_ctx, size, &iv_offset, &iv_tmp[0], buf_in, buf_out);
if (iv_out)
std::memcpy(iv_out, &iv_tmp[0], 16);
}
} // namespace Common::AES

View File

@ -0,0 +1,586 @@
package com.houarizegai.calculator.ui;
import com.houarizegai.calculator.theme.properties.Theme;
import com.houarizegai.calculator.theme.ThemeLoader;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.event.ItemEvent;
import java.util.Map;
import java.util.regex.Pattern;
import java.awt.Color;
import javax.swing.*;
import static com.houarizegai.calculator.util.ColorUtil.hex2Color;
public class CalculatorUI {
private static final String FONT_NAME = "Comic Sans MS";
private static final String DOUBLE_OR_NUMBER_REGEX = "([-]?\\d+[.]\\d*)|(\\d+)|(-\\d+)";
private static final String APPLICATION_TITLE = "Calculator";
private static final int WINDOW_WIDTH = 410;
private static final int WINDOW_HEIGHT = 600;
private static final int BUTTON_WIDTH = 80;
private static final int BUTTON_HEIGHT = 70;
private static final int MARGIN_X = 20;
private static final int MARGIN_Y = 60;
private final JFrame window;
private JComboBox<String> comboCalculatorType;
private JComboBox<String> comboTheme;
private JTextField inputScreen;
private JButton btnC;
private JButton btnBack;
private JButton btnMod;
private JButton btnDiv;
private JButton btnMul;
private JButton btnSub;
private JButton btnAdd;
private JButton btn0;
private JButton btn1;
private JButton btn2;
private JButton btn3;
private JButton btn4;
private JButton btn5;
private JButton btn6;
private JButton btn7;
private JButton btn8;
private JButton btn9;
private JButton btnPoint;
private JButton btnEqual;
private JButton btnRoot;
private JButton btnPower;
private JButton btnLog;
private char selectedOperator = ' ';
private boolean go = true; // For calculate with Opt != (=)
private boolean addToDisplay = true; // Connect numbers in display
private double typedValue = 0;
private final Map<String, Theme> themesMap;
public CalculatorUI() {
themesMap = ThemeLoader.loadThemes();
window = new JFrame(APPLICATION_TITLE);
window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
window.setLocationRelativeTo(null);
int[] columns = {MARGIN_X, MARGIN_X + 90, MARGIN_X + 90 * 2, MARGIN_X + 90 * 3, MARGIN_X + 90 * 4};
int[] rows = {MARGIN_Y, MARGIN_Y + 100, MARGIN_Y + 100 + 80, MARGIN_Y + 100 + 80 * 2, MARGIN_Y + 100 + 80 * 3, MARGIN_Y + 100 + 80 * 4};
initInputScreen(columns, rows);
initButtons(columns, rows);
initCalculatorTypeSelector();
initThemeSelector();
window.setLayout(null);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
public double calculate(double firstNumber, double secondNumber, char operator) {
switch (operator) {
case '+':
return firstNumber + secondNumber;
case '-':
return firstNumber - secondNumber;
case '*':
return firstNumber * secondNumber;
case '/':
return firstNumber / secondNumber;
case '%':
return firstNumber % secondNumber;
case '^':
return Math.pow(firstNumber, secondNumber);
default:
return secondNumber;
}
}
private void initThemeSelector() {
comboTheme = createComboBox(themesMap.keySet().toArray(new String[0]), 230, 30, "Theme");
comboTheme.addItemListener(event -> {
if (event.getStateChange() != ItemEvent.SELECTED)
return;
String selectedTheme = (String) event.getItem();
applyTheme(themesMap.get(selectedTheme));
});
if (themesMap.entrySet().iterator().hasNext()) {
applyTheme(themesMap.entrySet().iterator().next().getValue());
}
}
private void initInputScreen(int[] columns, int[] rows) {
inputScreen = new JTextField("0");
inputScreen.setBounds(columns[0], rows[0], 350, 70);
inputScreen.setEditable(false);
inputScreen.setBackground(Color.WHITE);
inputScreen.setFont(new Font(FONT_NAME, Font.PLAIN, 33));
window.add(inputScreen);
}
private void initCalculatorTypeSelector() {
comboCalculatorType = createComboBox(new String[]{"Standard", "Scientific"}, 20, 30, "Calculator type");
comboCalculatorType.addItemListener(event -> {
if (event.getStateChange() != ItemEvent.SELECTED)
return;
String selectedItem = (String) event.getItem();
switch (selectedItem) {
case "Standard":
window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
btnRoot.setVisible(false);
btnPower.setVisible(false);
btnLog.setVisible(false);
break;
case "Scientific":
window.setSize(WINDOW_WIDTH + 80, WINDOW_HEIGHT);
btnRoot.setVisible(true);
btnPower.setVisible(true);
btnLog.setVisible(true);
break;
}
});
}
private void initButtons(int[] columns, int[] rows) {
btnC = createButton("C", columns[0], rows[1]);
btnC.addActionListener(event -> {
inputScreen.setText("0");
selectedOperator = ' ';
typedValue = 0;
});
btnBack = createButton("<-", columns[1], rows[1]);
btnBack.addActionListener(event -> {
String str = inputScreen.getText();
StringBuilder str2 = new StringBuilder();
for (int i = 0; i < (str.length() - 1); i++) {
str2.append(str.charAt(i));
}
if (str2.toString().equals("")) {
inputScreen.setText("0");
} else {
inputScreen.setText(str2.toString());
}
});
btnMod = createButton("%", columns[2], rows[1]);
btnMod.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()) || !go)
return;
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '%';
go = false;
addToDisplay = false;
});
btnDiv = createButton("/", columns[3], rows[1]);
btnDiv.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '/';
go = false;
addToDisplay = false;
} else {
selectedOperator = '/';
}
});
btn7 = createButton("7", columns[0], rows[2]);
btn7.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("7");
} else {
inputScreen.setText(inputScreen.getText() + "7");
}
} else {
inputScreen.setText("7");
addToDisplay = true;
}
go = true;
});
btn8 = createButton("8", columns[1], rows[2]);
btn8.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("8");
} else {
inputScreen.setText(inputScreen.getText() + "8");
}
} else {
inputScreen.setText("8");
addToDisplay = true;
}
go = true;
});
btn9 = createButton("9", columns[2], rows[2]);
btn9.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("9");
} else {
inputScreen.setText(inputScreen.getText() + "9");
}
} else {
inputScreen.setText("9");
addToDisplay = true;
}
go = true;
});
btnMul = createButton("*", columns[3], rows[2]);
btnMul.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '*';
go = false;
addToDisplay = false;
} else {
selectedOperator = '*';
}
});
btn4 = createButton("4", columns[0], rows[3]);
btn4.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("4");
} else {
inputScreen.setText(inputScreen.getText() + "4");
}
} else {
inputScreen.setText("4");
addToDisplay = true;
}
go = true;
});
btn5 = createButton("5", columns[1], rows[3]);
btn5.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("5");
} else {
inputScreen.setText(inputScreen.getText() + "5");
}
} else {
inputScreen.setText("5");
addToDisplay = true;
}
go = true;
});
btn6 = createButton("6", columns[2], rows[3]);
btn6.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("6");
} else {
inputScreen.setText(inputScreen.getText() + "6");
}
} else {
inputScreen.setText("6");
addToDisplay = true;
}
go = true;
});
btnSub = createButton("-", columns[3], rows[3]);
btnSub.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '-';
go = false;
addToDisplay = false;
} else {
selectedOperator = '-';
}
});
btn1 = createButton("1", columns[0], rows[4]);
btn1.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("1");
} else {
inputScreen.setText(inputScreen.getText() + "1");
}
} else {
inputScreen.setText("1");
addToDisplay = true;
}
go = true;
});
btn2 = createButton("2", columns[1], rows[4]);
btn2.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("2");
} else {
inputScreen.setText(inputScreen.getText() + "2");
}
} else {
inputScreen.setText("2");
addToDisplay = true;
}
go = true;
});
btn3 = createButton("3", columns[2], rows[4]);
btn3.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("3");
} else {
inputScreen.setText(inputScreen.getText() + "3");
}
} else {
inputScreen.setText("3");
addToDisplay = true;
}
go = true;
});
btnAdd = createButton("+", columns[3], rows[4]);
btnAdd.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '+';
go = false;
addToDisplay = false;
} else {
selectedOperator = '+';
}
});
btnPoint = createButton(".", columns[0], rows[5]);
btnPoint.addActionListener(event -> {
if (addToDisplay) {
if (!inputScreen.getText().contains(".")) {
inputScreen.setText(inputScreen.getText() + ".");
}
} else {
inputScreen.setText("0.");
addToDisplay = true;
}
go = true;
});
btn0 = createButton("0", columns[1], rows[5]);
btn0.addActionListener(event -> {
if (addToDisplay) {
if (Pattern.matches("[0]*", inputScreen.getText())) {
inputScreen.setText("0");
} else {
inputScreen.setText(inputScreen.getText() + "0");
}
} else {
inputScreen.setText("0");
addToDisplay = true;
}
go = true;
});
btnEqual = createButton("=", columns[2], rows[5]);
btnEqual.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '=';
addToDisplay = false;
}
});
btnEqual.setSize(2 * BUTTON_WIDTH + 10, BUTTON_HEIGHT);
btnRoot = createButton("", columns[4], rows[1]);
btnRoot.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = Math.sqrt(Double.parseDouble(inputScreen.getText()));
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '√';
addToDisplay = false;
}
});
btnRoot.setVisible(false);
btnPower = createButton("pow", columns[4], rows[2]);
btnPower.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = calculate(typedValue, Double.parseDouble(inputScreen.getText()), selectedOperator);
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = '^';
go = false;
addToDisplay = false;
} else {
selectedOperator = '^';
}
});
btnPower.setFont(new Font("Comic Sans MS", Font.PLAIN, 24));
btnPower.setVisible(false);
btnLog = createButton("ln", columns[4], rows[3]);
btnLog.addActionListener(event -> {
if (!Pattern.matches(DOUBLE_OR_NUMBER_REGEX, inputScreen.getText()))
return;
if (go) {
typedValue = Math.log(Double.parseDouble(inputScreen.getText()));
if (Pattern.matches("[-]?[\\d]+[.][0]*", String.valueOf(typedValue))) {
inputScreen.setText(String.valueOf((int) typedValue));
} else {
inputScreen.setText(String.valueOf(typedValue));
}
selectedOperator = 'l';
addToDisplay = false;
}
});
btnLog.setVisible(false);
}
private JComboBox<String> createComboBox(String[] items, int x, int y, String toolTip) {
JComboBox<String> combo = new JComboBox<>(items);
combo.setBounds(x, y, 140, 25);
combo.setToolTipText(toolTip);
combo.setCursor(new Cursor(Cursor.HAND_CURSOR));
window.add(combo);
return combo;
}
private JButton createButton(String label, int x, int y) {
JButton btn = new JButton(label);
btn.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
btn.setFont(new Font("Comic Sans MS", Font.PLAIN, 28));
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
btn.setFocusable(false);
window.add(btn);
return btn;
}
private void applyTheme(Theme theme) {
window.getContentPane().setBackground(hex2Color(theme.getApplicationBackground()));
comboCalculatorType.setForeground(hex2Color(theme.getTextColor()));
comboTheme.setForeground(hex2Color(theme.getTextColor()));
inputScreen.setForeground(hex2Color(theme.getTextColor()));
btn0.setForeground(hex2Color(theme.getTextColor()));
btn1.setForeground(hex2Color(theme.getTextColor()));
btn2.setForeground(hex2Color(theme.getTextColor()));
btn3.setForeground(hex2Color(theme.getTextColor()));
btn4.setForeground(hex2Color(theme.getTextColor()));
btn5.setForeground(hex2Color(theme.getTextColor()));
btn6.setForeground(hex2Color(theme.getTextColor()));
btn7.setForeground(hex2Color(theme.getTextColor()));
btn8.setForeground(hex2Color(theme.getTextColor()));
btn9.setForeground(hex2Color(theme.getTextColor()));
btnPoint.setForeground(hex2Color(theme.getTextColor()));
btnC.setForeground(hex2Color(theme.getTextColor()));
btnBack.setForeground(hex2Color(theme.getTextColor()));
btnMod.setForeground(hex2Color(theme.getTextColor()));
btnDiv.setForeground(hex2Color(theme.getTextColor()));
btnMul.setForeground(hex2Color(theme.getTextColor()));
btnSub.setForeground(hex2Color(theme.getTextColor()));
btnAdd.setForeground(hex2Color(theme.getTextColor()));
btnRoot.setForeground(hex2Color(theme.getTextColor()));
btnLog.setForeground(hex2Color(theme.getTextColor()));
btnPower.setForeground(hex2Color(theme.getTextColor()));
btnEqual.setForeground(hex2Color(theme.getBtnEqualTextColor()));
comboCalculatorType.setBackground(hex2Color(theme.getApplicationBackground()));
comboTheme.setBackground(hex2Color(theme.getApplicationBackground()));
inputScreen.setBackground(hex2Color(theme.getApplicationBackground()));
btn0.setBackground(hex2Color(theme.getNumbersBackground()));
btn1.setBackground(hex2Color(theme.getNumbersBackground()));
btn2.setBackground(hex2Color(theme.getNumbersBackground()));
btn3.setBackground(hex2Color(theme.getNumbersBackground()));
btn4.setBackground(hex2Color(theme.getNumbersBackground()));
btn5.setBackground(hex2Color(theme.getNumbersBackground()));
btn6.setBackground(hex2Color(theme.getNumbersBackground()));
btn7.setBackground(hex2Color(theme.getNumbersBackground()));
btn8.setBackground(hex2Color(theme.getNumbersBackground()));
btn9.setBackground(hex2Color(theme.getNumbersBackground()));
btnPoint.setBackground(hex2Color(theme.getNumbersBackground()));
btnC.setBackground(hex2Color(theme.getOperatorBackground()));
btnBack.setBackground(hex2Color(theme.getOperatorBackground()));
btnMod.setBackground(hex2Color(theme.getOperatorBackground()));
btnDiv.setBackground(hex2Color(theme.getOperatorBackground()));
btnMul.setBackground(hex2Color(theme.getOperatorBackground()));
btnSub.setBackground(hex2Color(theme.getOperatorBackground()));
btnAdd.setBackground(hex2Color(theme.getOperatorBackground()));
btnRoot.setBackground(hex2Color(theme.getOperatorBackground()));
btnLog.setBackground(hex2Color(theme.getOperatorBackground()));
btnPower.setBackground(hex2Color(theme.getOperatorBackground()));
btnEqual.setBackground(hex2Color(theme.getBtnEqualBackground()));
}
}

View File

@ -0,0 +1,138 @@
// @ts-ignore
import { Pattern } from './types/Pattern'; // @ts-ignore
import { Match } from './types/Match'; // @ts-ignore
import * as symbols from './internals/symbols'; // @ts-ignore
import { matchPattern } from './internals/helpers'; // @ts-ignore
type MatchState<output> =
| { matched: true; value: output }
| { matched: false; value: undefined };
const unmatched: MatchState<never> = {
matched: false,
value: undefined,
};
/**
* `match` creates a **pattern matching expression**.
* * Use `.with(pattern, handler)` to pattern match on the input.
* * Use `.exhaustive()` or `.otherwise(() => defaultValue)` to end the expression and get the result.
*
* [Read the documentation for `match` on GitHub](https://github.com/gvergnaud/ts-pattern#match)
*
* @example
* declare let input: "A" | "B";
*
* return match(input)
* .with("A", () => "It's an A!")
* .with("B", () => "It's a B!")
* .exhaustive();
*
*/
export function match<const input, output = symbols.unset>(
value: input
): Match<input, output> {
return new MatchExpression(value, unmatched) as any;
}
/**
* This class represents a match expression. It follows the
* builder pattern, we chain methods to add features to the expression
* until we call `.exhaustive`, `.otherwise` or the unsafe `.run`
* method to execute it.
*
* The types of this class aren't public, the public type definition
* can be found in src/types/Match.ts.
*/
class MatchExpression<input, output> {
constructor(private input: input, private state: MatchState<output>) {}
with(...args: any[]): MatchExpression<input, output> {
if (this.state.matched) return this;
const handler: (selection: unknown, value: input) => output =
args[args.length - 1];
const patterns: Pattern<input>[] = [args[0]];
let predicate: ((value: input) => unknown) | undefined = undefined;
if (args.length === 3 && typeof args[1] === 'function') {
// case with guard as second argument
patterns.push(args[0]);
predicate = args[1];
} else if (args.length > 2) {
// case with several patterns
patterns.push(...args.slice(1, args.length - 1));
}
let hasSelections = false;
let selected: Record<string, unknown> = {};
const select = (key: string, value: unknown) => {
hasSelections = true;
selected[key] = value;
};
const matched =
patterns.some((pattern) => matchPattern(pattern, this.input, select)) &&
(predicate ? Boolean(predicate(this.input)) : true);
const selections = hasSelections
? symbols.anonymousSelectKey in selected
? selected[symbols.anonymousSelectKey]
: selected
: this.input;
const state = matched
? {
matched: true as const,
value: handler(selections, this.input),
}
: unmatched;
return new MatchExpression(this.input, state);
}
when(
predicate: (value: input) => unknown,
handler: (selection: input, value: input) => output
): MatchExpression<input, output> {
if (this.state.matched) return this;
const matched = Boolean(predicate(this.input));
return new MatchExpression<input, output>(
this.input,
matched
? { matched: true, value: handler(this.input, this.input) }
: unmatched
);
}
otherwise(handler: (value: input) => output): output {
if (this.state.matched) return this.state.value;
return handler(this.input);
}
exhaustive(): output {
return this.run();
}
run(): output {
if (this.state.matched) return this.state.value;
let displayedValue;
try {
displayedValue = JSON.stringify(this.input);
} catch (e) {
displayedValue = this.input;
}
throw new Error(
`Pattern matching error: no pattern matches value ${displayedValue}`
);
}
returnType() {
return this;
}
}