GunJack
GunJack

Reputation: 1978

Math expression from a string in dart

This is the code I am using to separate a string into variables and operators. So that they can be operated upon.

String expression = "1567x356-234+908/56x12"; int lastPos = 0;

  List<double> exprVar = new List<double>();
  List<String> operator = new List<String>();

  for (int i =0; i < expression.length; i++) {

      if (expression[i] == "+" || expression[i] == "-" || expression[i] == "/" || expression[i] == "x") {

        operator.add(expression[i]);

        exprVar.add(double.parse(expression.substring(lastPos, i)));
        lastPos = i+1;
      }

    if (i == expression.length - 1) {
        exprVar.add(double.parse(expression.substring(lastPos, i+1)));
      }

  }

  print("The Numbers are is: $exprVar");
  print("The operators are: $operator");

I have two questions:

  1. Am I reinventing the wheel here? Is there a String library function in dart that I am unaware of that might make this code more convenient?

  2. Now that I have the numbers and operator, do I have to write code to determine the order of precedence of operators or can I make a giant single line expression and the processor would solve it for me?

Upvotes: 5

Views: 7030

Answers (3)

mezoni
mezoni

Reputation: 11210

An example of how to do this very simply.
You can check how it works in DartPad.

Dart calculator generated by PEG generator. https://pub.dev/packages/peg

You can also generate a parser, just the way you need it.
Grammar from which a parser of 500 lines of code is generated.
Completely ready for work.
Completely ready for modification.
The grammar includes (in its definition) a small example of using the parser.

More information can be found here.
https://pub.dev/packages/peg
https://github.com/mezoni/peg

%{
// ignore_for_file: prefer_final_locals

import 'package:source_span/source_span.dart';

void main() {
  const source = ' 1 + 2 * 3 + x ';
  final result = calc(source, {'x': 5});
  print(result);
}

int calc(String source, Map<String, int> vars) {
  final parser = CalcParser(vars);
  final state = State(source);
  final result = parser.parseStart(state);
  if (result == null) {
    final file = SourceFile.fromString(source);
    throw FormatException(state
        .getErrors()
        .map((e) => file.span(e.start, e.end).message(e.message))
        .join('\n'));
  }

  return result.$1;
}

}%

%%

Map<String, int> vars = {};

CalcParser(this.vars);

%%

`int`
Start =>
  S
  $ = Expr
  EOF

`int`
Expr('expression') =>
  Sum

`int`
Sum =>
  $ = Product
  @while (*) {
    [+] S
    r = Product
    { $ += r; }
    ----
    [-] S
    r = Product
    { $ -= r; }
  }

`int`
Product =>
  $ = Value
  @while (*) {
    [*] S
    r = Value
    { $ *= r; }
    ----
    [/] S
    r = Value
    { $ ~/= r; }
  }

`int`
Value('expression') => (
  NUMBER
  ----
  i = ID
  $ = { $$ = vars[i]!; }
  ----
  '(' S
  $ = Expr
  ')' S
)

`int`
NUMBER =>
  n = <[0-9]+>
  S
  $ = { $$ = int.parse(n); }

`String`
ID =>
  $ = <[a-zA-Z]>
  S

`void`
EOF('end of file') =>
  ! .

`void`
S => [ \t\r\n]*

Automatic generation of common errors is also supported.

Input: ''
----------------------------------------
FormatException: line 1, column 1: Expected: 'expression'
  ╷
1 │ 
  │ ^
  ╵
========================================
Input: '1`'
----------------------------------------
FormatException: line 1, column 2: Expected: 'end of file'
  ╷
1 │ 1`
  │  ^
  ╵
========================================
Input: '1+'
----------------------------------------
FormatException: line 1, column 3: Expected: 'expression'
  ╷
1 │ 1+
  │   ^
  ╵
========================================
Input: '(1+'
----------------------------------------
FormatException: line 1, column 4: Expected: 'expression'
  ╷
1 │ (1+
  │    ^
  ╵
========================================
Input: '(1'
----------------------------------------
FormatException: line 1, column 3: Expected: ')'
  ╷
1 │ (1
  │   ^
  ╵
========================================

Script for examine error messages.

import 'package:source_span/source_span.dart';

import 'example.dart';

void main(List<String> args) {
  final strings = ['', '1`', '1+', '(1+', '(1'];

  for (final element in strings) {
    print('Input: \'$element\'');
    print('-' * 40);
    try {
      parse(element);
    } catch (e) {
      print(e);
      print('=' * 40);
    }
  }
}

int parse(String source) {
  final parser = CalcParser(const {});
  final state = State(source);
  final result = parser.parseStart(state);
  if (!state.isSuccess) {
    final file = SourceFile.fromString(source);
    throw FormatException(state
        .getErrors()
        .map((e) => file.span(e.start, e.end).message(e.message))
        .join('\n'));
  }

  return result as int;
}

Upvotes: 0

user28910360
user28910360

Reputation: 11

you can use tiny_expr package, there is simple and straightforward implementation of parsing the expressions from string.

Upvotes: 1

Chanaka Weerasinghe
Chanaka Weerasinghe

Reputation: 5742

use function_tree
see documentation https://pub.dev/packages/function_tree#-installing-tab-

final expressionsExample = [
   '2 + 2 - 2 - 2',
   '(3 + 2)^3',
   '3 * pi / 4',
   '3 * sin(5 * pi / 6)',
   'e^(-1)'
 ];
 for (final expression in expressionsExample) {
   print("'$expression' -> ${expression.interpret()}");
 }

Output will be

'2 + 2 - 2 - 2' -> 0
'(3 + 2)^3' -> 125
'3 * pi / 4' -> 2.356194490192345
'3 * sin(5 * pi / 6)' -> 1.5000000000000009
'e^(-1)' -> 0.36787944117144233

Upvotes: 11

Related Questions