mirror of
https://github.com/dancojocaru2000/logic-circuits-simulator.git
synced 2025-02-20 08:09:36 +02:00
Implemented logic expressions
Also made truth table horizontally scrollable
This commit is contained in:
parent
cf71d5457a
commit
85838c2b32
10 changed files with 946 additions and 46 deletions
72
lib/components/logic_expression_field.dart
Normal file
72
lib/components/logic_expression_field.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:logic_circuits_simulator/utils/logic_expressions.dart';
|
||||
import 'package:logic_circuits_simulator/utils/logic_operators.dart';
|
||||
|
||||
class LogicExpressionField extends HookWidget {
|
||||
final ValueListenable<List<String>> inputsListener;
|
||||
final String outputName;
|
||||
final String? initialText;
|
||||
final void Function(String input, LogicExpression expression)? onChanged;
|
||||
final void Function()? onInputError;
|
||||
|
||||
const LogicExpressionField({required this.inputsListener, required this.outputName, this.initialText, this.onChanged, this.onInputError, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final inputs = useValueListenable(inputsListener);
|
||||
final controller = useTextEditingController(text: initialText);
|
||||
final errorText = useState<String?>(null);
|
||||
useValueListenable(controller);
|
||||
|
||||
final onChg = useMemoized(() => (String newValue) {
|
||||
final trimmed = newValue.trim();
|
||||
|
||||
try {
|
||||
if (trimmed.isEmpty) {
|
||||
onChanged?.call('', LogicExpression(operator: FalseLogicOperator(), arguments: []));
|
||||
}
|
||||
else {
|
||||
final newLogicExpression = LogicExpression.parse(trimmed);
|
||||
|
||||
// Check if unknown inputs are used
|
||||
final newInputs = newLogicExpression.inputs;
|
||||
final unknownInputs = newInputs.where((input) => !inputs.contains(input)).toList();
|
||||
if (unknownInputs.isNotEmpty) {
|
||||
throw Exception('Unknown inputs found: ${unknownInputs.join(", ")}');
|
||||
}
|
||||
|
||||
onChanged?.call(trimmed, newLogicExpression);
|
||||
}
|
||||
errorText.value = null;
|
||||
} catch (e) {
|
||||
errorText.value = e.toString();
|
||||
onInputError?.call();
|
||||
}
|
||||
}, [inputs, errorText]);
|
||||
useEffect(
|
||||
() {
|
||||
if (controller.text.isNotEmpty) {
|
||||
scheduleMicrotask(() {
|
||||
onChg(controller.text);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[inputs],
|
||||
);
|
||||
|
||||
return TextField(
|
||||
controller: controller,
|
||||
onChanged: onChg,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'Logic Experssion for $outputName',
|
||||
errorText: errorText.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,9 @@ class ComponentEntry with _$ComponentEntry {
|
|||
@JsonKey(includeIfNull: false)
|
||||
List<String>? truthTable,
|
||||
@JsonKey(includeIfNull: false)
|
||||
String? logicExpression,
|
||||
List<String>? logicExpression,
|
||||
@JsonKey(defaultValue: false)
|
||||
required bool visualDesigned,
|
||||
}) = _ComponentEntry;
|
||||
|
||||
factory ComponentEntry.fromJson(Map<String, Object?> json) => _$ComponentEntryFromJson(json);
|
||||
|
|
|
@ -167,7 +167,9 @@ mixin _$ComponentEntry {
|
|||
@JsonKey(includeIfNull: false)
|
||||
List<String>? get truthTable => throw _privateConstructorUsedError;
|
||||
@JsonKey(includeIfNull: false)
|
||||
String? get logicExpression => throw _privateConstructorUsedError;
|
||||
List<String>? get logicExpression => throw _privateConstructorUsedError;
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get visualDesigned => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
@ -187,7 +189,8 @@ abstract class $ComponentEntryCopyWith<$Res> {
|
|||
List<String> inputs,
|
||||
List<String> outputs,
|
||||
@JsonKey(includeIfNull: false) List<String>? truthTable,
|
||||
@JsonKey(includeIfNull: false) String? logicExpression});
|
||||
@JsonKey(includeIfNull: false) List<String>? logicExpression,
|
||||
@JsonKey(defaultValue: false) bool visualDesigned});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
@ -208,6 +211,7 @@ class _$ComponentEntryCopyWithImpl<$Res>
|
|||
Object? outputs = freezed,
|
||||
Object? truthTable = freezed,
|
||||
Object? logicExpression = freezed,
|
||||
Object? visualDesigned = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
componentId: componentId == freezed
|
||||
|
@ -237,7 +241,11 @@ class _$ComponentEntryCopyWithImpl<$Res>
|
|||
logicExpression: logicExpression == freezed
|
||||
? _value.logicExpression
|
||||
: logicExpression // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
as List<String>?,
|
||||
visualDesigned: visualDesigned == freezed
|
||||
? _value.visualDesigned
|
||||
: visualDesigned // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +264,8 @@ abstract class _$$_ComponentEntryCopyWith<$Res>
|
|||
List<String> inputs,
|
||||
List<String> outputs,
|
||||
@JsonKey(includeIfNull: false) List<String>? truthTable,
|
||||
@JsonKey(includeIfNull: false) String? logicExpression});
|
||||
@JsonKey(includeIfNull: false) List<String>? logicExpression,
|
||||
@JsonKey(defaultValue: false) bool visualDesigned});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
@ -279,6 +288,7 @@ class __$$_ComponentEntryCopyWithImpl<$Res>
|
|||
Object? outputs = freezed,
|
||||
Object? truthTable = freezed,
|
||||
Object? logicExpression = freezed,
|
||||
Object? visualDesigned = freezed,
|
||||
}) {
|
||||
return _then(_$_ComponentEntry(
|
||||
componentId: componentId == freezed
|
||||
|
@ -306,9 +316,13 @@ class __$$_ComponentEntryCopyWithImpl<$Res>
|
|||
: truthTable // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
logicExpression: logicExpression == freezed
|
||||
? _value.logicExpression
|
||||
? _value._logicExpression
|
||||
: logicExpression // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
as List<String>?,
|
||||
visualDesigned: visualDesigned == freezed
|
||||
? _value.visualDesigned
|
||||
: visualDesigned // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -323,10 +337,12 @@ class _$_ComponentEntry implements _ComponentEntry {
|
|||
required final List<String> inputs,
|
||||
required final List<String> outputs,
|
||||
@JsonKey(includeIfNull: false) final List<String>? truthTable,
|
||||
@JsonKey(includeIfNull: false) this.logicExpression})
|
||||
@JsonKey(includeIfNull: false) final List<String>? logicExpression,
|
||||
@JsonKey(defaultValue: false) required this.visualDesigned})
|
||||
: _inputs = inputs,
|
||||
_outputs = outputs,
|
||||
_truthTable = truthTable;
|
||||
_truthTable = truthTable,
|
||||
_logicExpression = logicExpression;
|
||||
|
||||
factory _$_ComponentEntry.fromJson(Map<String, dynamic> json) =>
|
||||
_$$_ComponentEntryFromJson(json);
|
||||
|
@ -362,13 +378,23 @@ class _$_ComponentEntry implements _ComponentEntry {
|
|||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
final List<String>? _logicExpression;
|
||||
@override
|
||||
@JsonKey(includeIfNull: false)
|
||||
final String? logicExpression;
|
||||
List<String>? get logicExpression {
|
||||
final value = _logicExpression;
|
||||
if (value == null) return null;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool visualDesigned;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ComponentEntry(componentId: $componentId, componentName: $componentName, componentDescription: $componentDescription, inputs: $inputs, outputs: $outputs, truthTable: $truthTable, logicExpression: $logicExpression)';
|
||||
return 'ComponentEntry(componentId: $componentId, componentName: $componentName, componentDescription: $componentDescription, inputs: $inputs, outputs: $outputs, truthTable: $truthTable, logicExpression: $logicExpression, visualDesigned: $visualDesigned)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -387,7 +413,9 @@ class _$_ComponentEntry implements _ComponentEntry {
|
|||
const DeepCollectionEquality()
|
||||
.equals(other._truthTable, _truthTable) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.logicExpression, logicExpression));
|
||||
.equals(other._logicExpression, _logicExpression) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.visualDesigned, visualDesigned));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
|
@ -400,7 +428,8 @@ class _$_ComponentEntry implements _ComponentEntry {
|
|||
const DeepCollectionEquality().hash(_inputs),
|
||||
const DeepCollectionEquality().hash(_outputs),
|
||||
const DeepCollectionEquality().hash(_truthTable),
|
||||
const DeepCollectionEquality().hash(logicExpression));
|
||||
const DeepCollectionEquality().hash(_logicExpression),
|
||||
const DeepCollectionEquality().hash(visualDesigned));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
|
@ -421,7 +450,8 @@ abstract class _ComponentEntry implements ComponentEntry {
|
|||
required final List<String> inputs,
|
||||
required final List<String> outputs,
|
||||
@JsonKey(includeIfNull: false) final List<String>? truthTable,
|
||||
@JsonKey(includeIfNull: false) final String? logicExpression}) =
|
||||
@JsonKey(includeIfNull: false) final List<String>? logicExpression,
|
||||
@JsonKey(defaultValue: false) required final bool visualDesigned}) =
|
||||
_$_ComponentEntry;
|
||||
|
||||
factory _ComponentEntry.fromJson(Map<String, dynamic> json) =
|
||||
|
@ -443,7 +473,10 @@ abstract class _ComponentEntry implements ComponentEntry {
|
|||
List<String>? get truthTable => throw _privateConstructorUsedError;
|
||||
@override
|
||||
@JsonKey(includeIfNull: false)
|
||||
String? get logicExpression => throw _privateConstructorUsedError;
|
||||
List<String>? get logicExpression => throw _privateConstructorUsedError;
|
||||
@override
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get visualDesigned => throw _privateConstructorUsedError;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_ComponentEntryCopyWith<_$_ComponentEntry> get copyWith =>
|
||||
|
|
|
@ -30,7 +30,10 @@ _$_ComponentEntry _$$_ComponentEntryFromJson(Map<String, dynamic> json) =>
|
|||
truthTable: (json['truthTable'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
logicExpression: json['logicExpression'] as String?,
|
||||
logicExpression: (json['logicExpression'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
visualDesigned: json['visualDesigned'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_ComponentEntryToJson(_$_ComponentEntry instance) {
|
||||
|
@ -50,5 +53,6 @@ Map<String, dynamic> _$$_ComponentEntryToJson(_$_ComponentEntry instance) {
|
|||
val['outputs'] = instance.outputs;
|
||||
writeNotNull('truthTable', instance.truthTable);
|
||||
writeNotNull('logicExpression', instance.logicExpression);
|
||||
val['visualDesigned'] = instance.visualDesigned;
|
||||
return val;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,14 @@ import 'dart:math';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:logic_circuits_simulator/components/logic_expression_field.dart';
|
||||
import 'package:logic_circuits_simulator/components/truth_table.dart';
|
||||
import 'package:logic_circuits_simulator/dialogs/new_ask_for_name.dart';
|
||||
import 'package:logic_circuits_simulator/models/project.dart';
|
||||
import 'package:logic_circuits_simulator/state/project.dart';
|
||||
import 'package:logic_circuits_simulator/utils/iterable_extension.dart';
|
||||
import 'package:logic_circuits_simulator/utils/logic_expressions.dart';
|
||||
import 'package:logic_circuits_simulator/utils/logic_operators.dart';
|
||||
import 'package:logic_circuits_simulator/utils/provider_hook.dart';
|
||||
|
||||
class EditComponentPage extends HookWidget {
|
||||
|
@ -24,13 +27,21 @@ class EditComponentPage extends HookWidget {
|
|||
final projectState = useProvider<ProjectState>();
|
||||
ComponentEntry ce() => projectState.index.components.where((c) => c.componentId == component.componentId).first;
|
||||
final truthTable = useState(ce().truthTable?.toList());
|
||||
final logicExpression = useState(ce().logicExpression);
|
||||
final logicExpressions = useState(ce().logicExpression);
|
||||
final logicExpressionsParsed = useState(
|
||||
logicExpressions.value == null
|
||||
? null
|
||||
: List<LogicExpression?>.generate(logicExpressions.value!.length, (index) => null),
|
||||
);
|
||||
final visualDesigned = useState(ce().visualDesigned);
|
||||
final inputs = useState(ce().inputs.toList());
|
||||
final outputs = useState(ce().outputs.toList());
|
||||
final componentNameEditingController = useTextEditingController(text: ce().componentName);
|
||||
useValueListenable(componentNameEditingController);
|
||||
final dirty = useMemoized(
|
||||
() {
|
||||
const le = ListEquality();
|
||||
|
||||
if (componentNameEditingController.text.isEmpty) {
|
||||
// Don't allow saving empty name
|
||||
return false;
|
||||
|
@ -43,24 +54,31 @@ class EditComponentPage extends HookWidget {
|
|||
// Don't allow saving empty outputs
|
||||
return false;
|
||||
}
|
||||
if (truthTable.value == null && logicExpression.value == null) {
|
||||
if (truthTable.value == null && logicExpressions.value == null && !visualDesigned.value) {
|
||||
// Don't allow saving components without functionality
|
||||
return false;
|
||||
}
|
||||
if (logicExpressionsParsed.value != null && logicExpressionsParsed.value?.contains(null) != false) {
|
||||
// Don't allow saving components with errors in parsing logic expressions
|
||||
return false;
|
||||
}
|
||||
|
||||
if (componentNameEditingController.text != ce().componentName) {
|
||||
return true;
|
||||
}
|
||||
if (!const ListEquality().equals(inputs.value, ce().inputs)) {
|
||||
if (!le.equals(inputs.value, ce().inputs)) {
|
||||
return true;
|
||||
}
|
||||
if (!const ListEquality().equals(outputs.value, ce().outputs)) {
|
||||
if (!le.equals(outputs.value, ce().outputs)) {
|
||||
return true;
|
||||
}
|
||||
if (!const ListEquality().equals(truthTable.value, ce().truthTable)) {
|
||||
if (!le.equals(truthTable.value, ce().truthTable)) {
|
||||
return true;
|
||||
}
|
||||
if (logicExpression.value != ce().logicExpression) {
|
||||
if (!le.equals(logicExpressions.value, ce().logicExpression)) {
|
||||
return true;
|
||||
}
|
||||
if (visualDesigned.value != ce().visualDesigned) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -74,9 +92,31 @@ class EditComponentPage extends HookWidget {
|
|||
ce().outputs,
|
||||
truthTable.value,
|
||||
ce().truthTable,
|
||||
logicExpressions.value,
|
||||
ce().logicExpression,
|
||||
visualDesigned.value,
|
||||
ce().visualDesigned,
|
||||
logicExpressionsParsed.value,
|
||||
],
|
||||
);
|
||||
|
||||
final updateTTFromLE = useMemoized(
|
||||
() {
|
||||
return () {
|
||||
if (logicExpressionsParsed.value?.contains(null) != false) {
|
||||
truthTable.value = null;
|
||||
}
|
||||
else {
|
||||
truthTable.value = logicExpressionsParsed.value!.first!.computeTruthTable(inputs.value).zipWith(
|
||||
logicExpressionsParsed.value!.skip(1).map((le) => le!.computeTruthTable(inputs.value)),
|
||||
(args) => args.join(),
|
||||
).toList();
|
||||
}
|
||||
};
|
||||
},
|
||||
[logicExpressions.value, truthTable.value]
|
||||
);
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (!dirty && !newComponent) {
|
||||
|
@ -137,7 +177,7 @@ class EditComponentPage extends HookWidget {
|
|||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -161,7 +201,13 @@ class EditComponentPage extends HookWidget {
|
|||
},
|
||||
);
|
||||
if (inputName != null) {
|
||||
truthTable.value = truthTable.value?.expand((element) => [element, element]).toList();
|
||||
if (logicExpressions.value != null) {
|
||||
// They should update themselves
|
||||
updateTTFromLE();
|
||||
}
|
||||
else if (truthTable.value != null) {
|
||||
truthTable.value = truthTable.value?.expand((element) => [element, element]).toList();
|
||||
}
|
||||
inputs.value = inputs.value.toList()..add(inputName);
|
||||
}
|
||||
},
|
||||
|
@ -205,7 +251,11 @@ class EditComponentPage extends HookWidget {
|
|||
},
|
||||
);
|
||||
if (shouldRemove == true) {
|
||||
if (truthTable.value != null) {
|
||||
if (logicExpressions.value != null) {
|
||||
// They should update themselves
|
||||
updateTTFromLE();
|
||||
}
|
||||
else if (truthTable.value != null) {
|
||||
final tt = truthTable.value!.toList();
|
||||
final shiftIndex = inputs.value.length - 1 - idx;
|
||||
final shifted = 1 << shiftIndex;
|
||||
|
@ -230,7 +280,7 @@ class EditComponentPage extends HookWidget {
|
|||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -254,7 +304,14 @@ class EditComponentPage extends HookWidget {
|
|||
},
|
||||
);
|
||||
if (outputName != null) {
|
||||
truthTable.value = truthTable.value?.map((e) => '${e}0').toList();
|
||||
if (logicExpressions.value != null) {
|
||||
logicExpressions.value = logicExpressions.value!.followedBy(['']).toList();
|
||||
logicExpressionsParsed.value = logicExpressionsParsed.value?.followedBy([LogicExpression.ofZeroOp(FalseLogicOperator())]).toList();
|
||||
updateTTFromLE();
|
||||
}
|
||||
else if (truthTable.value != null) {
|
||||
truthTable.value = truthTable.value?.map((e) => '${e}0').toList();
|
||||
}
|
||||
outputs.value = outputs.value.toList()..add(outputName);
|
||||
}
|
||||
},
|
||||
|
@ -298,7 +355,12 @@ class EditComponentPage extends HookWidget {
|
|||
},
|
||||
);
|
||||
if (shouldRemove == true) {
|
||||
if (truthTable.value != null) {
|
||||
if (logicExpressions.value != null) {
|
||||
logicExpressions.value = logicExpressions.value?.toList()?..replaceRange(idx, idx+1, []);
|
||||
logicExpressionsParsed.value = logicExpressionsParsed.value?.toList()?..replaceRange(idx, idx+1, []);
|
||||
updateTTFromLE();
|
||||
}
|
||||
else if (truthTable.value != null) {
|
||||
for (var i = 0; i < truthTable.value!.length; i++) {
|
||||
truthTable.value!.replaceRange(i, i+1, [truthTable.value![i].replaceRange(idx, idx+1, "")]);
|
||||
}
|
||||
|
@ -311,11 +373,37 @@ class EditComponentPage extends HookWidget {
|
|||
childCount: outputs.value.length,
|
||||
),
|
||||
),
|
||||
if (truthTable.value == null && logicExpression.value == null) ...[
|
||||
const SliverToBoxAdapter(
|
||||
child: Divider(),
|
||||
),
|
||||
if (inputs.value.isEmpty && outputs.value.isEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'Add inputs and outputs to continue',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
else if (inputs.value.isEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'Add inputs to continue',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
else if (outputs.value.isEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'Add outputs to continue',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
else if (truthTable.value == null && logicExpressions.value == null && !visualDesigned.value) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
const Divider(),
|
||||
Text(
|
||||
'Choose component kind',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
|
@ -324,7 +412,15 @@ class EditComponentPage extends HookWidget {
|
|||
padding: const EdgeInsets.all(8.0),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
logicExpression.value = '';
|
||||
// For each output, a separate logic expression is needed
|
||||
logicExpressions.value = List.generate(
|
||||
outputs.value.length,
|
||||
(index) => '',
|
||||
);
|
||||
logicExpressionsParsed.value = List.generate(
|
||||
outputs.value.length,
|
||||
(index) => null,
|
||||
);
|
||||
},
|
||||
child: const Text('Logic Expression'),
|
||||
),
|
||||
|
@ -333,8 +429,13 @@ class EditComponentPage extends HookWidget {
|
|||
padding: const EdgeInsets.all(8.0),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
// Assign false by default to each output
|
||||
final row = "0" * outputs.value.length;
|
||||
truthTable.value = List.generate(pow(2, inputs.value.length) as int, (_) => row);
|
||||
// There are 2^inputs combinations in a truth table
|
||||
truthTable.value = List.generate(
|
||||
pow(2, inputs.value.length) as int,
|
||||
(_) => row,
|
||||
);
|
||||
},
|
||||
child: const Text('Truth Table'),
|
||||
),
|
||||
|
@ -342,7 +443,9 @@ class EditComponentPage extends HookWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: OutlinedButton(
|
||||
onPressed: null,
|
||||
onPressed: () {
|
||||
visualDesigned.value = true;
|
||||
},
|
||||
child: const Text('Visual Designer'),
|
||||
),
|
||||
),
|
||||
|
@ -350,33 +453,115 @@ class EditComponentPage extends HookWidget {
|
|||
),
|
||||
),
|
||||
],
|
||||
if (logicExpression.value != null) ...[
|
||||
|
||||
],
|
||||
if (truthTable.value != null) ...[
|
||||
if (logicExpressions.value != null) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Truth Table',
|
||||
outputs.value.length == 1 ? 'Logic Expression' : 'Logic Expressions',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: LogicExpressionField(
|
||||
inputsListener: inputs,
|
||||
outputName: outputs.value[index],
|
||||
initialText: logicExpressions.value![index],
|
||||
onChanged: (newValue, newExpression) {
|
||||
logicExpressions.value = logicExpressions.value!.toList();
|
||||
logicExpressions.value![index] = newValue;
|
||||
logicExpressionsParsed.value = logicExpressionsParsed.value!.toList();
|
||||
logicExpressionsParsed.value![index] = newExpression;
|
||||
updateTTFromLE();
|
||||
},
|
||||
onInputError: () {
|
||||
logicExpressionsParsed.value = logicExpressionsParsed.value!.toList();
|
||||
logicExpressionsParsed.value![index] = null;
|
||||
updateTTFromLE();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: logicExpressions.value!.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (truthTable.value != null) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Text(
|
||||
logicExpressions.value == null ? 'Truth Table' : 'Resulting Truth Table',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (logicExpressions.value == null)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Tap output cells to toggle',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TruthTableEditor(
|
||||
truthTable: truthTable.value!,
|
||||
inputs: inputs.value,
|
||||
outputs: outputs.value,
|
||||
onUpdateTable: (idx, newValue) {
|
||||
truthTable.value = truthTable.value?.toList()?..replaceRange(idx, idx+1, [newValue]);
|
||||
},
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: constraints.maxWidth),
|
||||
child: TruthTableEditor(
|
||||
truthTable: truthTable.value!,
|
||||
inputs: inputs.value,
|
||||
outputs: outputs.value,
|
||||
// Only allow updating truth table if it is *NOT* autogenerated by logic expressions
|
||||
onUpdateTable: logicExpressions.value != null ? null : (idx, newValue) {
|
||||
truthTable.value = truthTable.value?.toList()?..replaceRange(idx, idx+1, [newValue]);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
if (visualDesigned.value) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Visually Designed Component",
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (dirty) Text(
|
||||
"Save the component to open the designer",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
) else Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ElevatedButton(
|
||||
// TODO: Implement visual designer
|
||||
onPressed: null,
|
||||
child: Text('Open Designer'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
const SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: 56 + 16 + 16),
|
||||
),
|
||||
|
@ -391,6 +576,8 @@ class EditComponentPage extends HookWidget {
|
|||
inputs: inputs.value,
|
||||
outputs: outputs.value,
|
||||
truthTable: truthTable.value,
|
||||
logicExpression: logicExpressions.value,
|
||||
visualDesigned: visualDesigned.value,
|
||||
));
|
||||
anySave.value = true;
|
||||
// TODO: Implement saving
|
||||
|
|
|
@ -76,6 +76,7 @@ class ProjectState extends ChangeNotifier {
|
|||
componentName: '',
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
visualDesigned: false,
|
||||
);
|
||||
await _updateIndex(index.copyWith(components: index.components + [newComponent]));
|
||||
return newComponent;
|
||||
|
|
|
@ -3,4 +3,22 @@ extension IndexedMap<T> on Iterable<T> {
|
|||
int index = 0;
|
||||
return map((e) => toElement(index++, e));
|
||||
}
|
||||
|
||||
Iterable<O> zipWith<O>(Iterable<Iterable<T>> otherIterables, O Function(List<T>) toElement, {bool allowUnequalLengths = false}) sync* {
|
||||
final iterators = [this].followedBy(otherIterables).map((e) => e.iterator).toList();
|
||||
while (true) {
|
||||
int ends = iterators.map((it) => it.moveNext()).where((e) => e == false).length;
|
||||
if (ends != 0) {
|
||||
// At least one iterator finished
|
||||
if (ends != iterators.length && !allowUnequalLengths) {
|
||||
throw Exception('While zipping, $ends iterators ended early');
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield toElement(iterators.map((it) => it.current).toList(growable: false));
|
||||
}
|
||||
}
|
||||
}
|
286
lib/utils/logic_expressions.dart
Normal file
286
lib/utils/logic_expressions.dart
Normal file
|
@ -0,0 +1,286 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:logic_circuits_simulator/utils/iterable_extension.dart';
|
||||
import 'package:logic_circuits_simulator/utils/logic_operators.dart';
|
||||
|
||||
part 'logic_expressions.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class LogicExpression with _$LogicExpression {
|
||||
const LogicExpression._();
|
||||
|
||||
const factory LogicExpression({
|
||||
required LogicOperator operator,
|
||||
required List<dynamic> arguments,
|
||||
}) = _LogicExpression;
|
||||
|
||||
factory LogicExpression.ofZeroOp(ZeroOpLogicOperator operator) => LogicExpression(operator: operator, arguments: []);
|
||||
|
||||
static dynamic _classify(String token) {
|
||||
final operators = [
|
||||
FalseLogicOperator(),
|
||||
TrueLogicOperator(),
|
||||
NotLogicOperator(),
|
||||
AndLogicOperator(),
|
||||
OrLogicOperator(),
|
||||
XorLogicOperator(),
|
||||
NandLogicOperator(),
|
||||
NorLogicOperator(),
|
||||
XnorLogicOperator(),
|
||||
];
|
||||
|
||||
for (final op in operators) {
|
||||
if (op.representations.contains(token)) {
|
||||
return op;
|
||||
}
|
||||
}
|
||||
|
||||
final inputStart = RegExp('^[A-Za-z]');
|
||||
if (inputStart.hasMatch(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
throw Exception('Unknown operator: $token');
|
||||
}
|
||||
|
||||
static List<dynamic> _tokenize(String input) {
|
||||
final space = ' '.codeUnits[0];
|
||||
final openedParen = '('.codeUnits[0];
|
||||
final closedParen = ')'.codeUnits[0];
|
||||
final transitionToOperator = RegExp('[^A-Za-z0-9]');
|
||||
final transitionToInput = RegExp('[A-Za-z]');
|
||||
|
||||
List<dynamic> result = [];
|
||||
final buffer = StringBuffer();
|
||||
bool operator = false;
|
||||
int parenDepth = 0;
|
||||
|
||||
for (final rune in input.runes) {
|
||||
if (rune == openedParen) {
|
||||
if (parenDepth == 0 && buffer.isNotEmpty) {
|
||||
result.add(_classify(buffer.toString()));
|
||||
buffer.clear();
|
||||
}
|
||||
else if (parenDepth > 0) {
|
||||
buffer.writeCharCode(rune);
|
||||
}
|
||||
parenDepth++;
|
||||
continue;
|
||||
}
|
||||
else if (rune == closedParen) {
|
||||
parenDepth--;
|
||||
if (parenDepth == 0) {
|
||||
result.add(_tokenize(buffer.toString()));
|
||||
buffer.clear();
|
||||
}
|
||||
else if (parenDepth < 0) {
|
||||
throw Exception('Unmached parenthesis: too many closed parenthesis');
|
||||
}
|
||||
else {
|
||||
buffer.writeCharCode(rune);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (parenDepth > 0) {
|
||||
// While inside paren, just add stuff to the buffer to be further
|
||||
// processed recursively and put inside of a list.
|
||||
// ~(~(A&(A+B))+B& ~A)
|
||||
// │ │ └───┘│ │
|
||||
// │ └───────┘ │
|
||||
// └────────────────┘
|
||||
buffer.writeCharCode(rune);
|
||||
continue;
|
||||
}
|
||||
else if (rune == space) {
|
||||
if (buffer.isNotEmpty) {
|
||||
result.add(_classify(buffer.toString()));
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (buffer.isNotEmpty) {
|
||||
// Check if switching from operator to input.
|
||||
// This allows an expression such as A&B to be valid.
|
||||
// Switching happens when in the middle of a token
|
||||
// and changing from [A-Za-z0-9] to [^A-Za-z0-9]
|
||||
// or from [^A-Za-z] to [A-Za-z].
|
||||
// Inputs can't start with digits.
|
||||
if (!operator && transitionToOperator.hasMatch(String.fromCharCode(rune))) {
|
||||
result.add(_classify(buffer.toString()));
|
||||
buffer.clear();
|
||||
}
|
||||
else if (operator && transitionToInput.hasMatch(String.fromCharCode(rune))) {
|
||||
result.add(_classify(buffer.toString()));
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
if (buffer.isEmpty) {
|
||||
operator = !transitionToInput.hasMatch(String.fromCharCode(rune));
|
||||
}
|
||||
buffer.writeCharCode(rune);
|
||||
}
|
||||
}
|
||||
if (parenDepth != 0) {
|
||||
throw Exception('Unmached parenthesis: too many open parenthesis');
|
||||
}
|
||||
if (buffer.isNotEmpty) {
|
||||
result.add(_classify(buffer.toString()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
factory LogicExpression.parse(String input) {
|
||||
final tokens = _tokenize(input);
|
||||
|
||||
final result = LogicExpression._parse(tokens);
|
||||
if (result is String) {
|
||||
return LogicExpression(
|
||||
operator: OrLogicOperator(),
|
||||
arguments: [
|
||||
result,
|
||||
LogicExpression.ofZeroOp(FalseLogicOperator()),
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static dynamic _parse(dynamic input) {
|
||||
if (input is List) {
|
||||
final tokens = input;
|
||||
|
||||
final orderedOpGroups = [
|
||||
[OrLogicOperator(), NorLogicOperator()],
|
||||
[XorLogicOperator(), XnorLogicOperator()],
|
||||
[AndLogicOperator(), NandLogicOperator()],
|
||||
[NotLogicOperator()],
|
||||
[FalseLogicOperator(), TrueLogicOperator()],
|
||||
];
|
||||
|
||||
for (final ops in orderedOpGroups) {
|
||||
for (var i = tokens.length - 1; i >= 0; i--) {
|
||||
if (ops.contains(tokens[i])) {
|
||||
if (tokens[i] is ZeroOpLogicOperator) {
|
||||
// ZeroOp operator should be alone
|
||||
if (tokens.length != 1) {
|
||||
throw Exception('ZeroOp operator should be alone');
|
||||
}
|
||||
return LogicExpression.ofZeroOp(tokens[i]);
|
||||
}
|
||||
else if (tokens[i] is OneOpLogicOperator) {
|
||||
// OneOp operator should appear prefix only
|
||||
// So index should be 0
|
||||
if (i != 0) {
|
||||
throw Exception('OneOp operator should be prefix');
|
||||
}
|
||||
// It should only be possible to get here if there is only one argument
|
||||
// follows. The only other case is someone writing:
|
||||
// ~ A B
|
||||
// which would result in [NotLogicOperator, 'A', 'B'].
|
||||
// Such syntax is ambiguous and should not be allowed:
|
||||
// (~ A) & B -or- ~ (A & B) ?
|
||||
// This is the disadvantage of linear, left-to-right notation (as opposed
|
||||
// to the notation with a bar above the NOT-ed expression).
|
||||
if (tokens.length > 2) {
|
||||
throw Exception('Ambiguous expression: ${tokens[i]} followed by multiple tokens (${tokens.skip(1).toList()})');
|
||||
}
|
||||
else if (tokens.length == 1) {
|
||||
throw Exception('Unfinished expression');
|
||||
}
|
||||
return LogicExpression(
|
||||
operator: tokens[0],
|
||||
arguments: [_parse(tokens[1])],
|
||||
);
|
||||
}
|
||||
else if (tokens[i] is TwoOpLogicOperator) {
|
||||
return LogicExpression(
|
||||
operator: tokens[i],
|
||||
arguments: [
|
||||
_parse(tokens.getRange(0, i).toList()),
|
||||
_parse(tokens.getRange(i + 1, tokens.length).toList()),
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw Exception('Matched with operator that somehow isn\'t Zero/One/TwoOp');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No operators were found. This means the only tokens are props.
|
||||
// If there is only one prop, return it alone:
|
||||
// A => [A] => A
|
||||
// If there are multiple props, apply AND:
|
||||
// A B C => [A, B, C] => A & B & C => (A & B) & C
|
||||
// Keep in mind the second case is only possible if the props are separated by spaces,
|
||||
// as the nature of prop names allowing multiple characters only allows multiple props
|
||||
// to appear one after the other if separated by spaces.
|
||||
if (tokens.length == 1) {
|
||||
return tokens[0];
|
||||
}
|
||||
else if (tokens.isEmpty) {
|
||||
// This happens in unfinished expressions:
|
||||
// A ^ ! => XOR(A, NOT(?))
|
||||
// A ^ => XOR(A, ?)
|
||||
throw Exception('Unfinished expression');
|
||||
}
|
||||
else {
|
||||
final and = AndLogicOperator();
|
||||
return _parse(tokens.expand((token) => [and, token]).skip(1).toList());
|
||||
}
|
||||
}
|
||||
else if (input is String) {
|
||||
// Prop, just return.
|
||||
// Happens in such cases:
|
||||
// B & ~ A => & [B, ~ [A]]
|
||||
// ^ ^
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Set<String> get inputs {
|
||||
Set<String> result = {};
|
||||
for (final arg in arguments) {
|
||||
if (arg is String) {
|
||||
result.add(arg);
|
||||
}
|
||||
else if (arg is LogicExpression) {
|
||||
result.addAll(arg.inputs);
|
||||
}
|
||||
else {
|
||||
throw Exception('Unknown argument type found: ${arg.runtimeType}');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool evaluate(Map<String, bool> inputs) {
|
||||
return operator.apply(
|
||||
arguments
|
||||
.map(
|
||||
// If the argument is a logical expression, evaluate recursively
|
||||
// else it must be an input name, so replace based on supplied mapping.
|
||||
(e) => e is LogicExpression ? e.evaluate(inputs) : inputs[e]!
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
List<String> computeTruthTable(List<String> inputs) {
|
||||
final ttRows = pow(2, inputs.length) as int;
|
||||
return List.generate(
|
||||
ttRows,
|
||||
(index) => evaluate(
|
||||
{
|
||||
for (var element in inputs.reversed.indexedMap((index, input) => [index, input]))
|
||||
element[1] as String : (index & (pow(2, element[0] as int) as int)) != 0
|
||||
},
|
||||
) ? '1' : '0',
|
||||
);
|
||||
}
|
||||
}
|
159
lib/utils/logic_expressions.freezed.dart
Normal file
159
lib/utils/logic_expressions.freezed.dart
Normal file
|
@ -0,0 +1,159 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
|
||||
|
||||
part of 'logic_expressions.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LogicExpression {
|
||||
LogicOperator get operator => throw _privateConstructorUsedError;
|
||||
List<dynamic> get arguments => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$LogicExpressionCopyWith<LogicExpression> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LogicExpressionCopyWith<$Res> {
|
||||
factory $LogicExpressionCopyWith(
|
||||
LogicExpression value, $Res Function(LogicExpression) then) =
|
||||
_$LogicExpressionCopyWithImpl<$Res>;
|
||||
$Res call({LogicOperator operator, List<dynamic> arguments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LogicExpressionCopyWithImpl<$Res>
|
||||
implements $LogicExpressionCopyWith<$Res> {
|
||||
_$LogicExpressionCopyWithImpl(this._value, this._then);
|
||||
|
||||
final LogicExpression _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(LogicExpression) _then;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? operator = freezed,
|
||||
Object? arguments = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
operator: operator == freezed
|
||||
? _value.operator
|
||||
: operator // ignore: cast_nullable_to_non_nullable
|
||||
as LogicOperator,
|
||||
arguments: arguments == freezed
|
||||
? _value.arguments
|
||||
: arguments // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$_LogicExpressionCopyWith<$Res>
|
||||
implements $LogicExpressionCopyWith<$Res> {
|
||||
factory _$$_LogicExpressionCopyWith(
|
||||
_$_LogicExpression value, $Res Function(_$_LogicExpression) then) =
|
||||
__$$_LogicExpressionCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call({LogicOperator operator, List<dynamic> arguments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$_LogicExpressionCopyWithImpl<$Res>
|
||||
extends _$LogicExpressionCopyWithImpl<$Res>
|
||||
implements _$$_LogicExpressionCopyWith<$Res> {
|
||||
__$$_LogicExpressionCopyWithImpl(
|
||||
_$_LogicExpression _value, $Res Function(_$_LogicExpression) _then)
|
||||
: super(_value, (v) => _then(v as _$_LogicExpression));
|
||||
|
||||
@override
|
||||
_$_LogicExpression get _value => super._value as _$_LogicExpression;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? operator = freezed,
|
||||
Object? arguments = freezed,
|
||||
}) {
|
||||
return _then(_$_LogicExpression(
|
||||
operator: operator == freezed
|
||||
? _value.operator
|
||||
: operator // ignore: cast_nullable_to_non_nullable
|
||||
as LogicOperator,
|
||||
arguments: arguments == freezed
|
||||
? _value._arguments
|
||||
: arguments // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$_LogicExpression extends _LogicExpression {
|
||||
const _$_LogicExpression(
|
||||
{required this.operator, required final List<dynamic> arguments})
|
||||
: _arguments = arguments,
|
||||
super._();
|
||||
|
||||
@override
|
||||
final LogicOperator operator;
|
||||
final List<dynamic> _arguments;
|
||||
@override
|
||||
List<dynamic> get arguments {
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_arguments);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LogicExpression(operator: $operator, arguments: $arguments)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$_LogicExpression &&
|
||||
const DeepCollectionEquality().equals(other.operator, operator) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._arguments, _arguments));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(operator),
|
||||
const DeepCollectionEquality().hash(_arguments));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
_$$_LogicExpressionCopyWith<_$_LogicExpression> get copyWith =>
|
||||
__$$_LogicExpressionCopyWithImpl<_$_LogicExpression>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _LogicExpression extends LogicExpression {
|
||||
const factory _LogicExpression(
|
||||
{required final LogicOperator operator,
|
||||
required final List<dynamic> arguments}) = _$_LogicExpression;
|
||||
const _LogicExpression._() : super._();
|
||||
|
||||
@override
|
||||
LogicOperator get operator => throw _privateConstructorUsedError;
|
||||
@override
|
||||
List<dynamic> get arguments => throw _privateConstructorUsedError;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_LogicExpressionCopyWith<_$_LogicExpression> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
138
lib/utils/logic_operators.dart
Normal file
138
lib/utils/logic_operators.dart
Normal file
|
@ -0,0 +1,138 @@
|
|||
abstract class LogicOperator {
|
||||
bool apply(List<bool> inputs);
|
||||
List<String> get representations;
|
||||
String get defaultRepresentation => representations[0];
|
||||
bool fromRepresentation(String repr) => representations.contains(repr);
|
||||
}
|
||||
|
||||
abstract class ZeroOpLogicOperator extends LogicOperator {}
|
||||
|
||||
class FalseLogicOperator extends ZeroOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => false;
|
||||
|
||||
@override
|
||||
List<String> get representations => ['0'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is FalseLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
class TrueLogicOperator extends ZeroOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => true;
|
||||
|
||||
@override
|
||||
List<String> get representations => ['1'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is TrueLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
abstract class OneOpLogicOperator extends LogicOperator {}
|
||||
|
||||
class NotLogicOperator extends OneOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => !inputs[0];
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['~', '!', '¬'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is NotLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
abstract class TwoOpLogicOperator extends LogicOperator {}
|
||||
|
||||
class AndLogicOperator extends TwoOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => inputs[0] && inputs[1];
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['&', '∧'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is AndLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
class OrLogicOperator extends TwoOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => inputs[0] || inputs[1];
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['|', '∨'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is OrLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
class XorLogicOperator extends TwoOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => inputs[0] != inputs[1];
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['^', '⊕', '⊻'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is XorLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
class NandLogicOperator extends TwoOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => !(inputs[0] && inputs[1]);
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['~&', '!&', '¬&', '~∧', '!∧', '¬∧'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is NandLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
class NorLogicOperator extends TwoOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => !(inputs[0] || inputs[1]);
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['~|', '!|', '¬|', '~∨', '!∨', '¬∨'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is NorLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
||||
|
||||
class XnorLogicOperator extends TwoOpLogicOperator {
|
||||
@override
|
||||
bool apply(List<bool> inputs) => inputs[0] == inputs[1];
|
||||
|
||||
@override
|
||||
List<String> get representations => const ['~^', '!^', '¬^', '~⊕', '!⊕', '¬⊕', '~⊻', '!⊻', '¬⊻'];
|
||||
|
||||
@override
|
||||
bool operator==(other) => other is XnorLogicOperator;
|
||||
|
||||
@override
|
||||
int get hashCode => defaultRepresentation.hashCode;
|
||||
}
|
Loading…
Add table
Reference in a new issue