diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml new file mode 100644 index 00000000..f2805468 --- /dev/null +++ b/.github/workflows/webpack.yml @@ -0,0 +1,28 @@ +name: NodeJS with Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + npm test diff --git a/.gitignore b/.gitignore index 4509cc46..acb05fa7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules dist .tmp bower_components +.aider* diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index e0e07301..00000000 --- a/.jshintrc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "node": true, - "browser": true, - "esnext": true, - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "immed": true, - "indent": 2, - "latedef": true, - "newcap": true, - "noarg": true, - "quotmark": "single", - "undef": true, - "unused": true, - "strict": true, - "trailing": true, - "smarttabs": true, - "jquery": true -} diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 37a480af..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,383 +0,0 @@ -// Generated on 2014-12-16 using -// generator-webapp 0.5.1 -'use strict'; - -// # Globbing -// for performance reasons we're only matching one level down: -// 'test/spec/{,*/}*.js' -// If you want to recursively match all subfolders, use: -// 'test/spec/**/*.js' - -module.exports = function (grunt) { - - // Time how long tasks take. Can help when optimizing build times - require('time-grunt')(grunt); - - // Load grunt tasks automatically - require('load-grunt-tasks')(grunt); - - // Configurable paths - var config = { - app: 'app', - dist: 'dist' - }; - - // Define the configuration for all the tasks - grunt.initConfig({ - - // Project settings - config: config, - - // Watches files for changes and runs tasks based on the changed files - watch: { - bower: { - files: ['bower.json'], - tasks: ['wiredep'] - }, - js: { - files: ['<%= config.app %>/scripts/{,*/}*.js'], - tasks: ['jshint'], - options: { - livereload: true - } - }, - jstest: { - files: ['test/spec/{,*/}*.js'], - tasks: ['test:watch'] - }, - gruntfile: { - files: ['Gruntfile.js'] - }, - styles: { - files: ['<%= config.app %>/styles/{,*/}*.css'], - tasks: ['newer:copy:styles', 'autoprefixer'] - }, - livereload: { - options: { - livereload: '<%= connect.options.livereload %>' - }, - files: [ - '<%= config.app %>/{,*/}*.html', - '.tmp/styles/{,*/}*.css', - '<%= config.app %>/images/{,*/}*' - ] - } - }, - - // The actual grunt server settings - connect: { - options: { - port: 9000, - open: true, - livereload: 35729, - // Change this to '0.0.0.0' to access the server from outside - hostname: 'localhost' - }, - livereload: { - options: { - middleware: function(connect) { - return [ - connect.static('.tmp'), - connect().use('/bower_components', connect.static('./bower_components')), - connect.static(config.app) - ]; - } - } - }, - test: { - options: { - open: false, - port: 9001, - middleware: function(connect) { - return [ - connect.static('.tmp'), - connect.static('test'), - connect().use('/bower_components', connect.static('./bower_components')), - connect.static(config.app) - ]; - } - } - }, - dist: { - options: { - base: '<%= config.dist %>', - livereload: false - } - } - }, - - // Empties folders to start fresh - clean: { - dist: { - files: [{ - dot: true, - src: [ - '.tmp', - '<%= config.dist %>/*', - '!<%= config.dist %>/.git*' - ] - }] - }, - server: '.tmp' - }, - - // Make sure code styles are up to par and there are no obvious mistakes - jshint: { - options: { - jshintrc: '.jshintrc', - reporter: require('jshint-stylish') - }, - all: [ - 'Gruntfile.js', - '<%= config.app %>/scripts/{,*/}*.js', - '!<%= config.app %>/scripts/vendor/*', - 'test/spec/{,*/}*.js' - ] - }, - - // Mocha testing framework configuration options - mocha: { - all: { - options: { - run: true, - urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html'] - } - } - }, - - // Add vendor prefixed styles - autoprefixer: { - options: { - browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1'] - }, - dist: { - files: [{ - expand: true, - cwd: '.tmp/styles/', - src: '{,*/}*.css', - dest: '.tmp/styles/' - }] - } - }, - - // Automatically inject Bower components into the HTML file - wiredep: { - app: { - ignorePath: /^\/|\.\.\//, - src: ['<%= config.app %>/index.html'] - } - }, - - // Renames files for browser caching purposes - rev: { - dist: { - files: { - src: [ - '<%= config.dist %>/scripts/{,*/}*.js', - '<%= config.dist %>/styles/{,*/}*.css', - '<%= config.dist %>/images/{,*/}*.*', - '<%= config.dist %>/styles/fonts/{,*/}*.*', - '<%= config.dist %>/*.{ico,png}' - ] - } - } - }, - - // Reads HTML for usemin blocks to enable smart builds that automatically - // concat, minify and revision files. Creates configurations in memory so - // additional tasks can operate on them - useminPrepare: { - options: { - dest: '<%= config.dist %>' - }, - html: '<%= config.app %>/index.html' - }, - - // Performs rewrites based on rev and the useminPrepare configuration - usemin: { - options: { - assetsDirs: [ - '<%= config.dist %>', - '<%= config.dist %>/images', - '<%= config.dist %>/styles' - ] - }, - html: ['<%= config.dist %>/{,*/}*.html'], - css: ['<%= config.dist %>/styles/{,*/}*.css'] - }, - - // The following *-min tasks produce minified files in the dist folder - imagemin: { - dist: { - files: [{ - expand: true, - cwd: '<%= config.app %>/images', - src: '{,*/}*.{gif,jpeg,jpg,png}', - dest: '<%= config.dist %>/images' - }] - } - }, - - svgmin: { - dist: { - files: [{ - expand: true, - cwd: '<%= config.app %>/images', - src: '{,*/}*.svg', - dest: '<%= config.dist %>/images' - }] - } - }, - - htmlmin: { - dist: { - options: { - collapseBooleanAttributes: true, - collapseWhitespace: true, - conservativeCollapse: true, - removeAttributeQuotes: true, - removeCommentsFromCDATA: true, - removeEmptyAttributes: true, - removeOptionalTags: true, - removeRedundantAttributes: true, - useShortDoctype: true - }, - files: [{ - expand: true, - cwd: '<%= config.dist %>', - src: '{,*/}*.html', - dest: '<%= config.dist %>' - }] - } - }, - - // By default, your `index.html`'s will take care - // of minification. These next options are pre-configured if you do not - // wish to use the Usemin blocks. - // cssmin: { - // dist: { - // files: { - // '<%= config.dist %>/styles/main.css': [ - // '.tmp/styles/{,*/}*.css', - // '<%= config.app %>/styles/{,*/}*.css' - // ] - // } - // } - // }, - // uglify: { - // dist: { - // files: { - // '<%= config.dist %>/scripts/scripts.js': [ - // '<%= config.dist %>/scripts/scripts.js' - // ] - // } - // } - // }, - // concat: { - // dist: {} - // }, - - // Copies remaining files to places other tasks can use - copy: { - dist: { - files: [{ - expand: true, - dot: true, - cwd: '<%= config.app %>', - dest: '<%= config.dist %>', - src: [ - '*.{ico,png,txt}', - 'images/{,*/}*.webp', - '{,*/}*.html', - 'styles/fonts/{,*/}*.*' - ] - }, { - src: 'node_modules/apache-server-configs/dist/.htaccess', - dest: '<%= config.dist %>/.htaccess' - }] - }, - styles: { - expand: true, - dot: true, - cwd: '<%= config.app %>/styles', - dest: '.tmp/styles/', - src: '{,*/}*.css' - } - }, - - // Run some tasks in parallel to speed up build process - concurrent: { - server: [ - 'copy:styles' - ], - test: [ - 'copy:styles' - ], - dist: [ - 'copy:styles', - 'imagemin', - 'svgmin' - ] - } - }); - - - grunt.registerTask('serve', 'start the server and preview your app, --allow-remote for remote access', function (target) { - if (grunt.option('allow-remote')) { - grunt.config.set('connect.options.hostname', '0.0.0.0'); - } - if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); - } - - grunt.task.run([ - 'clean:server', - 'wiredep', - 'concurrent:server', - 'autoprefixer', - 'connect:livereload', - 'watch' - ]); - }); - - grunt.registerTask('server', function (target) { - grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); - grunt.task.run([target ? ('serve:' + target) : 'serve']); - }); - - grunt.registerTask('test', function (target) { - if (target !== 'watch') { - grunt.task.run([ - 'clean:server', - 'concurrent:test', - 'autoprefixer' - ]); - } - - grunt.task.run([ - 'connect:test', - 'mocha' - ]); - }); - - grunt.registerTask('build', [ - 'clean:dist', - 'wiredep', - 'useminPrepare', - 'concurrent:dist', - 'autoprefixer', - 'concat', - 'cssmin', - 'uglify', - 'copy:dist', - 'rev', - 'usemin', - 'htmlmin' - ]); - - grunt.registerTask('default', [ - 'newer:jshint', - 'test', - 'build' - ]); -}; diff --git a/Gruntfile.js~ b/Gruntfile.js~ deleted file mode 100644 index 37a480af..00000000 --- a/Gruntfile.js~ +++ /dev/null @@ -1,383 +0,0 @@ -// Generated on 2014-12-16 using -// generator-webapp 0.5.1 -'use strict'; - -// # Globbing -// for performance reasons we're only matching one level down: -// 'test/spec/{,*/}*.js' -// If you want to recursively match all subfolders, use: -// 'test/spec/**/*.js' - -module.exports = function (grunt) { - - // Time how long tasks take. Can help when optimizing build times - require('time-grunt')(grunt); - - // Load grunt tasks automatically - require('load-grunt-tasks')(grunt); - - // Configurable paths - var config = { - app: 'app', - dist: 'dist' - }; - - // Define the configuration for all the tasks - grunt.initConfig({ - - // Project settings - config: config, - - // Watches files for changes and runs tasks based on the changed files - watch: { - bower: { - files: ['bower.json'], - tasks: ['wiredep'] - }, - js: { - files: ['<%= config.app %>/scripts/{,*/}*.js'], - tasks: ['jshint'], - options: { - livereload: true - } - }, - jstest: { - files: ['test/spec/{,*/}*.js'], - tasks: ['test:watch'] - }, - gruntfile: { - files: ['Gruntfile.js'] - }, - styles: { - files: ['<%= config.app %>/styles/{,*/}*.css'], - tasks: ['newer:copy:styles', 'autoprefixer'] - }, - livereload: { - options: { - livereload: '<%= connect.options.livereload %>' - }, - files: [ - '<%= config.app %>/{,*/}*.html', - '.tmp/styles/{,*/}*.css', - '<%= config.app %>/images/{,*/}*' - ] - } - }, - - // The actual grunt server settings - connect: { - options: { - port: 9000, - open: true, - livereload: 35729, - // Change this to '0.0.0.0' to access the server from outside - hostname: 'localhost' - }, - livereload: { - options: { - middleware: function(connect) { - return [ - connect.static('.tmp'), - connect().use('/bower_components', connect.static('./bower_components')), - connect.static(config.app) - ]; - } - } - }, - test: { - options: { - open: false, - port: 9001, - middleware: function(connect) { - return [ - connect.static('.tmp'), - connect.static('test'), - connect().use('/bower_components', connect.static('./bower_components')), - connect.static(config.app) - ]; - } - } - }, - dist: { - options: { - base: '<%= config.dist %>', - livereload: false - } - } - }, - - // Empties folders to start fresh - clean: { - dist: { - files: [{ - dot: true, - src: [ - '.tmp', - '<%= config.dist %>/*', - '!<%= config.dist %>/.git*' - ] - }] - }, - server: '.tmp' - }, - - // Make sure code styles are up to par and there are no obvious mistakes - jshint: { - options: { - jshintrc: '.jshintrc', - reporter: require('jshint-stylish') - }, - all: [ - 'Gruntfile.js', - '<%= config.app %>/scripts/{,*/}*.js', - '!<%= config.app %>/scripts/vendor/*', - 'test/spec/{,*/}*.js' - ] - }, - - // Mocha testing framework configuration options - mocha: { - all: { - options: { - run: true, - urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html'] - } - } - }, - - // Add vendor prefixed styles - autoprefixer: { - options: { - browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1'] - }, - dist: { - files: [{ - expand: true, - cwd: '.tmp/styles/', - src: '{,*/}*.css', - dest: '.tmp/styles/' - }] - } - }, - - // Automatically inject Bower components into the HTML file - wiredep: { - app: { - ignorePath: /^\/|\.\.\//, - src: ['<%= config.app %>/index.html'] - } - }, - - // Renames files for browser caching purposes - rev: { - dist: { - files: { - src: [ - '<%= config.dist %>/scripts/{,*/}*.js', - '<%= config.dist %>/styles/{,*/}*.css', - '<%= config.dist %>/images/{,*/}*.*', - '<%= config.dist %>/styles/fonts/{,*/}*.*', - '<%= config.dist %>/*.{ico,png}' - ] - } - } - }, - - // Reads HTML for usemin blocks to enable smart builds that automatically - // concat, minify and revision files. Creates configurations in memory so - // additional tasks can operate on them - useminPrepare: { - options: { - dest: '<%= config.dist %>' - }, - html: '<%= config.app %>/index.html' - }, - - // Performs rewrites based on rev and the useminPrepare configuration - usemin: { - options: { - assetsDirs: [ - '<%= config.dist %>', - '<%= config.dist %>/images', - '<%= config.dist %>/styles' - ] - }, - html: ['<%= config.dist %>/{,*/}*.html'], - css: ['<%= config.dist %>/styles/{,*/}*.css'] - }, - - // The following *-min tasks produce minified files in the dist folder - imagemin: { - dist: { - files: [{ - expand: true, - cwd: '<%= config.app %>/images', - src: '{,*/}*.{gif,jpeg,jpg,png}', - dest: '<%= config.dist %>/images' - }] - } - }, - - svgmin: { - dist: { - files: [{ - expand: true, - cwd: '<%= config.app %>/images', - src: '{,*/}*.svg', - dest: '<%= config.dist %>/images' - }] - } - }, - - htmlmin: { - dist: { - options: { - collapseBooleanAttributes: true, - collapseWhitespace: true, - conservativeCollapse: true, - removeAttributeQuotes: true, - removeCommentsFromCDATA: true, - removeEmptyAttributes: true, - removeOptionalTags: true, - removeRedundantAttributes: true, - useShortDoctype: true - }, - files: [{ - expand: true, - cwd: '<%= config.dist %>', - src: '{,*/}*.html', - dest: '<%= config.dist %>' - }] - } - }, - - // By default, your `index.html`'s will take care - // of minification. These next options are pre-configured if you do not - // wish to use the Usemin blocks. - // cssmin: { - // dist: { - // files: { - // '<%= config.dist %>/styles/main.css': [ - // '.tmp/styles/{,*/}*.css', - // '<%= config.app %>/styles/{,*/}*.css' - // ] - // } - // } - // }, - // uglify: { - // dist: { - // files: { - // '<%= config.dist %>/scripts/scripts.js': [ - // '<%= config.dist %>/scripts/scripts.js' - // ] - // } - // } - // }, - // concat: { - // dist: {} - // }, - - // Copies remaining files to places other tasks can use - copy: { - dist: { - files: [{ - expand: true, - dot: true, - cwd: '<%= config.app %>', - dest: '<%= config.dist %>', - src: [ - '*.{ico,png,txt}', - 'images/{,*/}*.webp', - '{,*/}*.html', - 'styles/fonts/{,*/}*.*' - ] - }, { - src: 'node_modules/apache-server-configs/dist/.htaccess', - dest: '<%= config.dist %>/.htaccess' - }] - }, - styles: { - expand: true, - dot: true, - cwd: '<%= config.app %>/styles', - dest: '.tmp/styles/', - src: '{,*/}*.css' - } - }, - - // Run some tasks in parallel to speed up build process - concurrent: { - server: [ - 'copy:styles' - ], - test: [ - 'copy:styles' - ], - dist: [ - 'copy:styles', - 'imagemin', - 'svgmin' - ] - } - }); - - - grunt.registerTask('serve', 'start the server and preview your app, --allow-remote for remote access', function (target) { - if (grunt.option('allow-remote')) { - grunt.config.set('connect.options.hostname', '0.0.0.0'); - } - if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); - } - - grunt.task.run([ - 'clean:server', - 'wiredep', - 'concurrent:server', - 'autoprefixer', - 'connect:livereload', - 'watch' - ]); - }); - - grunt.registerTask('server', function (target) { - grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); - grunt.task.run([target ? ('serve:' + target) : 'serve']); - }); - - grunt.registerTask('test', function (target) { - if (target !== 'watch') { - grunt.task.run([ - 'clean:server', - 'concurrent:test', - 'autoprefixer' - ]); - } - - grunt.task.run([ - 'connect:test', - 'mocha' - ]); - }); - - grunt.registerTask('build', [ - 'clean:dist', - 'wiredep', - 'useminPrepare', - 'concurrent:dist', - 'autoprefixer', - 'concat', - 'cssmin', - 'uglify', - 'copy:dist', - 'rev', - 'usemin', - 'htmlmin' - ]); - - grunt.registerTask('default', [ - 'newer:jshint', - 'test', - 'build' - ]); -}; diff --git a/README.md b/README.md new file mode 100644 index 00000000..15aa5356 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# 🚀 Skygraph Frontend: Charting the Future of Numerical Analysis! 🌌 + +Welcome, trailblazer! ✨ Are you ready to dive into the fascinating world where mathematics meets cutting-edge web technology? 🌐 Skygraph Frontend is your playground for exploring numerical analysis right in your browser, pushing the boundaries of what's possible on the client-side. No more waiting for servers – unleash the power of computation directly in your hands! 💻💡 + +This project is a journey from traditional web development to a modern, reactive, and highly testable architecture. We're building the future, one equation at a time! 📈✨ + +## 🌟 Badges of Honor (and Rizz!) 🌟 + +[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC) +[![Vue.js](https://img.shields.io/badge/Vue.js-3.x-4FC08D?logo=vue.js&logoColor=white)](https://vuejs.org/) +[![Vite](https://img.shields.io/badge/Vite-5.x-646CFF?logo=vite&logoColor=white)](https://vitejs.dev/) +[![Jest](https://img.shields.io/badge/Tests-Jest-C21325?logo=jest&logoColor=white)](https://jestjs.io/) +[![JavaScript](https://img.shields.io/badge/Language-ES6%2B-F7DF1E?logo=javascript&logoColor=black)](https://developer.mozilla.org/en-US/docs/Web/JavaScript) +[![CSS3](https://img.shields.io/badge/Styling-CSS3-1572B6?logo=css3&logoColor=white)](https://developer.mozilla.org/en-US/docs/Web/CSS) +[![Open Source Love](https://img.shields.io/badge/Open%20Source-%E2%9D%A4-ff69b4.svg)](https://github.com/jvishnefske/skygraph-frontend) +[![Pull Requests Welcome](https://img.shields.io/badge/PRs-Welcome-brightgreen.svg)](https://github.com/jvishnefske/skygraph-frontend/pulls) + +## 🎯 Project Overview + +Skygraph Frontend is an interactive web application designed to demonstrate numerical analysis methods. Imagine typing a complex mathematical equation and seeing its graph, its JavaScript translation, and numerical approximations, all computed instantly in your browser! This project aims to: + +* **Empower Client-Side Computation:** Reduce server load by performing heavy numerical tasks directly in the user's browser. +* **Visualize Complex Math:** Make abstract mathematical concepts tangible through interactive graphing and output. +* **Showcase Modern Web Dev:** Serve as a living example of migrating from legacy tools (jQuery, Grunt, Bower) to a sleek, component-based architecture with Vue.js and Vite. + +## ✨ Features (Current & Upcoming) + +* **Real-time Math Parsing:** Convert mathematical expressions into a parse tree and executable JavaScript. +* **Dynamic MathJax Rendering:** See your equations beautifully typeset in LaTeX. +* **Interactive Input:** Easily input and modify equations. +* **Example Equations:** Quick access to pre-defined examples to get started. +* **Modern Tooling:** Built with Vue 3, Vite, and Jest for a blazing-fast developer experience. + +## 🚀 Getting Started: Your First Leap! + +Ready to contribute or just explore? Follow these simple steps to get Skygraph Frontend up and running on your local machine. + +### Prerequisites + +Make sure you have Node.js and Yarn (or npm) installed. + +* [Node.js](https://nodejs.org/en/) (LTS version recommended) +* [Yarn](https://yarnpkg.com/getting-started/install) (or npm, which comes with Node.js) + +### Installation + +1. **Clone the repository:** + ```bash + git clone https://github.com/jvishnefske/skygraph-frontend.git + cd skygraph-frontend + ``` +2. **Install dependencies:** + ```bash + yarn install + # or npm install + ``` + +### Running the Development Server + +Start the Vite development server to see your changes in real-time. + diff --git a/app/.index.html.swp b/app/.index.html.swp deleted file mode 100644 index 1adfc9d5..00000000 Binary files a/app/.index.html.swp and /dev/null differ diff --git a/app/index.html~ b/app/index.html~ deleted file mode 100755 index 0d22a9c0..00000000 --- a/app/index.html~ +++ /dev/null @@ -1,114 +0,0 @@ - - -client side js numerical analysis - - - - - - - - - - - - - -
-Skygraph home. example html game Pseudorandom Graph Page parser experiment -
-

