@pheem49/mint 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +0 -17
  4. package/Release_Note.md +92 -58
  5. package/bin/mint +0 -0
  6. package/crates/mint-cli/src/agent.rs +100 -6
  7. package/crates/mint-cli/src/main.rs +166 -52
  8. package/crates/mint-core/src/agent_loop.rs +1 -0
  9. package/crates/mint-core/src/api_server.rs +55 -2
  10. package/crates/mint-core/src/browser.rs +55 -0
  11. package/crates/mint-core/src/channels.rs +1 -0
  12. package/crates/mint-core/src/chat.rs +3 -0
  13. package/crates/mint-core/src/config.rs +61 -0
  14. package/crates/mint-core/src/lib.rs +14 -0
  15. package/crates/mint-core/src/orchestration.rs +180 -11
  16. package/crates/mint-core/src/tts.rs +3 -1
  17. package/flatpak/com.pheem49.mint.desktop +9 -0
  18. package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
  19. package/flatpak/com.pheem49.mint.yaml +52 -0
  20. package/package.json +1 -1
  21. package/shared-modules/.github/workflows/ci.yml +120 -0
  22. package/shared-modules/.github/workflows/update.yaml +28 -0
  23. package/shared-modules/CODEOWNERS +23 -0
  24. package/shared-modules/README.md +79 -0
  25. package/shared-modules/SDL/README.md +3 -0
  26. package/shared-modules/SDL/SDL-1.2.15.json +51 -0
  27. package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
  28. package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
  29. package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
  30. package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
  31. package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
  32. package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
  33. package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
  34. package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
  35. package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
  36. package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
  37. package/shared-modules/SDL/sdl12-compat.json +22 -0
  38. package/shared-modules/SDL2/SDL2_image.json +27 -0
  39. package/shared-modules/SDL2/SDL2_ttf.json +29 -0
  40. package/shared-modules/cld2/CMakeLists.txt +157 -0
  41. package/shared-modules/cld2/cld2.json +24 -0
  42. package/shared-modules/clutter/clutter.json +72 -0
  43. package/shared-modules/dbus-glib/dbus-glib.json +29 -0
  44. package/shared-modules/glew/glew.json +36 -0
  45. package/shared-modules/glu/glu-9.json +12 -0
  46. package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
  47. package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
  48. package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
  49. package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
  50. package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
  51. package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
  52. package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
  53. package/shared-modules/gtk2/gtk2.json +120 -0
  54. package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
  55. package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
  56. package/shared-modules/gtk2/readme.md +10 -0
  57. package/shared-modules/intltool/intltool-0.51.json +20 -0
  58. package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
  59. package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
  60. package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
  61. package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
  62. package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
  63. package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
  64. package/shared-modules/lame/lame-3.99.5.json +32 -0
  65. package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
  66. package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
  67. package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
  68. package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
  69. package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
  70. package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
  71. package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
  72. package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
  73. package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
  74. package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
  75. package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
  76. package/shared-modules/libcanberra/libcanberra.json +44 -0
  77. package/shared-modules/libmad/libmad.json +19 -0
  78. package/shared-modules/libsecret/libsecret.json +23 -0
  79. package/shared-modules/libsoup/libsoup-2.4.json +14 -0
  80. package/shared-modules/libusb/libusb.json +27 -0
  81. package/shared-modules/linux-audio/dssi.json +31 -0
  82. package/shared-modules/linux-audio/fftw3f-static.json +44 -0
  83. package/shared-modules/linux-audio/fftw3f.json +42 -0
  84. package/shared-modules/linux-audio/fluidsynth2-static.json +30 -0
  85. package/shared-modules/linux-audio/fluidsynth2.json +19 -0
  86. package/shared-modules/linux-audio/ladspa.json +26 -0
  87. package/shared-modules/linux-audio/lash.json +35 -0
  88. package/shared-modules/linux-audio/libinstpatch.json +16 -0
  89. package/shared-modules/linux-audio/liblo-static.json +22 -0
  90. package/shared-modules/linux-audio/liblo.json +17 -0
  91. package/shared-modules/linux-audio/lilv-static.json +94 -0
  92. package/shared-modules/linux-audio/lilv.json +82 -0
  93. package/shared-modules/linux-audio/lrdf.json +57 -0
  94. package/shared-modules/linux-audio/lv2.json +21 -0
  95. package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
  96. package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
  97. package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
  98. package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
  99. package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
  100. package/shared-modules/linux-audio/stk.json +28 -0
  101. package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
  102. package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
  103. package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
  104. package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
  105. package/shared-modules/lua5.3/lua.pc.in +8 -0
  106. package/shared-modules/lua5.3/luaconf.patch +47 -0
  107. package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
  108. package/shared-modules/lua5.4/lua-5.4.json +41 -0
  109. package/shared-modules/lua5.4/lua.pc.in +8 -0
  110. package/shared-modules/lua5.4/luaconf.patch +34 -0
  111. package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
  112. package/shared-modules/luajit/luajit.json +30 -0
  113. package/shared-modules/lzo/lzo.json +27 -0
  114. package/shared-modules/mac/mac.json +18 -0
  115. package/shared-modules/physfs/physfs.json +26 -0
  116. package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
  117. package/shared-modules/pygame/cython-fixes.patch +34 -0
  118. package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
  119. package/shared-modules/pygame/glib.patch +25 -0
  120. package/shared-modules/pygame/pygame1-python2.json +157 -0
  121. package/shared-modules/pygtk/pygtk-c99.patch +15 -0
  122. package/shared-modules/pygtk/pygtk.json +117 -0
  123. package/shared-modules/pygtk/pygtk.patch +39 -0
  124. package/shared-modules/python2.7/python-2.7.json +48 -0
  125. package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
  126. package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
  127. package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
  128. package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
  129. package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
  130. package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
  131. package/shared-modules/update-checker.sh +50 -0
  132. package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
  133. package/shared-modules/vorbisgain/0002-errno.patch +33 -0
  134. package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
  135. package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
  136. package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
  137. package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
  138. package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
  139. package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
  140. package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
  141. package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
  142. package/shared-modules/vorbisgain/README.md +23 -0
  143. package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
  144. package/src/renderer/shared/agentProgress.ts +145 -0
  145. package/src/renderer/shared/calculator.ts +52 -0
  146. package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
  147. package/src/renderer/shared/components/AgentActivityTable.tsx +66 -0
  148. package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
  149. package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
  150. package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
  151. package/src/renderer/shared/constants/models.ts +91 -0
  152. package/src/renderer/shared/platform.ts +82 -0
  153. package/src/renderer/shared/types.ts +140 -0
  154. package/src/renderer/shared/utils/agentActivity.ts +187 -0
  155. package/src/renderer/shared/utils/approval.ts +1 -0
  156. package/src/renderer/shared/utils/approval.tsx +64 -0
  157. package/src/renderer/shared/utils/markdown.tsx +347 -0
  158. package/src/renderer/shared/utils/providers.ts +36 -0
  159. package/src/renderer/shared/utils/speech.ts +216 -0
  160. package/src/renderer/shared/utils/ui.ts +147 -0
  161. package/src/renderer/src/agentProgress.ts +11 -39
  162. package/src/renderer/src/calculator.ts +3 -46
  163. package/src/renderer/src/components/ChatPanel.tsx +194 -1303
  164. package/src/renderer/src/components/MintDashboard.tsx +23 -169
  165. package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
  166. package/src/renderer/src/components/Settings/AgentsTab.tsx +362 -0
  167. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
  168. package/src/renderer/src/components/SettingsWindow.tsx +53 -3
  169. package/src/renderer/src/css/styles.css +84 -0
  170. package/src/renderer/src/tauri.ts +128 -143
  171. package/src/renderer/src-web/agentProgress.ts +11 -39
  172. package/src/renderer/src-web/calculator.ts +3 -46
  173. package/src/renderer/src-web/components/ChatPanel.tsx +194 -1301
  174. package/src/renderer/src-web/components/MintDashboard.tsx +23 -169
  175. package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
  176. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +362 -0
  177. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
  178. package/src/renderer/src-web/components/SettingsWindow.tsx +53 -3
  179. package/src/renderer/src-web/css/styles.css +84 -0
  180. package/src/renderer/src-web/tauri.ts +180 -102
  181. package/src-tauri/src/lib.rs +137 -33
  182. package/src-tauri/src/webhooks.rs +1 -0
  183. package/src-tauri/src/workflows.rs +1 -0
  184. package/src-tauri/tauri.conf.json +1 -1
