Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ jobs:
- name: Write Julia data for MATLAB
run: julia --color=yes tests/julia/test_matlab_interop.jl write

- name: Run MATLAB interop tests
- name: Run MATLAB interop and wire-format tests
uses: matlab-actions/run-command@v3
with:
command: addpath(pwd); addpath(fullfile(pwd, 'tests', 'matlab')); test_julia_interop
command: addpath(pwd); addpath(fullfile(pwd, 'tests', 'matlab')); test_julia_interop; test_literal_eval

- name: Read MATLAB data from Julia
run: julia --color=yes tests/julia/test_matlab_interop.jl read
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ _concore_ enables composing studies from programs developed in different languag

## Wire Format

Concore payloads follow Python literal syntax compatible with `ast.literal_eval()`. The Python, C++, and Java implementations parse this shared format; the MATLAB and Verilog implementations currently support only flat numeric arrays derived from it. Supported value types include:
Concore payloads follow Python literal syntax compatible with `ast.literal_eval()`. The Python, C++, Java, Julia, and MATLAB/Octave implementations parse and serialize this shared format; the Verilog implementation currently supports flat numeric arrays derived from it. Supported value types include:

* **Numbers** — integers and floats, including scientific notation (e.g., `1e3`, `-2.5`)
* **Booleans** — `True` / `False` (converted to `1.0` / `0.0` in numeric contexts)
Expand Down
146 changes: 146 additions & 0 deletions TestLiteralEvalMatlab.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
% TestLiteralEvalMatlab.m
% Test suite for MATLAB/Octave Python-literal parser and serializer.
%
% Run in Octave: octave-cli TestLiteralEvalMatlab.m
% Run in MATLAB: matlab -batch "TestLiteralEvalMatlab"

passed = 0;
failed = 0;

function check(testName, condition)
global passed failed;
if condition
fprintf('PASS: %s\n', testName);
passed = passed + 1;
else
fprintf('FAIL: %s\n', testName);
failed = failed + 1;
end
end

function eq = approx(a, b)
eq = abs(a - b) < 1e-9;
end

global passed failed;
passed = 0;
failed = 0;

fprintf('===== MATLAB/Octave Literal Parser & Serializer Tests =====\n\n');

% 1. Flat numeric list
v = concore_literal_eval('[10.0, 0.5, 2.3]');
check('flat_numeric size==3', numel(v) == 3);
check('flat_numeric[1]==10.0', approx(v(1), 10.0));
check('flat_numeric[2]==0.5', approx(v(2), 0.5));
check('flat_numeric[3]==2.3', approx(v(3), 2.3));

% 2. Empty list
v = concore_literal_eval('[]');
check('empty_list isempty', isempty(v));

% 3. Single element
v = concore_literal_eval('[42.0]');
check('single_element len==1', numel(v) == 1);
check('single_element[1]==42', approx(v(1), 42.0));

% 4. Negative numbers
v = concore_literal_eval('[-1.5, -3.0, 2.0]');
check('negative size==3', numel(v) == 3);
check('negative[1]==-1.5', approx(v(1), -1.5));
check('negative[2]==-3.0', approx(v(2), -3.0));

% 5. Scientific notation
v = concore_literal_eval('[1e3, 2.5E-2, -1.0e+1]');
check('sci size==3', numel(v) == 3);
check('sci[1]==1000', approx(v(1), 1000.0));
check('sci[2]==0.025', approx(v(2), 0.025));
check('sci[3]==-10', approx(v(3), -10.0));

% 6. Integer values
v = concore_literal_eval('[1, 2, 3]');
check('int size==3', numel(v) == 3);
check('int[1]==1', approx(v(1), 1.0));
check('int[3]==3', approx(v(3), 3.0));

% 7. String elements
v = concore_literal_eval('[10.0, "start", 0.5]');
check('string_elem iscell', iscell(v));
check('string_elem len==3', numel(v) == 3);
check('string_elem[1]==10.0', approx(v{1}, 10.0));
check('string_elem[2]=="start"', strcmp(v{2}, 'start'));
check('string_elem[3]==0.5', approx(v{3}, 0.5));

% 8. Boolean elements
v = concore_literal_eval('[True, False]');
check('bool iscell or array', numel(v) == 2);
if iscell(v)
check('bool[1]==true', v{1} == true);
check('bool[2]==false', v{2} == false);
else
check('bool[1]==true', v(1) == true);
check('bool[2]==false', v(2) == false);
end

% 9. Nested list
v = concore_literal_eval('[10.0, [0.5, 0.3], 0.1]');
check('nested iscell', iscell(v));
check('nested len==3', numel(v) == 3);
check('nested[1]==10.0', approx(v{1}, 10.0));
check('nested[2] is list', numel(v{2}) == 2);
if iscell(v{2})
check('nested[2][1]==0.5', approx(v{2}{1}, 0.5));
check('nested[2][2]==0.3', approx(v{2}{2}, 0.3));
else
check('nested[2][1]==0.5', approx(v{2}(1), 0.5));
check('nested[2][2]==0.3', approx(v{2}(2), 0.3));
end