- - -

-
-Type an equation: - - -
- -
-
-
-
-
- -
\[ \]
- -
\[ \]
-
- -

trapazodial rule:

-$$ \int_a^bf(x)dx=\frac{b-a}{n} \left [ \frac{f(a)+f(b)}{2}+\sum_{i=1}^n {f \left( \left( \frac{i}{n}+a \right ) \frac{b}{a+1}\right) }\right ] +O(n^2) $$ -((\sin{x})) $\int{stuff dx}$ - http://en.wikipedia.org/wiki/Numerical_integration -

-
-
-
-

Proposal: Interactive numerical method demonstration.

-

CSC280 Senior Project

-

John Vishnefske

-

Numerical computing has historically required specialized client software, or development systems to be installed on the client system. While web based platforms such as Wolfram alpha, and iPython notebook provide an interactive numerical environment with many current systems computation is typically done on the server side, with instructions being passed to the server, and results being passed back to the server. While this allows a variety of native libraries to be utilized the fact that the processing is done on the server side presents a bottleneck as larger computation capabilities is provided to more concurrent users. This is solved in iPython notebook by allowing individual instances of the web server side to be installed by users, and run. My proposal takes the distributed approach a step further by doing the computation in the client browser. This has the potential to greatly reduce the computational cost on the server side allowing, more client connections than a centralized computational model. -My initial development goal is to develop a system that allows visual graphing of numerical efficiency of sparse grid numerical integration methods, and Markov Chain Monte Carlo for multi- dimensional integration.
-Current software work in the are includes the Tasmanian Sparse Grid Project. Typical usage of this library is to either implement the function as a c++ library, or to run, a wrapper program for Tasmanian that allows interfacing the console application with Matlab. Initial implementation could use this library to create a grid, and weight vector on the server side, and the client receiving this, and doing the actual function evaluation, and weighted averaging.