@@ -0,0 +1,147 @@
1
+ /**
2
+ * shared/utils/ui.ts
3
+ * Shared UI and theme helper functions.
4
+ * Used by both Desktop and Web components.
5
+ */
6
+
7
+ export function numericSetting(value: unknown, fallback: number): number {
8
+ const numeric = Number(value)
9
+ return Number.isFinite(numeric) ? numeric : fallback
10
+ }
11
+
12
+ export function errorMessage(reason: unknown): string {
13
+ return reason instanceof Error ? reason.message : String(reason)
14
+ }
15
+
16
+ export function readImage(file: File): Promise<string> {
17
+ return new Promise((resolve, reject) => {
18
+ const reader = new FileReader()
19
+ reader.onload = () => resolve(String(reader.result))
20
+ reader.onerror = () => reject(reader.error ?? new Error('Unable to read image'))
21
+ reader.readAsDataURL(file)
22
+ })
23
+ }
24
+
25
+ export function readDocument(file: File): Promise<string> {
26
+ return new Promise((resolve, reject) => {
27
+ const reader = new FileReader()
28
+ reader.onload = () => resolve(String(reader.result))
29
+ reader.onerror = () => reject(reader.error ?? new Error('Unable to read document'))
30
+ reader.readAsDataURL(file)
31
+ })
32
+ }
33
+
34
+ export async function createTrimmedImagePreview(dataUri: string): Promise<string> {
35
+ const image = await new Promise<HTMLImageElement>((resolve, reject) => {
36
+ const nextImage = new Image()
37
+ nextImage.onload = () => resolve(nextImage)
38
+ nextImage.onerror = () => reject(new Error('Unable to prepare image preview'))
39
+ nextImage.src = dataUri
40
+ })
41
+
42
+ const canvas = document.createElement('canvas')
43
+ canvas.width = image.naturalWidth || image.width
44
+ canvas.height = image.naturalHeight || image.height
45
+ const context = canvas.getContext('2d', { willReadFrequently: true })
46
+ if (!context || canvas.width === 0 || canvas.height === 0) return dataUri
47
+
48
+ context.drawImage(image, 0, 0)
49
+ const pixels = context.getImageData(0, 0, canvas.width, canvas.height)
50
+ let minX = canvas.width
51
+ let minY = canvas.height
52
+ let maxX = -1
53
+ let maxY = -1
54
+
55
+ for (let y = 0; y < canvas.height; y += 1) {
56
+ for (let x = 0; x < canvas.width; x += 1) {
57
+ const alpha = pixels.data[(y * canvas.width + x) * 4 + 3]
58
+ if (alpha > 12) {
59
+ minX = Math.min(minX, x)
60
+ minY = Math.min(minY, y)
61
+ maxX = Math.max(maxX, x)
62
+ maxY = Math.max(maxY, y)
63
+ }
64
+ }
65
+ }
66
+
67
+ if (maxX < minX || maxY < minY) return dataUri
68
+
69
+ const padding = 8
70
+ const sx = Math.max(0, minX - padding)
71
+ const sy = Math.max(0, minY - padding)
72
+ const sw = Math.min(canvas.width - sx, maxX - minX + 1 + padding * 2)
73
+ const sh = Math.min(canvas.height - sy, maxY - minY + 1 + padding * 2)
74
+
75
+ if (sw >= canvas.width * 0.92 && sh >= canvas.height * 0.92) return dataUri
76
+
77
+ const previewCanvas = document.createElement('canvas')
78
+ previewCanvas.width = sw
79
+ previewCanvas.height = sh
80
+ const previewContext = previewCanvas.getContext('2d')
81
+ if (!previewContext) return dataUri
82
+ previewContext.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh)
83
+ return previewCanvas.toDataURL('image/png')
84
+ }
85
+
86
+ export const lightenColor = (hex: string, amount: number): string => {
87
+ const clean = hex.replace('#', '')
88
+ if (clean.length !== 6) return hex
89
+ const num = parseInt(clean, 16)
90
+ const r = Math.min(255, (num >> 16) + amount)
91
+ const g = Math.min(255, ((num >> 8) & 0x00FF) + amount)
92
+ const b = Math.min(255, (num & 0x0000FF) + amount)
93
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
94
+ }
95
+
96
+ export const hexToRgb = (hex: string): { r: number; g: number; b: number } => {
97
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
98
+ return result ? {
99
+ r: parseInt(result[1], 16),
100
+ g: parseInt(result[2], 16),
101
+ b: parseInt(result[3], 16),
102
+ } : { r: 15, g: 23, b: 42 }
103
+ }
104
+
105
+ export const applyThemeStyles = (cfg: any): void => {
106
+ const theme = cfg.theme || 'dark'
107
+ const accentColor = cfg.accentColor || '#4f83e6'
108
+ const systemTextColor = cfg.systemTextColor || '#f8fafc'
109
+
110
+ document.documentElement.setAttribute('data-theme', theme)
111
+ document.documentElement.style.setProperty('--accent', accentColor)
112
+ document.documentElement.style.setProperty('--accent-hover', lightenColor(accentColor, 20))
113
+ document.documentElement.style.setProperty('--text-main', systemTextColor)
114
+ document.documentElement.style.setProperty('--glass-blur', cfg.glassBlur || 'blur(16px)')
115
+ document.body.style.fontFamily = cfg.fontFamily || "'Outfit', sans-serif"
116
+ document.documentElement.style.fontSize = cfg.fontSize || '18px'
117
+
118
+ if (theme === 'custom') {
119
+ if (cfg.customBgStart && cfg.customBgEnd) {
120
+ document.documentElement.style.setProperty('--bg-color', cfg.customBgStart)
121
+ document.documentElement.style.setProperty('--bg-gradient', `linear-gradient(135deg, ${cfg.customBgStart} 0%, ${cfg.customBgEnd} 100%)`)
122
+ }
123
+ if (cfg.customPanelBg) {
124
+ const rgb = hexToRgb(cfg.customPanelBg)
125
+ document.documentElement.style.setProperty('--panel-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
126
+ document.documentElement.style.setProperty('--panel-raised', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.82)`)
127
+ document.documentElement.style.setProperty('--panel-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.46)`)
128
+ document.documentElement.style.setProperty('--chrome-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.88)`)
129
+ document.documentElement.style.setProperty('--surface-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.62)`)
130
+ document.documentElement.style.setProperty('--surface-strong', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.86)`)
131
+ document.documentElement.style.setProperty('--input-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.72)`)
132
+ }
133
+ return
134
+ }
135
+
136
+ ;[
137
+ '--bg-color',
138
+ '--bg-gradient',
139
+ '--panel-bg',
140
+ '--panel-raised',
141
+ '--panel-soft',
142
+ '--chrome-bg',
143
+ '--surface-bg',
144
+ '--surface-strong',
145
+ '--input-bg',
146
+ ].forEach((prop) => document.documentElement.style.removeProperty(prop))
147
+ }
@@ -1,39 +1,11 @@
1
- import type { AgentProgress, InteractionMemory } from './tauri'
2
-
3
- export function thoughtsFrom(progress: AgentProgress[]): string[] {
4
- return progress
5
- .filter((event) => event.type === 'Thought')
6
- .map((event) => (event as Extract<AgentProgress, { type: 'Thought' }>).data.thought)
7
- .filter(Boolean)
8
- }
9
-
10
- export function trimAgentProgress(progress: AgentProgress[], maxNonThought = 20): AgentProgress[] {
11
- if (progress.length === 0) return progress
12
- const keptNonThought = new Set(
13
- progress.filter((event) => event.type !== 'Thought').slice(-maxNonThought),
14
- )
15
- return progress.filter((event) => event.type === 'Thought' || keptNonThought.has(event))
16
- }
17
-
18
- export function hasAgentToolActivity(progress: AgentProgress[]): boolean {
19
- return progress.some((event) => event.type === 'ToolStart' || event.type === 'ToolEnd')
20
- }
21
-
22
- export function mergeActivitySnapshots(
23
- current: Record<string, AgentProgress[]>,
24
- interactions: InteractionMemory[],
25
- ): Record<string, AgentProgress[]> {
26
- const merged = { ...current }
27
- for (const interaction of interactions) {
28
- const key = String(interaction.id)
29
- if (interaction.agentActivity?.length && !merged[key]) {
30
- merged[key] = interaction.agentActivity
31
- }
32
- }
33
- return merged
34
- }
35
-
36
- export function parseAgentActivity(value: unknown): AgentProgress[] | undefined {
37
- if (!Array.isArray(value)) return undefined
38
- return value as AgentProgress[]
39
- }
1
+ // Re-export everything from the shared module — do NOT add logic here.
2
+ // Edit src/renderer/shared/agentProgress.ts instead.
3
+ export type { AgentProgress, InteractionMemory } from '../shared/agentProgress'
4
+ export {
5
+ thoughtsFrom,
6
+ trimAgentProgress,
7
+ hasAgentToolActivity,
8
+ mergeActivitySnapshots,
9
+ parseAgentActivity,
10
+ parseFileChangesFromProgress,
11
+ } from '../shared/agentProgress'
@@ -1,47 +1,4 @@
1
- export function evaluateArithmetic(source: string): number {
2
- const tokens = source.replace(/\s+/g, '')
3
- let index = 0
1
+ // Re-export from the shared module — do NOT add logic here.
2
+ // Edit src/renderer/shared/calculator.ts instead.
3
+ export { evaluateArithmetic } from '../shared/calculator'
4
4
 
5
- function parseExpression(): number {
6
- let value = parseTerm()
7
- while (tokens[index] === '+' || tokens[index] === '-') {
8
- const operator = tokens[index++]
9
- const right = parseTerm()
10
- value = operator === '+' ? value + right : value - right
11
- }
12
- return value
13
- }
14
-
15
- function parseTerm(): number {
16
- let value = parseFactor()
17
- while (tokens[index] === '*' || tokens[index] === '/') {
18
- const operator = tokens[index++]
19
- const right = parseFactor()
20
- value = operator === '*' ? value * right : value / right
21
- }
22
- return value
23
- }
24
-
25
- function parseFactor(): number {
26
- if (tokens[index] === '+' || tokens[index] === '-') {
27
- return tokens[index++] === '-' ? -parseFactor() : parseFactor()
28
- }
29
- if (tokens[index] === '(') {
30
- index += 1
31
- const value = parseExpression()
32
- if (tokens[index++] !== ')') throw new Error('missing closing parenthesis')
33
- return value
34
- }
35
- const match = tokens.slice(index).match(/^(?:\d+(?:\.\d*)?|\.\d+)/)
36
- if (!match) throw new Error('expected a number')
37
- index += match[0].length
38
- return Number(match[0])
39
- }
40
-
41
- if (!tokens) throw new Error('expression is empty')
42
- const result = parseExpression()
43
- if (index !== tokens.length || !Number.isFinite(result)) {
44
- throw new Error('invalid arithmetic expression')
45
- }
46
- return result
47
- }