diff --git a/packages/desktop-client/src/components/SyncNotifications.js b/packages/desktop-client/src/components/SyncNotifications.js deleted file mode 100644 index 12563a78f1553e74707b28b46452c317ae94f491..0000000000000000000000000000000000000000 --- a/packages/desktop-client/src/components/SyncNotifications.js +++ /dev/null @@ -1,65 +0,0 @@ -export function RepairSyncNotification() {} - -// TODO: sync button shouldn't show error status if it's a local file -// and needs uploading.. should just be grayed out -// -// TODO: improve styling of these modals - -// export function NeedsUploadNotification({ actions }) { -// let [loading, setLoading] = useState(false); - -// return ( -// <Stack align="center" direction="row"> -// <Text> -// This file is not a cloud file. You need to register it to take advantage -// of syncing which allows you to use it across devices and never worry -// about losing your data. -// </Text> -// <ButtonWithLoading -// bare -// loading={loading} -// onClick={async () => { -// setLoading(true); -// await actions.uploadBudget(); -// actions.removeNotification('file-needs-upload'); -// setLoading(false); - -// actions.sync(); -// actions.loadPrefs(); -// }} -// style={{ -// backgroundColor: 'rgba(100, 100, 100, .12)', -// color: colors.n1, -// fontSize: 14, -// flexShrink: 0, -// '&:hover, &:active': { backgroundColor: 'rgba(100, 100, 100, .25)' } -// }} -// > -// Register -// </ButtonWithLoading> -// </Stack> -// ); -// } - -// export function SyncResetNotification({ cloudFileId, actions }) { -// return ( -// <Stack align="center" direction="row"> -// <Text> -// </Text> -// <Button -// bare -// onClick={async () => { -// actions.removeNotification('out-of-date-key'); -// }} -// style={{ -// backgroundColor: colors.r10, -// flexShrink: 0, -// '&:hover': { backgroundColor: colors.r10 }, -// '&:active': { backgroundColor: colors.r8 } -// }} -// > -// Revert -// </Button> -// </Stack> -// ); -// } diff --git a/packages/desktop-client/src/components/debug/index.js b/packages/desktop-client/src/components/debug/index.js deleted file mode 100644 index 0f12595ae290dccb411b7e9f3c368f4f16ce8a82..0000000000000000000000000000000000000000 --- a/packages/desktop-client/src/components/debug/index.js +++ /dev/null @@ -1,143 +0,0 @@ -import React, { Component } from 'react'; - -import styled from 'styled-components'; - -import { send } from 'loot-core/src/platform/client/fetch'; - -const Container = styled.div` - width: 100%; - overflow: auto; -`; - -const Code = styled.textarea` - width: 100%; - height: 10em; - font-size: 1em; -`; - -const Output = styled.pre` - width: 100%; - background-color: #333333; - color: white; - padding: 0.5em; -`; - -class Debug extends Component { - constructor() { - super(); - this.state = { - value: localStorage.debugValue, - outputType: 'ast', - ast: null, - code: null, - sql: null, - sqlgenValue: localStorage.sqlgenValue, - sqlgenRow: localStorage.sqlgenRow, - }; - } - - componentDidMount() { - this.fetchResults(this.state.value); - this.fetchSqlGenResult(this.state.value); - } - - fetchResults(value) { - localStorage.debugValue = value; - - send('debug-ast', { code: value }).then(ast => { - this.setState({ ast }); - }); - send('debug-code', { code: value }).then(code => { - this.setState({ code }); - }); - send('debug-query', { code: value }).then(sql => { - this.setState({ sql }); - }); - } - - async fetchSqlGenResult() { - let row = {}; - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-eval - row = (0, eval)('(' + this.state.sqlgenRow + ')'); - } catch (e) {} - - const res = await send('debug-sqlgen', { - expr: this.state.sqlgenValue, - }); - this.setState({ sqlgenResult: res }); - } - - processInput(e) { - this.setState({ value: e.target.value }); - this.fetchResults(e.target.value); - } - - processSqlGen(value, field) { - localStorage[field] = value; - this.setState({ [field]: value }, () => { - this.fetchSqlGenResult(); - }); - } - - onInputType(e) { - this.setState({ outputType: e.target.value }); - } - - render() { - const { - // value, - // outputType, - // ast, - // code, - // sql, - sqlgenValue, - sqlgenRow, - sqlgenResult, - } = this.state; - - return ( - <Container> - {/*<h2>Debug</h2> - <p>Input:</p> - <Code value={value} onChange={this.processInput.bind(this)} /> - <select - value={this.state.outputType} - onChange={this.onInputType.bind(this)} - > - <option value="ast">AST</option> - <option value="code">code</option> - <option value="sql">SQL</option> - </select> - - <div style={{ display: outputType === 'ast' ? 'block' : 'none' }}> - <p>AST:</p> - <Output>{ast ? JSON.stringify(ast, null, 2) : ''}</Output> - </div> - - <div style={{ display: outputType === 'code' ? 'block' : 'none' }}> - <p>Code:</p> - <Output>{code || ''}</Output> - </div> - - <div style={{ display: outputType === 'sql' ? 'block' : 'none' }}> - <p>SQL:</p> - <Output>{sql || ''}</Output> - </div>*/} - - <h3>sqlgen</h3> - <Code - value={sqlgenValue} - onChange={e => this.processSqlGen(e.target.value, 'sqlgenValue')} - /> - <Code - value={sqlgenRow} - onChange={e => this.processSqlGen(e.target.value, 'sqlgenRow')} - /> - <Output>{JSON.stringify(sqlgenResult)}</Output> - </Container> - ); - } -} - -export default Debug; diff --git a/packages/desktop-client/src/util.ts b/packages/desktop-client/src/util.ts deleted file mode 100644 index e506321203d4fee780dc3424d0f2a05674f2d5e7..0000000000000000000000000000000000000000 --- a/packages/desktop-client/src/util.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function getModalRoute(name: string): [string, string] { - let parts = name.split('/'); - return [parts[0], parts.slice(1).join('/')]; -} diff --git a/packages/loot-core/src/server/api-models.ts b/packages/loot-core/src/server/api-models.ts index 1678ae15af26d7eb0bd20530d5c290f4376e6a24..53a4ae52fc98c7e10b8b229bded0f076cc8a56da 100644 --- a/packages/loot-core/src/server/api-models.ts +++ b/packages/loot-core/src/server/api-models.ts @@ -1,82 +1,5 @@ import * as models from './models'; -export const transactionModel = { - ...models.transactionModel, - - toExternal(transactions, idx, payees) { - return transactions; - // function convert(t, payee) { - // return { - // id: t.id, - // account_id: t.acct, - // amount: t.amount, - // payee_id: payee ? payee.id : null, - // payee: payee ? payee.name : null, - // imported_payee: t.imported_description, - // category_id: t.category, - // date: t.date, - // notes: t.notes, - // imported_id: t.financial_id, - // transfer_id: t.transferred_id, - // cleared: t.cleared - // }; - // } - - // let splits = getAllSplitTransactions(transactions, idx); - // if (splits) { - // let payee = - // splits.parent.description && payees[splits.parent.description]; - - // return { - // ...convert(splits.parent, payee), - // subtransactions: splits.children.map(child => convert(child, payee)) - // }; - // } - - // let transaction = transactions[idx]; - // let payee = transaction.description && payees[transaction.description]; - // return convert(transaction, payee); - }, - - fromExternal(transaction) { - let result: Record<string, unknown> = {}; - if ('id' in transaction) { - result.id = transaction.id; - } - if ('account_id' in transaction) { - result.acct = transaction.account_id; - } - if ('amount' in transaction) { - result.amount = transaction.amount; - } - if ('payee_id' in transaction) { - result.description = transaction.payee_id; - } - if ('imported_payee' in transaction) { - result.imported_description = transaction.imported_payee; - } - if ('category_id' in transaction) { - result.category = transaction.category_id; - } - if ('date' in transaction) { - result.date = transaction.date; - } - if ('notes' in transaction) { - result.notes = transaction.notes; - } - if ('imported_id' in transaction) { - result.financial_id = transaction.imported_id; - } - if ('transfer_id' in transaction) { - result.transferred_id = transaction.transfer_id; - } - if ('cleared' in transaction) { - result.cleared = transaction.cleared; - } - return result; - }, -}; - export const accountModel = { ...models.accountModel, diff --git a/packages/loot-core/src/server/aql/compiler.ts b/packages/loot-core/src/server/aql/compiler.ts index 899cd383dd776edc66818957c3c5e01926cd7915..4898e9715dbcf071c3f52793eb98adede1003956 100644 --- a/packages/loot-core/src/server/aql/compiler.ts +++ b/packages/loot-core/src/server/aql/compiler.ts @@ -1118,7 +1118,3 @@ export function generateSQLWithState( let { sqlPieces, state } = compileQuery(queryState, schema, schemaConfig); return { sql: defaultConstructQuery(queryState, state, sqlPieces), state }; } - -export function generateSQL(queryState) { - return generateSQLWithState(queryState).sql; -} diff --git a/packages/loot-core/src/server/errors.ts b/packages/loot-core/src/server/errors.ts index 3d1ddccc511c7378c38fc994a3334957395c37a2..1450c447e66e0b95c2b68714398b41e5c55b9127 100644 --- a/packages/loot-core/src/server/errors.ts +++ b/packages/loot-core/src/server/errors.ts @@ -56,7 +56,3 @@ export function FileDownloadError(reason, meta?) { export function FileUploadError(reason, meta?) { return { type: 'FileUploadError', reason, meta }; } - -export function isCodeError(err) { - return err instanceof ReferenceError || err instanceof SyntaxError; -} diff --git a/packages/loot-core/src/server/perf.ts b/packages/loot-core/src/server/perf.ts deleted file mode 100644 index 6182c1f5a170aed7d93475efe89a20c1ead2ace7..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/perf.ts +++ /dev/null @@ -1,59 +0,0 @@ -let enabled = false; -let entries = {}; -let counters = {}; - -export function reset() { - entries = {}; - counters = {}; -} - -export function record(name) { - const start = Date.now(); - return () => unrecord(name, start); -} - -function unrecord(name, start) { - const end = Date.now(); - - if (enabled) { - if (entries[name] == null) { - entries[name] = []; - } - entries[name].push(end - start); - } -} - -export function increment(name) { - if (enabled) { - if (counters[name] == null) { - counters[name] = 0; - } - counters[name]++; - } -} - -export function start() { - enabled = true; -} - -export function stop() { - enabled = false; - - console.log('~~ PERFORMANCE REPORT ~~'); - for (let name in entries) { - const records = entries[name]; - const total = records.reduce((total, n) => total + n / 1000, 0); - const avg = total / records.length; - - console.log( - `[${name}] count: ${records.length} total: ${total}s avg: ${avg}`, - ); - } - - for (let name in counters) { - console.log(`[${name}] ${counters[name]}`); - } - console.log('~~ END REPORT ~~'); - - reset(); -} diff --git a/packages/loot-core/src/server/spreadsheet/globals.ts b/packages/loot-core/src/server/spreadsheet/globals.ts index fcbfbc6625149847f6f3506ea1d6b9be8d01cec5..45b65640961f556f55d0512229b8639d26c2c10b 100644 --- a/packages/loot-core/src/server/spreadsheet/globals.ts +++ b/packages/loot-core/src/server/spreadsheet/globals.ts @@ -1,12 +1,3 @@ -export function first(arr) { - return arr[0]; -} - -export function firstValue(arr) { - const keys = Object.keys(arr[0]); - return arr[0][keys[0]]; -} - export function number(v) { if (typeof v === 'number') { return v; @@ -20,11 +11,3 @@ export function number(v) { return 0; } - -export function min(x, y) { - return Math.min(x, y); -} - -export function max(x, y) { - return Math.max(x, y); -} diff --git a/packages/loot-core/src/server/spreadsheet/new/REQUIREMENTS.txt b/packages/loot-core/src/server/spreadsheet/new/REQUIREMENTS.txt deleted file mode 100644 index 4836a96ff50146b40dd6bea2d8d8a8c56c690280..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/REQUIREMENTS.txt +++ /dev/null @@ -1,33 +0,0 @@ -* Function calls (native hooks) -* Operators: + - / * > < >= <= = -* Queries: from t in transactions select { amount } -* Types -** Boolean (true / false) -** Integer -** Float -** String -* Variables (only global lookup) - -Need a stack to hold temporary values since function calls can be -nested. Instructions: - -MOV -CALL -QUERY -BOP -UOP - -Registers: - -PC -SP -REG1 - -Query language: - -=from transactions - where - date >= 20170101 and - date <= 20170131 and - category.is_income = 1 - calculate sum(amount) diff --git a/packages/loot-core/src/server/spreadsheet/new/__snapshots__/compiler.test.ts.snap b/packages/loot-core/src/server/spreadsheet/new/__snapshots__/compiler.test.ts.snap deleted file mode 100644 index b8d56b78d1725c6b2dfb79cd486de4a13da31265..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/__snapshots__/compiler.test.ts.snap +++ /dev/null @@ -1,734 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Compiler basic 1`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT sum(amount) FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - LEFT JOIN accounts t1 ON t1.id = transactions.acct -LEFT JOIN banks t2 ON t2.id = t1.bank - WHERE ((((date >= 20170101) and (date <= 20170131)) and (t2.name = 1))) AND transactions.isParent = 0 AND transactions.tombstone = 0 - ", - true, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(call), - Object { - "name": "generated!number", - "type": "__var", - }, - Array [ - Object { - "index": 0, - "type": "__stack", - }, - ], - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(call), - Object { - "name": "generated!first", - "type": "__var", - }, - Array [ - Object { - "index": 0, - "type": "__stack", - }, - ], - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler basic 2`] = ` -Array [ - Array [ - Symbol(mov), - "", - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler compiler binary ops 1`] = ` -Array [ - Array [ - Symbol(mov), - Object { - "name": "generated!bar", - "type": "__var", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "+", - Object { - "name": "generated!foo", - "type": "__var", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - Object { - "name": "generated!baz", - "type": "__var", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "+", - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - Object { - "name": "generated!boo", - "type": "__var", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "+", - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler compiler nested funcs 1`] = ` -Array [ - Array [ - Symbol(mov), - 0, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - -20000, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 1, - "type": "__stack", - }, - ], - Array [ - Symbol(call), - Object { - "name": "generated!number", - "type": "__var", - }, - Array [ - Object { - "index": 1, - "type": "__stack", - }, - ], - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 1, - "type": "__stack", - }, - ], - Array [ - Symbol(call), - Object { - "name": "generated!min", - "type": "__var", - }, - Array [ - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__stack", - }, - ], - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler compiles boolean types 1`] = ` -Array [ - Array [ - Symbol(mov), - true, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - 1, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "and", - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(jumpf), - Object { - "index": 0, - "type": "__stack", - }, - Object { - "get": [Function], - "resolve": [Function], - }, - ], - Array [ - Symbol(mov), - 0, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(jumpt), - Object { - "index": 0, - "type": "__stack", - }, - Object { - "get": [Function], - "resolve": [Function], - }, - ], - Array [ - Symbol(mov), - 1, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler complex query expressions 1`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT substr(date,0,7), sum(amount) FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - - WHERE transactions.isParent = 0 AND transactions.tombstone = 0 - GROUP BY substr(date,0,7)", - false, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler field dependencies 1`] = ` -Array [ - "acct", - "category", - "description", - "isParent", - "tombstone", - "date", -] -`; - -exports[`Compiler parens 1`] = ` -Array [ - Array [ - Symbol(mov), - 1, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - 2, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "+", - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler parens 2`] = ` -Array [ - Array [ - Symbol(mov), - 1232, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - 2, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "+", - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 0, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - 3, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "index": 2, - "type": "__stack", - }, - ], - Array [ - Symbol(mov), - 4, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "+", - Object { - "index": 2, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(bop), - "-", - Object { - "index": 0, - "type": "__stack", - }, - Object { - "index": 1, - "type": "__reg", - }, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler query expressions 1`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT sum(amount) as a FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - - WHERE ((amount > 0)) AND transactions.isParent = 0 AND transactions.tombstone = 0 - ", - false, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler query expressions with field remapping 1`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT id FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - - WHERE ((__cm.transferId = \\"50\\")) AND transactions.isParent = 0 AND transactions.tombstone = 0 - ", - false, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler query expressions with field remapping 2`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT id FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - LEFT JOIN categories t1 ON __cm.transferId = t1.id - WHERE ((t1.name = \\"foo\\")) AND transactions.isParent = 0 AND transactions.tombstone = 0 - ", - false, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler query expressions with field remapping 3`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT id, t1.name FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - LEFT JOIN categories t1 ON __cm.transferId = t1.id - WHERE ((__cm.transferId = \\"50\\")) AND transactions.isParent = 0 AND transactions.tombstone = 0 - ", - false, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; - -exports[`Compiler query expressions with null 1`] = ` -Array [ - Array [ - Symbol(query), - " - SELECT count(amount) FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - LEFT JOIN accounts t1 ON t1.id = transactions.acct - WHERE (((t1.offbudget = 0) and (__cm.transferId IS NULL))) AND transactions.isParent = 0 AND transactions.tombstone = 0 - ", - true, - ], - Array [ - Symbol(mov), - Object { - "index": 1, - "type": "__reg", - }, - Object { - "name": "generated!result", - "type": "__var", - }, - ], -] -`; diff --git a/packages/loot-core/src/server/spreadsheet/new/__snapshots__/lexer.test.ts.snap b/packages/loot-core/src/server/spreadsheet/new/__snapshots__/lexer.test.ts.snap deleted file mode 100644 index f3f36d7e4de5c30509e6fa4b717141eb0304390b..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/__snapshots__/lexer.test.ts.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`lexer basic 1`] = ` -Array [ - Object { - "colno": 0, - "lineno": 0, - "type": "whitespace", - "value": " - ", - }, - Object { - "colno": 5, - "lineno": 1, - "type": "symbol", - "value": "x", - }, - Object { - "colno": 6, - "lineno": 1, - "type": "whitespace", - "value": " ", - }, - Object { - "colno": 7, - "lineno": 1, - "type": "operator", - "value": "!=~", - }, - Object { - "colno": 10, - "lineno": 1, - "type": "whitespace", - "value": " ", - }, - Object { - "colno": 11, - "lineno": 1, - "type": "int", - "value": "4", - }, - Object { - "colno": 12, - "lineno": 1, - "type": "whitespace", - "value": " - ", - }, -] -`; diff --git a/packages/loot-core/src/server/spreadsheet/new/__snapshots__/vm.test.ts.snap b/packages/loot-core/src/server/spreadsheet/new/__snapshots__/vm.test.ts.snap deleted file mode 100644 index 663b19a0f4c6707536b280a67d5f4d23611eda02..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/__snapshots__/vm.test.ts.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`vm basic 1`] = ` -Object { - "firstValue": [Function], - "generated!result": -6, - "number": [Function], -} -`; - -exports[`vm boolean types 1`] = ` -Object { - "generated!result": 0, -} -`; diff --git a/packages/loot-core/src/server/spreadsheet/new/compiler.test.ts b/packages/loot-core/src/server/spreadsheet/new/compiler.test.ts deleted file mode 100644 index b1796ebd0a1b86a0d910e43c3171cac5d0995746..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/compiler.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { compile } from './compiler'; - -describe('Compiler', () => { - test('get-query', () => { - compile( - '=from transactions where acct.offbudget = 0 and category = null and (description.transfer_acct.offbudget = 1 or description.transfer_acct = null) calculate { count(date) }', - ); - }); - - test('basic', () => { - let ops = compile(` - =first(number(from transactions - where - date >= 20170101 and - date <= 20170131 and - acct.bank.name = 1 - calculate { sum(amount) })) - `).ops; - expect(ops).toMatchSnapshot(); - - ops = compile('').ops; - expect(ops).toMatchSnapshot(); - }); - - test('parens', () => { - let ops = compile('=(1 + 2)').ops; - expect(ops).toMatchSnapshot(); - - ops = compile('=(1232 + 2) - (3 + 4)').ops; - expect(ops).toMatchSnapshot(); - }); - - test('compiler binary ops', () => { - let ops = compile('=foo + bar + baz + boo').ops; - expect(ops).toMatchSnapshot(); - }); - - test('compiler nested funcs', () => { - let ops = compile('=min(0, number(-20000))').ops; - expect(ops).toMatchSnapshot(); - }); - - test('compiles boolean types', () => { - let ops = compile('=if(true and 1) { 0 } else { 1 } ').ops; - expect(ops).toMatchSnapshot(); - }); - - test('query expressions', () => { - let ops = compile(` - =from transactions - where amount > 0 - select { sum(amount) as a } - `).ops; - expect(ops).toMatchSnapshot(); - }); - - test('query expressions with null', () => { - let ops = compile(` - =from transactions where acct.offbudget = 0 and category = null calculate { count(amount) } - `).ops; - expect(ops).toMatchSnapshot(); - }); - - test('complex query expressions', () => { - let ops = compile(` - =from transactions groupby substr(date, 0, 7) select { substr(date, 0, 7), sum(amount) } - `).ops; - expect(ops).toMatchSnapshot(); - }); - - test('query expressions with field remapping', () => { - let ops = compile(` - =from transactions where category = "50" select { id } - `).ops; - expect(ops).toMatchSnapshot(); - - ops = compile(` - =from transactions where category.name = "foo" select { id } - `).ops; - expect(ops).toMatchSnapshot(); - - ops = compile(` - =from transactions where category = "50" select { id, category.name } - `).ops; - expect(ops).toMatchSnapshot(); - }); - - test('field dependencies', () => { - let sqlDependencies = compile( - '=from transactions where acct.offbudget = 0 and category = null and (description.transfer_acct.offbudget = 1 or description.transfer_acct = null) calculate { count(date) }', - ).sqlDependencies; - - expect(sqlDependencies[0].fields).toMatchSnapshot(); - }); -}); diff --git a/packages/loot-core/src/server/spreadsheet/new/compiler.ts b/packages/loot-core/src/server/spreadsheet/new/compiler.ts deleted file mode 100644 index cfcf8942622fff41678430c94542c699d815b79e..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/compiler.ts +++ /dev/null @@ -1,193 +0,0 @@ -import getSqlFields from './get-sql-fields'; -import * as nodes from './nodes'; -import { - MOV, - CALL, - QUERY, - UOP, - BOP, - REG1, - SP, - VAR, - JUMPF, - JUMPT, - LABEL, -} from './ops'; -import parse from './parser'; -import generateSql from './sqlgen'; - -class Compiler { - src; - scopeName; - binding; - ops; - dependencies; - sqlDependencies; - - constructor() { - this.ops = []; - this.dependencies = []; - this.sqlDependencies = []; - } - - fail(msg, lineno, colno) { - const lines = this.src.split('\n'); - - let space = ''; - for (let i = 0; i < colno; i++) { - space += ' '; - } - - throw new Error( - `[${lineno + 1}, ${colno + 1}] ${msg}:\n${lines[lineno]}\n${space}^`, - ); - } - - resolveVariable(name) { - if (name.indexOf('!') === -1) { - return this.scopeName + '!' + name; - } - return name; - } - - maybePushStack(node, si) { - if (node instanceof nodes.Symbol) { - // There's no need to push anything to the stack since it's a - // direct variable reference. Just store the referenced variable - // and pop the symbol operation off the stack. - const op = this.ops.pop(); - return [si, op[1]]; - } - - this.ops.push([MOV, REG1, SP(si)]); - return [si + 1, SP(si)]; - } - - compileLiteral(node, si) { - this.ops.push([MOV, node.value, REG1]); - } - - compileSymbol(node, si) { - const resolved = this.resolveVariable(node.value); - this.dependencies.push(resolved); - this.ops.push([MOV, VAR(resolved), REG1]); - } - - compileBinOp(node, si) { - this.compile(node.left, si); - // TODO: Get rid of all this and add a second pass which optimizes - // the opcodes. - let left; - [si, left] = this.maybePushStack(node.left, si); - - this.compile(node.right, si + 1); - this.ops.push([BOP, node.op, left, REG1]); - } - - compileUnaryOp(node, si) { - this.compile(node.target, si); - this.ops.push([UOP, node.op, REG1]); - } - - compileFunCall(node, si) { - this.compile(node.callee, si); - let callee; - [si, callee] = this.maybePushStack(node.callee, si); - - const args = node.args.children.map((arg, i) => { - this.compile(arg, si + i); - this.ops.push([MOV, REG1, SP(si + i)]); - return SP(si + i); - }); - - this.ops.push([CALL, callee, args]); - } - - compileQuery(node, si) { - let fields = getSqlFields(node.table, node.where) - .concat(getSqlFields(node.table, node.groupby)) - .concat(...node.select.map(s => getSqlFields(node.table, s.expr))); - - const { sql, where } = generateSql( - node.table, - node.where, - node.groupby, - node.select, - ); - - // TODO: This is a hack, but I'm pretty sure we can get rid of all - // of this. Just need to think through it. - fields = fields.map(f => (f === '__cm.transferId' ? 'category' : f)); - - // Uniquify them - fields = [...new Set(fields)]; - - this.sqlDependencies.push({ table: node.table, where, fields }); - this.ops.push([QUERY, sql, node.calculated]); - } - - compileIf(node, si) { - const L0 = LABEL(); - const L1 = LABEL(); - - this.compile(node.cond, si); - this.ops.push([MOV, REG1, SP(si)]); - - this.ops.push([JUMPF, SP(si), L0]); - this.compile(node.body, si + 1); - - this.ops.push([JUMPT, SP(si), L1]); - L0.resolve(this.ops.length - 1); - this.compile(node.else_, si + 1); - L1.resolve(this.ops.length - 1); - } - - compileRoot(node, si) { - node.children.forEach(node => { - this.compile(node, si); - }); - } - - compile(node, si) { - const method = this['compile' + node.getTypeName()]; - if (!method) { - this.fail( - 'Unknown node type: ' + node.getTypeName(), - node.lineno, - node.colno, - ); - } - return method.call(this, node, si); - } - - compileSource(binding, scopeName, src) { - this.src = src; - this.scopeName = scopeName; - this.binding = binding; - - this.compile(parse(src), 0); - - const resolvedBinding = this.resolveVariable(binding); - - if (this.ops.length !== 0) { - this.ops.push([MOV, REG1, VAR(resolvedBinding)]); - } else { - this.ops.push([MOV, '', VAR(resolvedBinding)]); - } - - return { - ops: this.ops, - dependencies: this.dependencies, - sqlDependencies: this.sqlDependencies, - }; - } -} - -export function compile(src) { - return compileBinding('result', 'generated', src); -} - -export function compileBinding(binding, scopeName, src) { - const compiler = new Compiler(); - return compiler.compileSource(binding, scopeName, src); -} diff --git a/packages/loot-core/src/server/spreadsheet/new/get-sql-fields.ts b/packages/loot-core/src/server/spreadsheet/new/get-sql-fields.ts deleted file mode 100644 index ff71f705cf711690692f3fb859c8c74fddfbb654..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/get-sql-fields.ts +++ /dev/null @@ -1,48 +0,0 @@ -function traverse(expr, fields) { - switch (expr.getTypeName()) { - case 'FunCall': - expr.args.children.map(arg => traverse(arg, fields)); - break; - - case 'Member': - // Right now we only track dependencies on the top-level table, - // and not any of the joined data. This tracks that field itself - // that is joined on, but not the joined data yet. - traverse(expr.object, fields); - break; - - case 'Literal': - break; - - case 'Symbol': - if (fields.indexOf(expr.value) === -1 && expr.value !== 'null') { - fields.push(expr.value); - } - break; - - case 'BinOp': - traverse(expr.left, fields); - traverse(expr.right, fields); - break; - default: - throw new Error('Unhandled node type: ' + expr.getTypeName()); - } -} - -export default function getSqlFields(table, ast) { - let fields: string[] = []; - if (!ast) { - return fields; - } - - traverse(ast, fields); - - // These are implicit fields added by the sql generator. Going to - // revisit how to track all of this. - if (table === 'transactions') { - fields.push('isParent'); - fields.push('tombstone'); - } - - return fields; -} diff --git a/packages/loot-core/src/server/spreadsheet/new/lexer.test.ts b/packages/loot-core/src/server/spreadsheet/new/lexer.test.ts deleted file mode 100644 index c1feaefd439b3a39bcf2b780f4f33cd724ef5262..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/lexer.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import lex from './lexer'; - -function getTokens(tokens) { - const toks = []; - while (!tokens.is_finished()) { - toks.push(tokens.nextToken()); - } - return toks; -} - -test('lexer basic', () => { - const tokens = lex(` - =x !=~ 4 - `); - - expect(getTokens(tokens)).toMatchSnapshot(); -}); diff --git a/packages/loot-core/src/server/spreadsheet/new/lexer.ts b/packages/loot-core/src/server/spreadsheet/new/lexer.ts deleted file mode 100644 index aabd626fef66c51b8955fd7b88c5818bcb369240..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/lexer.ts +++ /dev/null @@ -1,310 +0,0 @@ -const whitespaceChars = new Set(' \n\t\r\u00A0'); -const delimChars = new Set('()[]{}%*-+~/#,:|.<>=!'); -const whitespaceAndDelimChars = new Set([...whitespaceChars, ...delimChars]); -const intChars = new Set('0123456789'); - -const complexOps = new Set(['==', '!=', '<=', '>=', '=~', '!=~']); - -export const TOKEN_STRING = 'string'; -export const TOKEN_WHITESPACE = 'whitespace'; -export const TOKEN_LEFT_PAREN = 'left-paren'; -export const TOKEN_RIGHT_PAREN = 'right-paren'; -export const TOKEN_LEFT_BRACKET = 'left-bracket'; -export const TOKEN_RIGHT_BRACKET = 'right-bracket'; -export const TOKEN_LEFT_CURLY = 'left-curly'; -export const TOKEN_RIGHT_CURLY = 'right-curly'; -export const TOKEN_COMMA = 'comma'; -export const TOKEN_INT = 'int'; -export const TOKEN_FLOAT = 'float'; -export const TOKEN_BOOLEAN = 'boolean'; -export const TOKEN_SYMBOL = 'symbol'; -export const TOKEN_DOT = 'dot'; -export const TOKEN_EXCLAIM = 'exclaim'; -export const TOKEN_OPERATOR = 'operator'; - -function token(type, value, lineno, colno) { - return { - type: type, - value: value, - lineno: lineno, - colno: colno, - }; -} - -class Tokenizer { - colno; - hasCheckedMode; - index; - len; - lineno; - str; - - constructor(str, opts = {}) { - this.str = str; - this.index = 0; - this.len = str.length; - this.lineno = 0; - this.colno = 0; - this.hasCheckedMode = false; - } - - nextToken() { - let lineno = this.lineno; - let colno = this.colno; - let tok; - let cur = this.current(); - - if (this.is_finished()) { - return null; - } else if ((tok = this._extract(whitespaceChars))) { - // We hit some whitespace - return token(TOKEN_WHITESPACE, tok, lineno, colno); - } else if (!this.hasCheckedMode) { - this.hasCheckedMode = true; - - if (cur === '=') { - this.forward(); - cur = this.current(); - return this.nextToken(); - } else { - this.index = this.str.length; - return token(TOKEN_STRING, this.str, lineno, colno); - } - // eslint-disable-next-line rulesdir/typography - } else if (cur === '"' || cur === "'") { - // We've hit a string - return token(TOKEN_STRING, this.parseString(cur), lineno, colno); - } else if (delimChars.has(cur)) { - // We've hit a delimiter (a special char like a bracket) - let type; - - if (complexOps.has(cur + this.next() + this.next(2))) { - cur = cur + this.next() + this.next(2); - this.forward(); - this.forward(); - } else if (complexOps.has(cur + this.next())) { - cur = cur + this.next(); - this.forward(); - } - this.forward(); - - switch (cur) { - case '(': - type = TOKEN_LEFT_PAREN; - break; - case ')': - type = TOKEN_RIGHT_PAREN; - break; - case '[': - type = TOKEN_LEFT_BRACKET; - break; - case ']': - type = TOKEN_RIGHT_BRACKET; - break; - case '{': - type = TOKEN_LEFT_CURLY; - break; - case '}': - type = TOKEN_RIGHT_CURLY; - break; - case ',': - type = TOKEN_COMMA; - break; - case '.': - type = TOKEN_DOT; - break; - case '!': - type = TOKEN_EXCLAIM; - break; - default: - type = TOKEN_OPERATOR; - } - - return token(type, cur, lineno, colno); - } else { - // We are not at whitespace or a delimiter, so extract the - // text and parse it - tok = this._extractUntil(whitespaceAndDelimChars); - - if (tok.match(/^[-+]?[0-9]+$/)) { - if (this.current() === '.') { - this.forward(); - let dec = this._extract(intChars); - return token(TOKEN_FLOAT, tok + '.' + dec, lineno, colno); - } else { - return token(TOKEN_INT, tok, lineno, colno); - } - } else if (tok.match(/^(true|false)$/)) { - return token(TOKEN_BOOLEAN, tok, lineno, colno); - } else if (tok.match(/^(or|and|not)$/)) { - return token(TOKEN_OPERATOR, tok, lineno, colno); - } else if (tok) { - return token(TOKEN_SYMBOL, tok, lineno, colno); - } else { - throw new Error('Unexpected value while parsing: ' + tok); - } - } - } - - parseString(delimiter) { - this.forward(); - - let str = ''; - - while (!this.is_finished() && this.current() !== delimiter) { - let cur = this.current(); - - if (cur === '\\') { - this.forward(); - switch (this.current()) { - case 'n': - str += '\n'; - break; - case 't': - str += '\t'; - break; - case 'r': - str += '\r'; - break; - default: - str += this.current(); - } - this.forward(); - } else { - str += cur; - this.forward(); - } - } - - this.forward(); - return str; - } - - _matches(str) { - if (this.index + str.length > this.len) { - return null; - } - - let m = this.str.slice(this.index, this.index + str.length); - return m === str; - } - - _extractString(str) { - if (this._matches(str)) { - this.index += str.length; - return str; - } - return null; - } - - _extractUntil(chars) { - // Extract all non-matching chars, with the default matching set - // to everything - return this._extractMatching(true, chars || new Set()); - } - - _extract(chars) { - // Extract all matching chars (no default, so charString must be - // explicit) - return this._extractMatching(false, chars); - } - - _extractMatching(breakOnMatch, chars) { - // Pull out characters until a breaking char is hit. - // If breakOnMatch is false, a non-matching char stops it. - // If breakOnMatch is true, a matching char stops it. - - if (this.is_finished()) { - return null; - } - - let matches = chars.has(this.current()); - - // Only proceed if the first character meets our condition - if ((breakOnMatch && !matches) || (!breakOnMatch && matches)) { - let t = this.current(); - this.forward(); - - // And pull out all the chars one at a time until we hit a - // breaking char - let isMatch = chars.has(this.current()); - - while ( - ((breakOnMatch && !isMatch) || (!breakOnMatch && isMatch)) && - !this.is_finished() - ) { - t += this.current(); - this.forward(); - - isMatch = chars.has(this.current()); - } - - return t; - } - - return ''; - } - - is_finished() { - return this.index >= this.len; - } - - forward() { - this.index++; - - if (this.previous() === '\n') { - this.lineno++; - this.colno = 0; - } else { - this.colno++; - } - } - - back() { - this.index--; - - if (this.current() === '\n') { - this.lineno--; - - let idx = this.str.lastIndexOf('\n', this.index - 1); - if (idx === -1) { - this.colno = this.index; - } else { - this.colno = this.index - idx; - } - } else { - this.colno--; - } - } - - // current returns current character - current() { - if (!this.is_finished()) { - return this.str.charAt(this.index); - } - return ''; - } - - next(idx = 1) { - if (this.index + idx < this.str.length) { - return this.str.charAt(this.index + idx); - } - return ''; - } - - // currentStr returns what's left of the unparsed string - currentStr() { - if (!this.is_finished()) { - return this.str.substr(this.index); - } - return ''; - } - - previous() { - return this.str.charAt(this.index - 1); - } -} - -export default function lex(src, opts?: Record<string, unknown>) { - return new Tokenizer(src, opts); -} diff --git a/packages/loot-core/src/server/spreadsheet/new/nodes.ts b/packages/loot-core/src/server/spreadsheet/new/nodes.ts deleted file mode 100644 index 54fa0603a815f22cfe4a9163e89edd60d5088490..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/nodes.ts +++ /dev/null @@ -1,201 +0,0 @@ -class Node { - colno; - fieldNames; - lineno; - - constructor(lineno, colno, fieldNames) { - this.lineno = lineno; - this.colno = colno; - this.fieldNames = fieldNames; - } - - getTypeName() { - return 'Node'; - } - - traverseFields(onEnter, onExit) { - const fieldNames = this.fieldNames; - for (let i = 0; i < fieldNames.length; i++) { - const val = this[fieldNames[i]]; - - if (val instanceof Node) { - const ret = val.traverse(onEnter, onExit); - if (ret) { - this[fieldNames[i]] = ret; - } - } - } - } - - traverse(onEnter, onExit) { - if (onEnter) { - const val = onEnter(this); - if (val === true) { - return; - } else if (val != null) { - return val; - } - } - this.traverseFields(onEnter, onExit); - onExit && onExit(this); - } - - copy() { - const inst = Object.assign( - Object.create(Object.getPrototypeOf(this)), - this, - ); - - for (let i = 0; i < inst.fieldNames.length; i++) { - const field = inst.fieldNames[i]; - if (inst[field] instanceof Node) { - inst[field] = inst[field].copy(); - } - } - - return inst; - } -} - -export class NodeList extends Node { - children; - - constructor(lineno, colno, nodes: unknown[] = []) { - super(lineno, colno, ['children']); - this.children = nodes; - } - - getTypeName() { - return 'NodeList'; - } - - addChild(node) { - this.children.push(node); - } - - traverseFields(onEnter, onExit) { - for (let i = 0; i < this.children.length; i++) { - this.children[i].traverse(onEnter, onExit); - } - } -} - -export class Root extends NodeList { - getTypeName() { - return 'Root'; - } -} - -export class Value extends Node { - value; - - constructor(lineno, colno, value) { - super(lineno, colno, ['value']); - this.value = value ?? null; - } - getTypeName() { - return 'Value'; - } -} -export class UnaryOp extends Node { - op; - target; - - constructor(lineno, colno, op, target) { - super(lineno, colno, ['op', 'target']); - this.op = op ?? null; - this.target = target ?? null; - } - getTypeName() { - return 'UnaryOp'; - } -} -export class BinOp extends Node { - op; - left; - right; - - constructor(lineno, colno, op, left, right) { - super(lineno, colno, ['op', 'left', 'right']); - this.op = op ?? null; - this.left = left ?? null; - this.right = right ?? null; - } - getTypeName() { - return 'BinOp'; - } -} - -export class Literal extends Value { - getTypeName() { - return 'Literal'; - } -} -export class Symbol extends Value { - getTypeName() { - return 'Symbol'; - } -} -export class FunCall extends Node { - callee; - args; - - constructor(lineno, colno, callee, args) { - super(lineno, colno, ['callee', 'args']); - this.callee = callee ?? null; - this.args = args ?? null; - } - getTypeName() { - return 'FunCall'; - } -} - -export class Member extends Node { - object; - property; - - constructor(lineno, colno, object, property) { - super(lineno, colno, ['object', 'property']); - this.object = object ?? null; - this.property = property ?? null; - } - getTypeName() { - return 'Member'; - } -} - -export class Query extends Node { - table; - select; - where; - groupby; - calculated; - - constructor(lineno, colno, table, select, where, groupby, calculated) { - super(lineno, colno, ['table', 'select', 'where', 'groupby', 'calculated']); - this.table = table ?? null; - this.select = select ?? null; - this.where = where ?? null; - this.groupby = groupby ?? null; - this.calculated = calculated ?? null; - } - getTypeName() { - return 'Query'; - } -} - -export class If extends Node { - cond; - body; - else_; - - constructor(lineno, colno, cond, body, else_) { - super(lineno, colno, ['cond', 'body', 'else_']); - this.cond = cond ?? null; - this.body = body ?? null; - this.else_ = else_ ?? null; - } - getTypeName() { - return 'If'; - } -} diff --git a/packages/loot-core/src/server/spreadsheet/new/ops.ts b/packages/loot-core/src/server/spreadsheet/new/ops.ts deleted file mode 100644 index 897139ad7e51b0dc794f0b91cdb0cd8151f5378c..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/ops.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const MOV = Symbol('mov'); -export const CALL = Symbol('call'); -export const QUERY = Symbol('query'); -export const UOP = Symbol('uop'); -export const BOP = Symbol('bop'); -export const JUMPF = Symbol('jumpf'); -export const JUMPT = Symbol('jumpt'); - -export const REG1 = { type: '__reg', index: 1 }; - -export function SP(n) { - return { type: '__stack', index: n }; -} - -export function VAR(name) { - return { type: '__var', name: name }; -} - -export function LABEL() { - let idx = null; - return { - get() { - if (idx === null) { - throw new Error('Attempted access of unresolved label'); - } - return idx; - }, - - resolve(n) { - idx = n; - }, - }; -} diff --git a/packages/loot-core/src/server/spreadsheet/new/parser.ts b/packages/loot-core/src/server/spreadsheet/new/parser.ts deleted file mode 100644 index 635b783f0b4994f981ccbfa964ec7a18670a4acc..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/parser.ts +++ /dev/null @@ -1,462 +0,0 @@ -import lex, * as types from './lexer'; -import * as nodes from './nodes'; - -function nextToken(state, withWhitespace?: boolean) { - let tok; - let { peeked, tokens } = state; - - if (peeked) { - if (!withWhitespace && peeked.type === types.TOKEN_WHITESPACE) { - state.peeked = null; - } else { - tok = state.peeked; - state.peeked = null; - return tok; - } - } - - tok = tokens.nextToken(); - - if (!withWhitespace) { - while (tok && tok.type === types.TOKEN_WHITESPACE) { - tok = tokens.nextToken(); - } - } - - return tok; -} - -function peekToken(state) { - state.peeked = state.peeked || nextToken(state); - return state.peeked; -} - -function pushToken(state, tok) { - if (state.peeked) { - throw new Error('pushToken: can only push one token on between reads'); - } - state.peeked = tok; -} - -function fail(state, msg, lineno?: number, colno?: number) { - if (!peekToken(state)) { - throw new Error(msg + '\n\nSource:\n' + state.src + '\n'); - } - if (lineno === undefined || colno === undefined) { - const tok = peekToken(state); - lineno = tok.lineno; - colno = tok.colno; - } - - const lines = state.src.split('\n'); - - let space = ''; - for (let i = 0; i < colno; i++) { - space += ' '; - } - - throw new Error( - `[${lineno + 1}, ${colno + 1}] ${msg}:\n${lines[lineno]}\n${space}^`, - ); -} - -function skip(state, type) { - let tok = nextToken(state); - if (!tok || tok.type !== type) { - pushToken(state, tok); - return false; - } - return true; -} - -function expectValue(state, type, value) { - let tok = nextToken(state); - if (tok.type !== type || tok.value !== value) { - fail( - state, - 'expected ' + value + ', got ' + tok.value, - tok.lineno, - tok.colno, - ); - } - return tok; -} - -function expect(state, type) { - let tok = nextToken(state); - if (tok.type !== type) { - fail( - state, - 'expected ' + type + ', got ' + tok.type, - tok.lineno, - tok.colno, - ); - } - return tok; -} - -function skipValue(state, type, val) { - let tok = nextToken(state); - if (!tok || tok.type !== type || tok.value !== val) { - pushToken(state, tok); - return false; - } - return true; -} - -function skipSymbol(state, val) { - return skipValue(state, types.TOKEN_SYMBOL, val); -} - -function parseExpression(state) { - return parseOr(state); -} - -function parseOr(state) { - let left = parseAnd(state); - while (skipValue(state, types.TOKEN_OPERATOR, 'or')) { - const right = parseAnd(state); - left = new nodes.BinOp(left.lineno, left.colno, 'or', left, right); - } - return left; -} - -function parseAnd(state) { - let left = parseNot(state); - while (skipValue(state, types.TOKEN_OPERATOR, 'and')) { - const right = parseNot(state); - left = new nodes.BinOp(left.lineno, left.colno, 'and', left, right); - } - return left; -} - -function parseNot(state) { - let left = parseCompare(state); - while (skipValue(state, types.TOKEN_OPERATOR, 'not')) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const right = parseCompare(state); - left = new nodes.UnaryOp(left.lineno, left.colno, 'not', parseNot(state)); - } - return left; -} - -function parseCompare(state) { - let compareOps = ['=', '!=', '<', '>', '<=', '>=', '=~', '!=~']; - let node = parseAdd(state); - - while (1) { - let tok = nextToken(state); - - if (!tok) { - break; - } else if (compareOps.indexOf(tok.value) !== -1) { - node = new nodes.BinOp( - tok.lineno, - tok.colno, - tok.value, - node, - parseAdd(state), - ); - } else { - pushToken(state, tok); - break; - } - } - - return node; -} - -function parseAdd(state) { - let left = parseSub(state); - while (skipValue(state, types.TOKEN_OPERATOR, '+')) { - const right = parseSub(state); - left = new nodes.BinOp(left.lineno, left.colno, '+', left, right); - } - return left; -} - -function parseSub(state) { - let left = parseMul(state); - while (skipValue(state, types.TOKEN_OPERATOR, '-')) { - const right = parseMul(state); - left = new nodes.BinOp(left.lineno, left.colno, '-', left, right); - } - return left; -} - -function parseMul(state) { - let left = parseDiv(state); - while (skipValue(state, types.TOKEN_OPERATOR, '*')) { - const right = parseDiv(state); - left = new nodes.BinOp(left.lineno, left.colno, '*', left, right); - } - return left; -} - -function parseDiv(state) { - let left = parseUnary(state); - while (skipValue(state, types.TOKEN_OPERATOR, '/')) { - const right = parseUnary(state); - left = new nodes.BinOp(left.lineno, left.colno, '/', left, right); - } - return left; -} - -function parseUnary(state) { - let tok = peekToken(state); - - if (skipValue(state, types.TOKEN_OPERATOR, '-')) { - const nextTok = peekToken(state); - if (nextTok.type === types.TOKEN_INT) { - const number = parseInt(nextToken(state).value); - return new nodes.Literal(tok.lineno, tok.colno, -number); - } else if (nextTok.type === types.TOKEN_FLOAT) { - const number = parseFloat(nextToken(state).value); - return new nodes.Literal(tok.lineno, tok.colno, -number); - } - - return new nodes.UnaryOp(tok.lineno, tok.colno, '-', parseUnary(state)); - } - return parsePrimary(state); -} - -function parsePrimary(state) { - let tok = nextToken(state); - let val: number | boolean | null = null; - - if (!tok) { - fail(state, 'expected expression, got end of file'); - } else if (tok.type === types.TOKEN_STRING) { - val = tok.value; - } else if (tok.type === types.TOKEN_INT) { - val = parseInt(tok.value, 10); - } else if (tok.type === types.TOKEN_FLOAT) { - val = parseFloat(tok.value); - } else if (tok.type === types.TOKEN_BOOLEAN) { - if (tok.value === 'true') { - val = true; - } else if (tok.value === 'false') { - val = false; - } - } - - if (val !== null) { - return new nodes.Literal(tok.lineno, tok.colno, val); - } else if (tok.type === types.TOKEN_SYMBOL) { - if (tok.value === 'from') { - return parseQueryExpression(state); - } else if (tok.value === 'if') { - return parseIfExpression(state); - } - - return parsePostfix( - state, - new nodes.Symbol(tok.lineno, tok.colno, tok.value), - ); - } else if (tok.type === types.TOKEN_LEFT_PAREN) { - const node = parseExpression(state); - expect(state, types.TOKEN_RIGHT_PAREN); - return node; - } - - fail(state, 'Unexpected token: ' + tok.value, tok.lineno, tok.colno); -} - -function parseIfExpression(state) { - const tok = expect(state, types.TOKEN_LEFT_PAREN); - const cond = parseExpression(state); - expect(state, types.TOKEN_RIGHT_PAREN); - - expect(state, types.TOKEN_LEFT_CURLY); - const body = parseExpression(state); - expect(state, types.TOKEN_RIGHT_CURLY); - - let else_; - if (skipSymbol(state, 'else')) { - expect(state, types.TOKEN_LEFT_CURLY); - else_ = parseExpression(state); - expect(state, types.TOKEN_RIGHT_CURLY); - } - - return new nodes.If(tok.lineno, tok.colno, cond, body, else_); -} - -function parseQueryExpression(state) { - // The `from` keyword has already been parsed - const tok = expect(state, types.TOKEN_SYMBOL); - const table = tok.value; - - let where = null; - if (skipSymbol(state, 'where')) { - where = parseQuerySubExpression(state); - } - - let groupby = null; - if (skipSymbol(state, 'groupby')) { - groupby = parseQuerySubExpression(state); - } - - let select: Array<{ expr: unknown; as?: unknown }> = []; - let calculated; - - if (skipSymbol(state, 'select')) { - let checkComma = false; - calculated = false; - - expectValue(state, types.TOKEN_LEFT_CURLY, '{'); - - while (!skipValue(state, types.TOKEN_RIGHT_CURLY, '}')) { - const tok = peekToken(state); - - if (checkComma && !skip(state, types.TOKEN_COMMA)) { - fail( - state, - 'Unexpected token in query select: ' + tok.value, - tok.lineno, - tok.colno, - ); - } - - const expr = parseQuerySubExpression(state); - let as = null; - - if (skipSymbol(state, 'as')) { - const tok = expect(state, types.TOKEN_SYMBOL); - as = tok.value; - } - - select.push({ expr, as }); - - checkComma = true; - } - } else if (skipSymbol(state, 'calculate')) { - calculated = true; - - expectValue(state, types.TOKEN_LEFT_CURLY, '{'); - select.push({ expr: parseQuerySubExpression(state) }); - - if (!skipValue(state, types.TOKEN_RIGHT_CURLY, '}')) { - fail(state, 'Only one expression allowed for `calculate`'); - } - } else { - fail(state, 'Expected either the `select` or `calculate` keyword'); - } - - return new nodes.Query( - tok.lineno, - tok.colno, - table, - select, - where, - groupby, - calculated, - ); -} - -function parseQuerySubExpression(state) { - const node = parseExpression(state); - return node; -} - -function parsePostfix(state, node) { - let tok; - - while ((tok = nextToken(state))) { - if (tok.type === types.TOKEN_LEFT_PAREN) { - pushToken(state, tok); - let args = parseArgs(state); - node = new nodes.FunCall(tok.lineno, tok.colno, node, args); - } else if (tok.type === types.TOKEN_DOT) { - const val = nextToken(state); - node = new nodes.Member( - tok.lineno, - tok.colno, - node, - new nodes.Literal(val.lineno, val.colno, val.value), - ); - } else if (tok.type === types.TOKEN_EXCLAIM) { - const name = nextToken(state); - if (name.type !== types.TOKEN_SYMBOL) { - fail( - state, - 'Expected cell name in sheet reference', - name.lineno, - name.colno, - ); - } - - return new nodes.Symbol( - node.lineno, - node.colno, - node.value + '!' + name.value, - ); - } else { - pushToken(state, tok); - break; - } - } - - return node; -} - -function parseArgs(state) { - let tok = peekToken(state); - - if (tok.type !== types.TOKEN_LEFT_PAREN) { - fail(state, 'Expected arguments', tok.lineno, tok.colno); - } - - nextToken(state); - - let args = new nodes.NodeList(tok.lineno, tok.colno); - let checkComma = false; - - while (1) { - tok = peekToken(state); - if (tok.type === types.TOKEN_RIGHT_PAREN) { - nextToken(state); - break; - } - - if (checkComma && !skip(state, types.TOKEN_COMMA)) { - fail( - state, - 'Expected comma after function argument', - tok.lineno, - tok.colno, - ); - } - - args.addChild(parseExpression(state)); - checkComma = true; - } - - return args; -} - -export default function parse(src) { - let state = { - src: src, - tokens: lex(src), - peeked: null, - }; - - if (state.tokens.is_finished()) { - // If it's an empty string, return nothing - return new nodes.Root(0, 0, []); - } else { - const expr = parseExpression(state); - - const tok = nextToken(state); - if (tok) { - fail( - state, - 'Unexpected token after expression: ' + tok.value, - tok.lineno, - tok.colno, - ); - } - - return new nodes.Root(0, 0, [expr]); - } -} diff --git a/packages/loot-core/src/server/spreadsheet/new/sqlconvert.ts b/packages/loot-core/src/server/spreadsheet/new/sqlconvert.ts deleted file mode 100644 index 4316f1e6f940b328670b5f36bc73f5fbf149a791..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/sqlconvert.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { SCHEMA_PATHS } from './sqlgen'; - -export default function convert(table, item) { - if (SCHEMA_PATHS[table]) { - let fields = SCHEMA_PATHS[table]; - let updates = {}; - Object.keys(item).forEach(k => { - let mappedField = fields[k] && fields[k].field; - - if (mappedField) { - updates[k] = item[mappedField]; - } - }); - - return { ...item, ...updates }; - } - return item; -} diff --git a/packages/loot-core/src/server/spreadsheet/new/sqlgen.ts b/packages/loot-core/src/server/spreadsheet/new/sqlgen.ts deleted file mode 100644 index 4e4cd8636c1311997a46c1e552ca97f7b76163f6..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/sqlgen.ts +++ /dev/null @@ -1,303 +0,0 @@ -import * as nodes from './nodes'; - -type Lookup = { field: string; tableId?: string }; - -let _uid = 0; -function resetUid() { - _uid = 0; -} - -function uid() { - _uid++; - return 't' + _uid; -} - -function fail(node, message) { - const err = new Error(message); - // @ts-expect-error We should use error.cause to pass node info - err.node = node; - throw err; -} - -function generateExpression(expr) { - if (typeof expr === 'string') { - // eslint-disable-next-line rulesdir/typography - return `"${expr}"`; - } else if (typeof expr === 'number') { - return expr; - } - - switch (expr.getTypeName()) { - case 'FunCall': - return ( - generateExpression(expr.callee) + - '(' + - expr.args.children.map(node => generateExpression(node)).join(',') + - ')' - ); - case 'Member': - return ( - generateExpression(expr.object) + - '.' + - generateExpression(expr.property) - ); - case 'BinOp': - const left = generateExpression(expr.left); - let str; - - if ( - expr.op === '=' && - expr.right.getTypeName() === 'Symbol' && - expr.right.value === 'null' - ) { - str = left + ' IS NULL'; - } else { - const right = generateExpression(expr.right); - - switch (expr.op) { - case '=~': - str = `${left} LIKE ${right}`; - break; - case '!=~': - str = `${left} NOT LIKE ${right}`; - break; - default: - str = `${left} ${expr.op} ${right}`; - } - } - - return '(' + str + ')'; - case 'Literal': - if (typeof expr.value === 'string') { - // eslint-disable-next-line rulesdir/typography - return `"${expr.value}"`; - } - return expr.value; - case 'Symbol': - // if (expr.value.indexOf('!') !== -1) { - // fail(expr, 'SQL variable cannot contain cell lookup'); - // } - return expr.value; - default: - throw new Error('Unknown query node: ' + expr.getTypeName()); - } -} - -function transformColumns(node, implicitTable) { - let transformed = node.traverse(n => { - if (n instanceof nodes.Symbol) { - let table = implicitTable; - let field = n.value; - - if (SCHEMA_PATHS[table] && SCHEMA_PATHS[table][field]) { - let info = SCHEMA_PATHS[table][field]; - if (info.field) { - // Map the field onto something else - return new nodes.Symbol(n.lineno, n.colno, info.field); - } - } - } - }); - - return transformed || node; -} - -function transformLookups(node, implicitTable) { - let paths: Lookup[][] = []; - - const transformed = node.traverse(n => { - if (n instanceof nodes.Member) { - let currentNode = n; - - let lookups: Lookup[] = []; - while (currentNode instanceof nodes.Member) { - if (!(currentNode.property instanceof nodes.Value)) { - fail(currentNode, 'Invalid syntax for SQL reference'); - } - - lookups.push({ field: currentNode.property.value }); - currentNode = currentNode.object; - } - - // @ts-expect-error Node refinement missing - if (!(currentNode instanceof nodes.Symbol)) { - fail(currentNode, 'Invalid syntax for SQL reference'); - } - // @ts-expect-error Node refinement missing - lookups.push({ field: currentNode.value }); - lookups.reverse(); - - lookups = lookups.map((lookup, idx) => { - return { - field: lookup.field, - tableId: uid(), - }; - }); - - let table = implicitTable; - - // Skip the last field as we don't want to resolve to that - // table. The syntax to emit is `table.field`. - for (let i = 0; i < lookups.length - 1; i++) { - const lookup = lookups[i]; - - if (!SCHEMA_PATHS[table]) { - const err = new Error( - `Table “${table}†not joinable for field “${lookup}â€`, - ); - // @ts-expect-error We should use error.cause to pass node info - err.node = node; - throw err; - } - if (!SCHEMA_PATHS[table][lookup.field]) { - const err = new Error( - `Unknown field “${lookup}†on table “${table}â€`, - ); - // @ts-expect-error We should use error.cause to pass node info - err.node = node; - throw err; - } - - table = SCHEMA_PATHS[table][lookup.field].table; - } - - paths.push(lookups); - - let tableId = lookups[lookups.length - 2].tableId; - let field = lookups[lookups.length - 1].field; - - return new nodes.Member( - node.lineno, - node.colno, - new nodes.Symbol(node.lineno, node.colno, tableId), - new nodes.Symbol(node.lineno, node.colno, field), - ); - } - }); - - return { paths, node: transformed || node }; -} - -export default function generate(table, where, groupby, select) { - // Figure out the dep tables here. Return the SQL and dependent - // tables - let allPaths: Lookup[][] = []; - - resetUid(); - - if (!tables[table]) { - throw new Error('Table not found: ' + table); - } - - const selectStr = select - .map(s => { - let { paths, node } = transformLookups(s.expr, table); - let as = s.as; - allPaths = allPaths.concat(paths); - - let newNode = transformColumns(node, table); - - // If the selected field was transformed, select it as the - // original name - if (node !== newNode && node instanceof nodes.Symbol && !as) { - as = node.value; - } - - const exprStr = generateExpression(newNode); - return as ? `${exprStr} as ${as}` : exprStr; - }) - .join(', '); - - let whereStr = ''; - let whereTransformed; - if (where) { - let { paths, node } = transformLookups(where, table); - allPaths = allPaths.concat(paths); - whereTransformed = node.copy(); - - // Where clauses provide a special hook to map a column onto - // something different, so you can represent something more - // complex internally. You are still required to provide the - // original name somehow; all other references use the original - // name, so make sure you do `JOIN table <original-name>` or - // something like that. - node = transformColumns(node, table); - - whereStr = ' WHERE (' + generateExpression(node) + ')'; - } - - let groupByStr = ''; - if (groupby) { - let { paths, node } = transformLookups(groupby, table); - allPaths = allPaths.concat(paths); - groupByStr = ' GROUP BY ' + generateExpression(node); - } - - let dependencies: string[] = []; - let joins: string[] = []; - - allPaths.forEach(path => { - let currentTable = { name: table, id: table }; - for (let i = 0; i < path.length - 1; i++) { - let lookup = path[i]; - let meta = SCHEMA_PATHS[currentTable.name][lookup.field]; - - if (meta.sql) { - joins.push(meta.sql(lookup.tableId)); - } else { - joins.push( - `LEFT JOIN ${meta.table} ${lookup.tableId} ON ${lookup.tableId}.id = ${currentTable.id}.${lookup.field}`, - ); - } - - if (dependencies.indexOf(meta.table) === -1) { - dependencies.push(meta.table); - } - - currentTable = { name: meta.table, id: lookup.tableId }; - } - }); - - const sql = - tables[table](selectStr, whereStr, joins.join('\n')) + ' ' + groupByStr; - - return { - sql, - where: whereTransformed, - dependencies, - }; -} - -export const SCHEMA_PATHS = { - transactions: { - category: { - table: 'categories', - sql: id => `LEFT JOIN categories ${id} ON __cm.transferId = ${id}.id`, - field: '__cm.transferId', - }, - acct: { table: 'accounts' }, - description: { table: 'payees' }, - }, - payees: { - transfer_acct: { table: 'accounts' }, - }, - accounts: { - bank: { table: 'banks' }, - }, -}; - -const tables = { - transactions: (select, where, join) => { - // Never take into account parent split transactions. Their - // children should sum up to be equal to it - // prettier-ignore - let whereStr = `${where === '' ? 'WHERE' : where + ' AND'} transactions.isParent = 0 AND transactions.tombstone = 0`; - - return ` - SELECT ${select} FROM transactions - LEFT JOIN category_mapping __cm ON __cm.id = transactions.category - ${join} - ${whereStr} - `; - }, -}; diff --git a/packages/loot-core/src/server/spreadsheet/new/vm.test.ts b/packages/loot-core/src/server/spreadsheet/new/vm.test.ts deleted file mode 100644 index c03fa6364672928dd25a06c40a62652c721180f6..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/vm.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { unresolveName } from '../util'; - -import VM from './vm'; - -const db = { - runQuery: sql => { - return Promise.resolve([{ 'sum(t.amount)': 1000 }]); - }, -}; - -function makeScopes(vars) { - return { - getVariable: resolvedName => { - const { name } = unresolveName(resolvedName); - - if (vars[resolvedName] !== undefined) { - return vars[resolvedName]; - } else if (vars[name] !== undefined) { - return vars[name]; - } - - throw new Error(`“${resolvedName}†is not defined`); - }, - - setVariable: (name, value) => { - vars[name] = value; - }, - - getAll: () => vars, - }; -} - -function run(src, vars = {}) { - const scopes = makeScopes(vars); - const vm = new VM(db, scopes); - - return new Promise(resolve => { - vm.runSource(src, () => { - expect(scopes.getAll()).toMatchSnapshot(); - resolve(undefined); - }); - }); -} - -test('vm basic', async () => { - return run(`=-(1 + 2 + 3)`, { - number: x => { - return x; - }, - firstValue: arr => { - return arr[0]['sum(t.amount)']; - }, - }); -}); - -test('vm boolean types', async () => { - return run('=if(true and (1 + 2 + 3 - 5)) { 0 } else { 1 } '); -}); diff --git a/packages/loot-core/src/server/spreadsheet/new/vm.ts b/packages/loot-core/src/server/spreadsheet/new/vm.ts deleted file mode 100644 index 8fc2e0b32f69d039d4ef179160c8fccead3ec4d1..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/new/vm.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { compile } from './compiler'; -import { MOV, CALL, QUERY, UOP, BOP, JUMPF, JUMPT } from './ops'; - -export default class VM { - _onFinish; - db; - ops; - paused; - pc; - reg1; - scopes; - stack; - - constructor(db, scopes) { - this.stack = new Array(1000); - this.reg1 = null; - this.pc = 0; - this.db = db; - this.scopes = scopes; - } - - get(ref) { - if (ref && ref.type) { - if (ref.type === '__reg') { - return this.reg1; - } else if (ref.type === '__var') { - return this.scopes.getVariable(ref.name); - } else if (ref.type === '__stack') { - return this.stack[ref.index]; - } - } - - return ref; - } - - set(ref, value) { - if (ref && ref.type) { - if (ref.type === '__reg') { - this.reg1 = this.get(value); - } else if (ref.type === '__var') { - this.scopes.setVariable(ref.name, this.get(value)); - } else if (ref.type === '__stack') { - this.stack[ref.index] = this.get(value); - } - } - } - - binaryOp(op, left, right) { - switch (op) { - case '+': - // TODO: Enforce these to be numbers - this.reg1 = this.get(left) + this.get(right); - break; - case '-': - this.reg1 = this.get(left) - this.get(right); - break; - case '*': - this.reg1 = this.get(left) * this.get(right); - break; - case '/': - this.reg1 = this.get(left) / this.get(right); - break; - case 'and': - this.reg1 = this.get(left) && this.get(right); - break; - case 'or': - this.reg1 = this.get(left) || this.get(right); - break; - default: - throw new Error('Unimplemented operator: ' + op); - } - } - - unaryOp(op, target) { - switch (op) { - case '-': - this.reg1 = -this.get(target); - break; - default: - throw new Error('Unimplemented operator: ' + op); - } - } - - call(callee, args) { - const func = this.get(callee); - this.reg1 = func.apply( - null, - args.map(arg => this.get(arg)), - ); - } - - query(sql, calculated) { - this.pause( - this.db.runQuery(sql, [], true).then(res => { - if (calculated) { - const keys = Object.keys(res[0]); - return res[0][keys[0]]; - } - return res; - }), - 'Running sql: ' + sql, - ); - } - - jump(value, loc, { test }) { - const result = this.get(value); - const falsy = result === false || result === 0 || result === ''; - - if ((test === 'true' && !falsy) || (test === 'false' && falsy)) { - this.pc = loc.get(); - } - } - - pause(promise, activityName) { - this.paused = true; - - promise.then( - val => { - this.resume(val); - }, - err => { - console.log('VM caught error during activity: ' + activityName); - console.log(err); - this.resume(null); - }, - ); - } - - resume(val) { - this.reg1 = val; - this.paused = false; - this._run(); - } - - _run() { - while (this.pc < this.ops.length) { - const op = this.ops[this.pc]; - - switch (op[0]) { - case MOV: - this.set(op[2], op[1]); - break; - case CALL: - this.call(op[1], op[2]); - break; - case QUERY: - this.query(op[1], op[2]); - break; - case BOP: - this.binaryOp(op[1], op[2], op[3]); - break; - case UOP: - this.unaryOp(op[1], op[2]); - break; - case JUMPF: - this.jump(op[1], op[2], { test: 'false' }); - break; - case JUMPT: - this.jump(op[1], op[2], { test: 'true' }); - break; - default: - throw new Error('Unimplemented opcode: ' + op[0].toString()); - } - - this.pc++; - - if (this.paused) { - break; - } - } - - if (this.pc === this.ops.length && this._onFinish) { - this._onFinish(this.reg1); - } - } - - onFinish(func) { - this._onFinish = func; - } - - run(ops, onFinish) { - this.pc = 0; - this.ops = ops; - this._onFinish = onFinish; - this._run(); - return this.reg1; - } - - runSource(src, onFinish) { - const { ops } = compile(src); - return this.run(ops, onFinish); - } -} diff --git a/packages/loot-core/src/server/spreadsheet/sqlinterp.test.ts b/packages/loot-core/src/server/spreadsheet/sqlinterp.test.ts deleted file mode 100644 index 7f9eb2b7cc5f2c6b84548047992e76b90d3fbdf5..0000000000000000000000000000000000000000 --- a/packages/loot-core/src/server/spreadsheet/sqlinterp.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { compile } from './new/compiler'; -import sqlinterp from './sqlinterp'; - -test('sql interpretation works', async () => { - const transJan = { - date: 20170106, - amount: -5000, - acct: 'boa', - category: 1, - }; - const transFeb = { - date: 20170215, - amount: -7620, - acct: 'boa', - category: 1, - }; - - const { sqlDependencies } = compile(` - =from transactions - where date >= 20170101 and date <= 20170131 and - category = 1 - select { amount } - `); - const where = sqlDependencies[0].where; - - expect(sqlinterp(where, transJan, 'transactions')).toBe(true); - expect(sqlinterp(where, transFeb, 'transactions')).toBe(false); -}); diff --git a/packages/loot-core/src/server/spreadsheet/util.ts b/packages/loot-core/src/server/spreadsheet/util.ts index cc69f85894231d1cf3d01601282702b91d39949d..92709cffa52871fe1c75bb6e955fbc7a66ca38dc 100644 --- a/packages/loot-core/src/server/spreadsheet/util.ts +++ b/packages/loot-core/src/server/spreadsheet/util.ts @@ -12,28 +12,3 @@ export function unresolveName(name) { export function resolveName(sheet, name) { return sheet + '!' + name; } - -export function resolveNamesAsObjects(sheets) { - const cells = {}; - Object.keys(sheets).forEach(sheetName => { - const sheet = sheets[sheetName]; - - Object.keys(sheet).forEach(name => { - const expr = sheet[name]; - cells[resolveName(sheetName, name)] = expr; - }); - }); - return cells; -} - -export function resolveNamesAsArrays(sheets) { - const cells = []; - Object.keys(sheets).forEach(sheetName => { - const sheet = sheets[sheetName]; - - sheet.forEach(name => { - cells.push(resolveName(sheetName, name)); - }); - }); - return cells; -} diff --git a/packages/loot-core/src/shared/errors.ts b/packages/loot-core/src/shared/errors.ts index 56ee3fcb6a074920f5af6c6e5d36203ed9edc2ea..0fec6f41676c776dd9a797d7ef05e807cf9efa75 100644 --- a/packages/loot-core/src/shared/errors.ts +++ b/packages/loot-core/src/shared/errors.ts @@ -76,19 +76,6 @@ export function getTestKeyError({ reason }) { } } -export function getSubscribeError({ reason }) { - switch (reason) { - case 'network': - return 'Unable to reach the server. Check your internet connection'; - case 'exists': - return 'An account with that email already exists. Did you mean to login?'; - case 'invalid-email': - return 'Invalid email'; - default: - return 'An error occurred. Please try again later.'; - } -} - export function getSyncError(error, id) { if (error === 'out-of-sync-migrations' || error === 'out-of-sync-data') { return 'This budget cannot be loaded with this version of the app.'; diff --git a/packages/loot-core/src/shared/rules.ts b/packages/loot-core/src/shared/rules.ts index 2764becb9c5b9740409aec9ee18679c558552bba..93b2727ff9d54da234c2d0f6595faf7aa0ff9385 100644 --- a/packages/loot-core/src/shared/rules.ts +++ b/packages/loot-core/src/shared/rules.ts @@ -25,19 +25,6 @@ export const TYPE_INFO = { }, }; -export type FieldTypes = { - imported_payee: string; - payee: string; - date: string; - notes: string; - amount: number; - amountInflow: number; - amountOutfow: number; - category: string; - account: string; - cleared: boolean; -}; - export const FIELD_TYPES = new Map( Object.entries({ imported_payee: 'string', diff --git a/packages/loot-core/src/shared/util.ts b/packages/loot-core/src/shared/util.ts index 008278f4a17b533103d6ff2f365841c65fcb8037..3f726ea1d9897ae47adb3ab3629e5097561a2e94 100644 --- a/packages/loot-core/src/shared/util.ts +++ b/packages/loot-core/src/shared/util.ts @@ -1,50 +1,7 @@ -export function cleanUUID(uuid) { - return uuid.replace(/-/g, ''); -} - export function last(arr) { return arr[arr.length - 1]; } -export function mergeObjects(objects) { - return Object.assign.apply(null, [{}, ...objects]); -} - -export function composeCellChanges(objects) { - const merged = {}; - Object.keys(objects).forEach(key => { - if (merged[key]) { - merged[key] = { ...merged[key], ...objects[key] }; - } else { - merged[key] = objects[key]; - } - }); -} - -export function flattenArray(arrays) { - return Array.prototype.concat.apply([], arrays); -} - -export function shallowEqual(a, b) { - if (a === b) { - return true; - } - - let numKeysA = 0, - numKeysB = 0, - key; - for (key in b) { - numKeysB++; - if (!a.hasOwnProperty(key) || a[key] !== b[key]) { - return false; - } - } - for (key in a) { - numKeysA++; - } - return numKeysA === numKeysB; -} - export function getChangedValues(obj1, obj2) { // Keep the id field because this is mostly used to diff database // objects @@ -132,19 +89,6 @@ export function groupBy(data, field, mapper?: (v: unknown) => unknown) { return res; } -export function groupBySingle(data, field, mapper) { - let res = new Map(); - for (let i = 0; i < data.length; i++) { - let item = data[i]; - let key = item[field]; - if (res.has(key)) { - throw new Error('groupBySingle found conflicting key: ' + key); - } - res.set(key, mapper ? mapper(item) : data[i]); - } - return res; -} - // This should replace the existing `groupById` function, since a // `Map` is better, but we can't swap it out because `Map` has a // different API and we need to go through and update everywhere that @@ -192,14 +136,6 @@ export function groupById(data) { return res; } -export function debugMemoFailure(prevProps, nextProps) { - let changed = getChangedValues(prevProps, nextProps); - if (changed !== null) { - console.log(changed); - } - return changed === null; -} - export function setIn(map, keys, item) { for (let i = 0; i < keys.length; i++) { let key = keys[i]; @@ -228,11 +164,6 @@ export function getIn(map, keys) { return item; } -// Useful for throwing exception from expressions -export function throwError(err) { - throw err; -} - export function fastSetMerge(set1, set2) { let finalSet = new Set(set1); let iter = set2.values(); @@ -339,10 +270,6 @@ export function toRelaxedNumber(value) { return integerToAmount(currencyToInteger(value) || 0); } -export function toRelaxedInteger(value) { - return stringToInteger(value) || 0; -} - export function integerToCurrency(n) { return numberFormat.formatter.format(safeNumber(n) / 100); } @@ -406,15 +333,3 @@ export function looselyParseAmount(amount) { return safeNumber(parseFloat(left + '.' + right)); } - -export function semverToNumber(str) { - return parseInt( - '1' + - str - .split('.') - .map(x => { - return ('000' + x.replace(/[^0-9]/g, '')).slice(-3); - }) - .join(''), - ); -} diff --git a/upcoming-release-notes/1158.md b/upcoming-release-notes/1158.md new file mode 100644 index 0000000000000000000000000000000000000000..3bd127418993fc90b8d01ffa583b3afc5d5fce8e --- /dev/null +++ b/upcoming-release-notes/1158.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [Shazib] +--- + +Remove unused/legacy code from codebase