-

Project Steps:

- -

Future Consideration

- -
diff --git a/app/js/main.js b/app/js/main.js index 20ca76d6..e69de29b 100644 --- a/app/js/main.js +++ b/app/js/main.js @@ -1 +0,0 @@ -console.log('\'Allo \'Allo!'); \ No newline at end of file diff --git a/app/scripts/main.js b/app/scripts/main.js new file mode 100644 index 00000000..e69de29b diff --git a/app/scripts/parser.js b/app/scripts/parser.js index cc229361..e69de29b 100755 --- a/app/scripts/parser.js +++ b/app/scripts/parser.js @@ -1,172 +0,0 @@ -'use strict'; -(function($, MathJax, undefined) { - -$(document).ready(function() { - var reservedWords = ['abstract', 'arguments', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class*', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum*', 'eval', 'export*', 'extends*', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import*', 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super*', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'yield']; -var forbiddenWords = ['Array', 'Date', 'eval', 'function', 'hasOwnProperty', 'Infinity', 'isFinite', 'isNaN', 'isPrototypeOf', 'length', 'Math', 'NaN', 'name', 'Number', 'Object', 'prototype', 'String', 'toString', 'undefined', 'valueOf']; - reservedWords = reservedWords.concat(forbiddenWords); - var chart; - //get MathJax output object - var mjDisplayBox, mjOutputBox; - MathJax.Hub.Queue(function() { - mjDisplayBox = MathJax.Hub.getAllJax('math-display')[0]; - mjOutputBox = MathJax.Hub.getAllJax('math-output')[0]; - }); - function contains(obj, key) { - return typeof(obj[key]) !== 'undefined'; - } - function plot(pts) { - if (typeof chart === 'undefined') { - var ctx = $('#myChart').get(0).getContext('2d'); - //chart = new Chart(ctx); - } - //chart.:w - //PolarArea(pts); - } - function treeToJS(tree) { - //for the javascript emitter a terminal string is a current - //valid javascript string. - //a nonterminal is anything else we still need to analyze - // the stack contains type value pairs of the form [t,v] - // where the type is a boolean true if terminal, false if nonterminal. - - var jsDebug = $('#js-debug'); - jsDebug.text('jsdebug:'); - var stack = []; - stack.getTerminals = function(count) { - var vars = []; - for (var i = 0; i < count; ++i) { - var temp = this.pop(); - if (temp[0] === false) { - throw new Error('Expected literal in parse tree.'); - } - vars.append(temp + ''); - } - return vars; - }; - var specialVars = { - e: 'Math.E', - pi: 'Math.PI' - }; - var functions = { - sin: ['Math.sin', 1], - cos: ['Math.cos', 1], - tan: ['Math.tan', 1], - Parentheses: ['', 1], - Exponent: ['Math.pow', 2] - }; - var binaryOperators = { - Plus: '+', - Times: '*', - Minus: '-' - }; - var ignore = { - Int: true, - Float: true - }; - var valid; - var token = tree[0]; - if (token === 'Plus') { - return String(treeToJS(tree[1])) + '+' + treeToJS(tree[2]); - }else if (token === 'Exponent') { - return 'Math.pow(' + treeToJS(tree[1]) + ',' + treeToJS(tree[2]) + ')'; - }else if (token === 'Times') { - return treeToJS(tree[1]) + '*' + treeToJS(tree[2]); - }else if (token === 'Literal') { - return String(tree[2]); - }else if (token === 'Minus') { - - return treeToJS(tree[1]) + '-' + treeToJS(tree[2]); - }else if (token === 'Variable') { - if (contains(specialVars, tree[1])) { - return specialVars[tree[1]]; - }else if (!contains(reservedWords, tree)) { - - functionVars = functionVars.concat([tree[1]]); - return tree[1]; - } - - else return '/*bad function*/'; - }else if (token === 'Parentheses') { - return '(' + treeToJS(tree[1]) + ')'; - }else if (token === 'Function') { - if (contains(functions, tree[1][1])) { - var productionRule = functions[tree[1][1]]; - return productionRule[0] + '(' + tree[2].map(treeToJS).join() + ')'; - //return "# fun: "+JSON.stringify(tree)+"//"+JSON.stringify(productionRule)+"#"; - - } - else return '/* invalid function' + JSON.stringify(tree[1][1]) + '*/'; - - } - return ' /* invalid */ '; - } - var functionVars; - // live output MathJax whenever a key is pressed - $('#math-input').on('keyup', function(evt) { - - - var math = $(this).val(); - var tree; - $(this).css('color', 'black'); - if (math.length > 0) { - try { - tree = MathLex.parse(math); - //var sageText = MathLex.render(tree,); - $('#debug-out').text(JSON.stringify(tree)); - //$('.math-output') - //MathJax.Hub.Queue(['Text',mjDisplayBox,latex]); - - } catch (err) { - $(this).css('color', 'red'); - $('#debug').text(err); - //window.alert(err); - throw err; - } - }else { - MathJax.Hub.Queue(['Text', mjDisplayBox, '']); - MathJax.Hub.Queue(['Text', mjOutputBox, '']); - } - functionVars = []; - var strFunction = treeToJS(tree); - //functionVars=functionVars.concat(5); - eval('var myFunc=function(' + functionVars.join(',') + '){return ' + strFunction + '};'); - $('#js-out').text('js:' + String(myFunc)); - //)); - //treeToJS(tree); - //$("#js-out").text("yep, js works"); - }); - //plot([[1,2,3][1,0,1]] - // - /* - plot( - ta = { - labels: ["January", "February", "March", "April", "May", "June", "July"], - datasets: [ - { - label: "My First dataset", - fillColor: "rgba(220,220,220,0.2)", - strokeColor: "rgba(220,220,220,1)", - pointColor: "rgba(220,220,220,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(220,220,220,1)", - data: [65, 59, 80, 81, 56, 55, 40] - }, - { - label: "My Second dataset", - fillColor: "rgba(151,187,205,0.2)", - strokeColor: "rgba(151,187,205,1)", - pointColor: "rgba(151,187,205,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(151,187,205,1)", - data: [28, 48, 40, 19, 86, 27, 90] - } - ] -}; - - ); -*/ - }); -}(jQuery, MathJax)); diff --git a/app/utils/mathParser.js b/app/utils/mathParser.js new file mode 100644 index 00000000..68ddd4fb --- /dev/null +++ b/app/utils/mathParser.js @@ -0,0 +1,424 @@ +/** + * @file mathParser.js + * @description This module provides functionality to parse mathematical expressions + * using MathLex and convert them into executable JavaScript function strings. + * It replaces the core logic previously found in app/scripts/parser.js. + */ + +// List of JavaScript reserved words and forbidden global names to prevent variable name clashes. +// These are used to validate variable names extracted from the math expression. +const reservedWords = [ + 'abstract', 'arguments', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'yield' +]; +const forbiddenWords = [ + 'Array', 'Date', 'eval', 'function', 'hasOwnProperty', 'Infinity', 'isFinite', 'isNaN', 'isPrototypeOf', 'length', 'Math', 'NaN', 'name', 'Number', 'Object', 'prototype', 'String', 'toString', 'undefined', 'valueOf' +]; +const allForbiddenWords = reservedWords.concat(forbiddenWords); + +/** + * Checks if an object contains a specific key. + * @param {object} obj - The object to check. + * @param {string} key - The key to look for. + * @returns {boolean} True if the key exists in the object, false otherwise. + */ +function contains(obj, key) { + return typeof(obj[key]) !== 'undefined'; +} + +// A temporary array to collect variable names encountered during tree traversal. +// It is reset for each call to parseMathExpression. +let collectedFunctionVars = []; + +/** + * Recursively converts a MathLex parse tree node into a JavaScript expression string. + * This function handles basic arithmetic operations, literals, variables, parentheses, + * and a limited set of mathematical functions. + * + * @param {Array} tree - A node from the MathLex parse tree (e.g., `['Plus', operand1, operand2]`). + * @returns {string} The JavaScript equivalent of the parse tree node. + */ +function treeToJS(tree) { + // Special variables that map to JavaScript's Math constants. + const specialVars = { + e: 'Math.E', + pi: 'Math.PI', + infinity: 'Infinity', + oo: 'Infinity', // 'oo' is also used for infinity in MathLex + true: 'true', + false: 'false' + }; + + const token = tree[0]; // The type of the current node (e.g., 'Plus', 'Variable', 'Function'). + + switch (token) { + case 'Empty': + return '0'; // Represents an empty expression, default to 0 or an empty string + case 'EmptySet': + return 'new Set()'; // Represents an empty set + + // Arithmetic Operations + case 'Plus': + return `${treeToJS(tree[1])} + ${treeToJS(tree[2])}`; + case 'Minus': + return `${treeToJS(tree[1])} - ${treeToJS(tree[2])}`; + case 'Times': + return `${treeToJS(tree[1])} * ${treeToJS(tree[2])}`; + case 'Divide': + // tree[3] indicates if it's a fraction (true) or inline division (false) + return `(${treeToJS(tree[1])}) / (${treeToJS(tree[2])})`; + case 'Modulus': + return `(${treeToJS(tree[1])}) % (${treeToJS(tree[2])})`; + case 'Exponent': + return `Math.pow(${treeToJS(tree[1])}, ${treeToJS(tree[2])})`; + case 'Positive': + return `+${treeToJS(tree[1])}`; + case 'Negative': + return `-${treeToJS(tree[1])}`; + case 'PlusMinus': + // Represents a value that can be either sum or difference. + // This is ambiguous for a single JS expression. Returning the sum for now. + // A more robust solution would return an array or a custom object. + return `(${treeToJS(tree[1])} + ${treeToJS(tree[2])})`; + case 'MinusPlus': + // Similar to PlusMinus, returning the difference. + return `(${treeToJS(tree[1])} - ${treeToJS(tree[2])})`; + case 'Ratio': + return `(${treeToJS(tree[1])}) / (${treeToJS(tree[2])})`; // Treat ratio as division + + // Literals, Variables, Constants + case 'Literal': + return String(tree[2]); + case 'Variable': + const varName = tree[1]; + if (contains(specialVars, varName)) { + return specialVars[varName]; + } else if (!allForbiddenWords.includes(varName)) { + if (!collectedFunctionVars.includes(varName)) { + collectedFunctionVars.push(varName); + } + return varName; + } else { + console.warn(`Forbidden variable name encountered: ${varName}`); + return `/*forbidden_var:${varName}*/`; + } + case 'Constant': + const constName = tree[1].toLowerCase(); + if (contains(specialVars, constName)) { + return specialVars[constName]; + } else { + // For other mathematical constants like 'gamma', 'tau', or set notations like 'C', 'R', 'Z', etc. + // These don't have direct JS equivalents and might need custom objects or be treated as variables. + console.warn(`Unsupported constant: ${tree[1]}`); + return `/*unsupported_constant:${tree[1]}*/`; + } + + // Grouping + case 'Parentheses': + return `(${treeToJS(tree[1])})`; + case 'AbsVal': + return `Math.abs(${treeToJS(tree[1])})`; + case 'Norm': + return `/* norm(${treeToJS(tree[1])}) */`; // Requires vector/matrix library + + // Logical Operations + case 'Equal': + return `(${treeToJS(tree[1])} === ${treeToJS(tree[2])})`; + case 'NotEqual': + return `(${treeToJS(tree[1])} !== ${treeToJS(tree[2])})`; + case 'Less': + return `(${treeToJS(tree[1])} < ${treeToJS(tree[2])})`; + case 'LessEqual': + return `(${treeToJS(tree[1])} <= ${treeToJS(tree[2])})`; + case 'Greater': + return `(${treeToJS(tree[1])} > ${treeToJS(tree[2])})`; + case 'GreaterEqual': + return `(${treeToJS(tree[1])} >= ${treeToJS(tree[2])})`; + case 'And': + return `(${treeToJS(tree[1])} && ${treeToJS(tree[2])})`; + case 'Or': + return `(${treeToJS(tree[1])} || ${treeToJS(tree[2])})`; + case 'Not': + return `!(${treeToJS(tree[1])})`; + case 'Iff': // If and only if (logical equivalence) + return `((${treeToJS(tree[1])} && ${treeToJS(tree[2])}) || (!${treeToJS(tree[1])} && !${treeToJS(tree[2])}))`; + case 'Implies': // Implication (A -> B is !A || B) + // tree[3] indicates direction, but for JS, A -> B is always !A || B + return `(!(${treeToJS(tree[1])}) || (${treeToJS(tree[2])}))`; + case 'Xor': // Exclusive OR + return `((${treeToJS(tree[1])} || ${treeToJS(tree[2])}) && !(${treeToJS(tree[1])} && ${treeToJS(tree[2])}))`; + case 'Equivalent': + return `(${treeToJS(tree[1])} === ${treeToJS(tree[2])})`; + case 'NotEquivalent': + return `(${treeToJS(tree[1])} !== ${treeToJS(tree[2])})`; + + // Relational (non-equality) + case 'RatioEqual': + return `/* ratio_equal(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Congruent': + return `/* congruent(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Similar': + return `/* similar(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Parallel': + return `/* parallel(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Perpendicular': + return `/* perpendicular(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Divides': + return `((${treeToJS(tree[2])}) % (${treeToJS(tree[1])}) === 0)`; + case 'NotDivides': + return `((${treeToJS(tree[2])}) % (${treeToJS(tree[1])}) !== 0)`; + + // Set Operations + case 'Union': + return `/* union(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Intersection': + return `/* intersection(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'SetDiff': + return `/* setDifference(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Subset': + return `/* isSubset(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Superset': + return `/* isSuperset(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'ProperSubset': + return `/* isProperSubset(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'ProperSuperset': + return `/* isProperSuperset(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Inclusion': // Element in set + return `/* ${treeToJS(tree[2])}.has(${treeToJS(tree[1])}) */`; + case 'Set': + const setElements = tree[1].map(item => treeToJS(item)).join(', '); + return `new Set([${setElements}])`; + case 'List': // Represent as a JS array + const listElements = tree[1].map(item => treeToJS(item)).join(', '); + return `[${listElements}]`; + case 'SetBuilder': // { x : P(x) } + // This is complex and requires a filter/map operation over a domain. + return `/* setBuilder(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Range': + // tree[1] is start inclusive, tree[4] is end inclusive + // tree[2] is start value, tree[3] is end value + // This is a conceptual range, not a direct JS array. + return `/* range(${treeToJS(tree[2])}, ${treeToJS(tree[3])}, ${tree[1] ? 'inclusive_start' : 'exclusive_start'}, ${tree[4] ? 'inclusive_end' : 'exclusive_end'}) */`; + + // Vector/Matrix Operations + case 'Vector': + const vectorElements = tree[1].map(item => treeToJS(item)).join(', '); + return `[${vectorElements}]`; // Simple array for now, could be a custom Vector class + case 'DotProduct': + return `/* dotProduct(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'CrossProduct': + return `/* crossProduct(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'WedgeProduct': + return `/* wedgeProduct(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'TensorProduct': + return `/* tensorProduct(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Vectorizer': + return `/* vectorizer(${treeToJS(tree[1])}) */`; + case 'UnitVectorizer': + return `/* unitVectorizer(${treeToJS(tree[1])}) */`; + + // Calculus and Special Functions/Operators + case 'Factorial': + return `/* factorial(${treeToJS(tree[1])}) */`; // Requires a factorial function + case 'Prime': + return `/* prime(${treeToJS(tree[1])}) */`; // Placeholder for derivative notation (f') + case 'DotDiff': + // This represents derivatives with respect to time (e.g., x_dot, x_double_dot) + // The MathLex parser handles multiple dots by nesting 'DotDiff' nodes. + let dotCount = 0; + let current = tree; + while (current[0] === 'DotDiff') { + dotCount++; + current = current[1]; + } + return `/* ${dotCount}-dot_derivative(${treeToJS(current)}) */`; + case 'Partial': + return `/* partial(${treeToJS(tree[1])}) */`; + case 'Differential': + return `/* differential(${treeToJS(tree[1])}) */`; + case 'Change': + return `/* change(${treeToJS(tree[1])}) */`; + case 'Gradient': + return `/* gradient(${treeToJS(tree[1])}) */`; + case 'Divergence': + return `/* divergence(${treeToJS(tree[1])}) */`; + case 'Curl': + return `/* curl(${treeToJS(tree[1])}) */`; + case 'Superscript': + // This is often used for exponents, but can also be for other notations. + // If it's a number, it's likely an exponent. Otherwise, it's a notation. + // For now, treat as Math.pow if the superscript is a number, otherwise a placeholder. + const base = treeToJS(tree[1]); + const superscript = treeToJS(tree[2]); + if (!isNaN(parseFloat(superscript)) && isFinite(superscript)) { + return `Math.pow(${base}, ${superscript})`; + } + return `/* superscript(${base}, ${superscript}) */`; + case 'Subscript': + // Subscripts usually denote elements of a sequence/array or specific variables. + // This is highly context-dependent. Treat as array access if base is an array/list, + // otherwise as a composite variable name. + const subBase = treeToJS(tree[1]); + const subIndex = treeToJS(tree[2]); + return `/* subscript(${subBase}, ${subIndex}) */`; // e.g., `${subBase}[${subIndex}]` if `subBase` is an array + + // Quantifiers + case 'Forall': + return `/* forall(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Exists': + return `/* exists(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'Unique': + return `/* unique(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + + // Function Calls (general and specific MathLex functions) + case 'Function': + const funcNameNode = tree[1]; + const args = tree[2]; + + if (funcNameNode[0] !== 'Variable') { + console.warn('Expected function name to be a variable, but got:', funcNameNode); + return `/* invalid_function_name */`; + } + const funcName = funcNameNode[1]; + const jsArgs = args.map(argTree => treeToJS(argTree)); + + switch (funcName) { + // Standard Math functions + case 'sin': case 'cos': case 'tan': + case 'asin': case 'acos': case 'atan': + case 'sinh': case 'cosh': case 'tanh': + case 'log': // Math.log is natural log (ln) + case 'exp': // Math.exp is e^x + case 'sqrt': + case 'abs': + case 'floor': + case 'ceil': + return `Math.${funcName}(${jsArgs.join(',')})`; + case 'ln': // Natural logarithm + return `Math.log(${jsArgs[0]})`; + case 'log10': // Base 10 logarithm + return `Math.log10(${jsArgs[0]})`; + case 'log2': // Base 2 logarithm + return `Math.log2(${jsArgs[0]})`; + case 'csc': case 'sec': case 'cot': + case 'acsc': case 'asec': case 'acot': + case 'csch': case 'sech': case 'coth': + case 'acsch': case 'asech': case 'acoth': + // These are not directly in Math, need to implement via sin/cos/tan + return `/* ${funcName}(${jsArgs.join(',')}) */`; + case 'root': // root(x, n) -> Math.pow(x, 1/n) + if (jsArgs.length === 2) { + return `Math.pow(${jsArgs[0]}, 1/${jsArgs[1]})`; + } + console.warn(`Invalid arguments for root function: ${jsArgs.length}`); + return `/* root(${jsArgs.join(',')}) */`; + + // Aggregation functions (sum, prod, big union, big intersect) + case 'sum': case 'prod': case 'Union': case 'Intersect': + // These require iterating over a range, which is complex in simple JS. + // The arguments structure for these is complex (e.g., [expression, variable, lower_bound, upper_bound]) + return `/* ${funcName}(${jsArgs.join(',')}) */`; + case 'lim': case 'limit': + // Arguments: [expression, variable, limit_point] + return `/* limit(${jsArgs.join(',')}) */`; + + // Calculus operators (often require symbolic libraries or specific numerical methods) + case 'int': case 'integral': + // Arguments: [integrand, variable, lower_bound, upper_bound] + return `/* integral(${jsArgs.join(',')}) */`; + case 'diff': // Differential, arguments: [expression, variable] + return `/* diff(${jsArgs.join(',')}) */`; + case 'pdiff': // Partial differential, arguments: [expression, variable] + return `/* pdiff(${jsArgs.join(',')}) */`; + case 'grad': case 'div': case 'curl': + return `/* ${funcName}(${jsArgs.join(',')}) */`; + + // Combinatorics + case 'combination': // C(n, k) + case 'comb': + return `/* combination(${jsArgs.join(',')}) */`; + case 'perm': // P(n, k) + return `/* permutation(${jsArgs.join(',')}) */`; + case 'choose': // Alias for combination + return `/* choose(${jsArgs.join(',')}) */`; + + // Special functions + case 'Gamma': + return `/* Gamma(${jsArgs.join(',')}) */`; // Requires a Gamma function implementation + + default: + // For custom functions or unrecognized ones, treat as direct call + return `${funcName}(${jsArgs.join(',')})`; + } + + // Quantum Mechanics Notation + case 'Bra': + return `/* bra(${treeToJS(tree[1])}) */`; + case 'Ket': + return `/* ket(${treeToJS(tree[1])}) */`; + case 'BraKet': + return `/* braKet(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + + // Other complex structures + case 'Integral': + // This is the specific integral node from the grammar, distinct from 'int' function. + // Structure: ['Integral', integrand, differential_variable, bounds_object] + // bounds_object: {lo: lower_bound_tree, hi: upper_bound_tree} + const integrand = treeToJS(tree[1]); + const diffVar = treeToJS(tree[2]); + const bounds = tree[3]; + const lowerBound = bounds.lo ? treeToJS(bounds.lo) : 'null'; + const upperBound = bounds.hi ? treeToJS(bounds.hi) : 'null'; + return `/* integral(${integrand}, ${diffVar}, ${lowerBound}, ${upperBound}) */`; + + case 'BigUnion': + return `/* bigUnion(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + case 'BigIntersect': + return `/* bigIntersect(${treeToJS(tree[1])}, ${treeToJS(tree[2])}) */`; + + default: + // For any other unsupported token, return an error placeholder. + console.warn(`Unhandled MathLex node type: ${token}`, tree); + return `/* unsupported_token:${token} */`; + } +} + +/** + * Parses a mathematical expression string using MathLex and converts it into a + * JavaScript function string. It also identifies and returns the variables + * present in the expression. + * + * @param {string} mathExpression - The mathematical expression string to parse. + * @returns {{jsFunctionString: string, variables: string[], parseTree: Array}} + * An object containing: + * - `jsFunctionString`: The generated JavaScript function string (e.g., "function(x, y) { return Math.pow(x, y); }"). + * - `variables`: An array of variable names found in the expression (e.g., ['x', 'y']). + * - `parseTree`: The raw parse tree generated by MathLex (useful for debugging). + * @throws {Error} If MathLex encounters a parsing error. + */ +export function parseMathExpression(mathExpression) { + collectedFunctionVars = []; // Reset collected variables for each new parse call. + let tree; + + try { + // MathLex is assumed to be globally available (e.g., loaded via a script tag). + // If MathLex were an ES6 module, it would be imported here. + tree = MathLex.parse(mathExpression); + } catch (err) { + throw new Error(`MathLex parsing error: ${err.message}`); + } + + // Convert the MathLex parse tree into a JavaScript expression body. + const jsBody = treeToJS(tree); + + // Sort the collected variables alphabetically for a consistent function signature. + const sortedVars = collectedFunctionVars.sort(); + + // Construct the full JavaScript function string. + const jsFunctionString = `function(${sortedVars.join(', ')}) { return ${jsBody}; }`; + + return { + jsFunctionString: jsFunctionString, + variables: sortedVars, + parseTree: tree // Include the parse tree for potential debugging or further processing. + }; +} diff --git a/bower.json b/bower.json deleted file mode 100644 index 630d62a3..00000000 --- a/bower.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "seniorproject", - "private": true, - "dependencies": { - "jquery": "~1.11.1" - } -} \ No newline at end of file diff --git a/package.json b/package.json index 25d0739f..5fbbde0a 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,39 @@ { "name": "seniorproject", + "version": "1.0.0", + "description": "Client-side JS numerical analysis with Vue.js", + "main": "app/main.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "jest" + }, + "keywords": [], + "author": "", + "license": "ISC", "devDependencies": { - "apache-server-configs": "^2.7.1", - "grunt": "^1.6.0", - "grunt-autoprefixer": "^1.0.0", - "grunt-concurrent": "^0.5.0", - "grunt-contrib-clean": "^0.6.0", - "grunt-contrib-concat": "^0.5.0", - "grunt-contrib-connect": "^0.8.0", - "grunt-contrib-copy": "^0.5.0", - "grunt-contrib-cssmin": "^0.10.0", - "grunt-contrib-htmlmin": "^0.3.0", - "grunt-contrib-imagemin": "^0.8.0", - "grunt-contrib-jshint": "^0.10.0", - "grunt-contrib-uglify": "^0.5.1", - "grunt-contrib-watch": "^0.6.1", - "grunt-mocha": "^0.4.10", - "grunt-newer": "^0.7.0", - "grunt-rev": "^0.1.0", - "grunt-svgmin": "^0.4.0", - "grunt-usemin": "^2.3.0", - "grunt-wiredep": "^1.7.0", - "jshint-stylish": "^0.4.0", - "load-grunt-tasks": "^0.4.0", - "time-grunt": "^0.4.0" + "@babel/core": "^7.x.x", + "@babel/preset-env": "^7.x.x", + "@vitejs/plugin-vue": "^5.x.x", + "@vue/test-utils": "^2.x.x", + "babel-jest": "^29.x.x", + "jest": "^29.x.x", + "vue": "^3.x.x", + "vite": "^5.x.x" }, "engines": { "node": ">=0.10.0" - } + }, + "directories": { + "test": "test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jvishnefske/skygraph-frontend.git" + }, + "bugs": { + "url": "https://github.com/jvishnefske/skygraph-frontend/issues" + }, + "homepage": "https://github.com/jvishnefske/skygraph-frontend#readme" } diff --git a/readme.md b/readme.md deleted file mode 100644 index c8a73691..00000000 --- a/readme.md +++ /dev/null @@ -1,12 +0,0 @@ -Numerical integration example -### -John Vishnefske Senior project - -this uses grunt to run javascript tests, and minification -if you do not have node.js installed see [nodejs.org](http://nodejs.org/) - - git pull https://github.com/jvishnefske/numerical-example/ - cd numerical-example - npm install - -to run integration