% 10. Tuple payload
v = concore_literal_eval('(10.0, 0.3)');
check('tuple len==2', numel(v) == 2);
if iscell(v)
check('tuple[1]==10.0', approx(v{1}, 10.0));
check('tuple[2]==0.3', approx(v{2}, 0.3));
else
check('tuple[1]==10.0', approx(v(1), 10.0));
check('tuple[2]==0.3', approx(v(2), 0.3));
end

% 11. Escape sequences in string
v = concore_literal_eval('["line\none", "tab\ttwo"]');
check('escape newline', iscell(v) && strcmp(v{1}, sprintf('line\none')));
check('escape tab', iscell(v) && strcmp(v{2}, sprintf('tab\ttwo')));

% 12. Single-quoted strings
v = concore_literal_eval('[\''single_quote\'', 42]');
check('single_quote string', iscell(v) && strcmp(v{1}, 'single_quote'));

% 13. None literal
v = concore_literal_eval('[None, 1]');
check('none is empty', iscell(v) && isempty(v{1}));
check('none second elem is 1', iscell(v) && (v{2} == 1));

% 14. Dictionary parsing
v = concore_literal_eval('{"a": 1, "b": 2.5}');
check('dict is map', isa(v, 'containers.Map'));
check('dict has a', v('a') == 1);
check('dict has b', approx(v('b'), 2.5));

% 15. Serialization tests
s_num = concore_literal_serialize([1.0, 2.0, 3.0]);
check('serialize numeric contains 1', ~isempty(strfind(s_num, '1')));
check('serialize numeric contains 3', ~isempty(strfind(s_num, '3')));

s_bool = concore_literal_serialize(true);
check('serialize bool True', strcmp(s_bool, 'True'));

s_str = concore_literal_serialize('hello');
check('serialize string quotes', strcmp(s_str, '"hello"'));

s_cell = concore_literal_serialize({10.0, 'start', true, [1, 2]});
check('serialize cell starts with [', s_cell(1) == '[');
check('serialize cell contains start', ~isempty(strfind(s_cell, '"start"')));
check('serialize cell contains True', ~isempty(strfind(s_cell, 'True')));

fprintf('\n=== Results: %d passed, %d failed out of %d tests ===\n', passed, failed, passed + failed);
12 changes: 3 additions & 9 deletions concore_default_maxtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@ function concore_default_maxtime(default)
try
maxfile = fopen(strcat(concore.inpath,'1/concore.maxtime'));
instr = fscanf(maxfile,'%c');
% Safe numeric parsing (replaces unsafe eval)
clean_str = strtrim(instr);
clean_str = regexprep(clean_str, '[\[\]]', '');
% Normalize commas to whitespace so sscanf can parse all tokens
clean_str = strrep(clean_str, ',', ' ');
parsed_values = sscanf(clean_str, '%f');
if numel(parsed_values) == 1
concore.maxtime = parsed_values;
parsed_val = concore_literal_eval(instr);
if isnumeric(parsed_val) && numel(parsed_val) == 1
concore.maxtime = parsed_val;
else
concore.maxtime = default;
end
Expand All @@ -19,4 +14,3 @@ function concore_default_maxtime(default)
concore.maxtime = default;
end
end

58 changes: 47 additions & 11 deletions concore_initval.m
Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
function [result] = concore_initval(simtime_val)
global concore;
% Safe numeric parsing (replaces unsafe eval)
clean_str = strtrim(simtime_val);
clean_str = regexprep(clean_str, '[\[\]]', '');
clean_str = strrep(clean_str, ',', ' ');
result = sscanf(clean_str, '%f').';
% Guard against empty or invalid numeric input
if isempty(result)
try
parsed = concore_literal_eval(simtime_val);
catch exc
concore.simtime = 0;
result = [];
return;
end
concore.simtime = result(1);
if numel(result) >= 2
result = result(2:end);
else

if isempty(parsed)
concore.simtime = 0;
result = [];
return;
end

if iscell(parsed)
if isempty(parsed)
concore.simtime = 0;
result = [];
return;
end
first_elem = parsed{1};
if isnumeric(first_elem) || islogical(first_elem)
concore.simtime = double(first_elem);
else
concore.simtime = 0;
end
if numel(parsed) >= 2
all_num_scalar = true;
for k = 2:numel(parsed)
if ~isnumeric(parsed{k}) || numel(parsed{k}) ~= 1
all_num_scalar = false;
break;
end
end
if all_num_scalar
result = cell2mat(parsed(2:end));
else
result = parsed(2:end);
end
else
result = [];
end
elseif isnumeric(parsed)
concore.simtime = parsed(1);
if numel(parsed) >= 2
result = parsed(2:end);
else
result = [];
end
else
concore.simtime = 0;
result = parsed;
end
end
Loading