Skip to content

Commit

Permalink
SLPhy can take macro now #420
Browse files Browse the repository at this point in the history
  • Loading branch information
walterxie committed Nov 29, 2023
1 parent 06f4c86 commit 2443d39
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 48 deletions.
2 changes: 2 additions & 0 deletions examples/macro/MacroLanguage.lphy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// It only shows the default when using studio,
// but macro value can be replaced by SLPhy (command line application).
data {
L = {{L = 100}};
taxa = taxa(names=1:{{n = 10}});
Expand Down
2 changes: 1 addition & 1 deletion lphy-base/src/test/java/lphy/base/LPhyExamplesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void testLPhyExamplesInDir(File exampleDir) {
try {
FileReader lphyFile = new FileReader(exampleDir.getAbsoluteFile() + File.separator + fileName);
BufferedReader fin = new BufferedReader(lphyFile);
lPhyMetaParser.source(fin);
lPhyMetaParser.source(fin, null);
} catch (Exception e) {
// failedFiles.add(fileName);
System.err.println("Example " + fileName + " failed\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import lphy.core.logger.LoggerUtils;
import lphy.core.model.*;
import lphy.core.parser.LPhyParserDictionary;
import lphy.core.parser.REPL;
import lphy.core.parser.graphicalmodel.GraphicalModel;
import lphy.core.parser.graphicalmodel.GraphicalModelListener;
import lphy.core.simulator.Sampler;
Expand Down Expand Up @@ -230,7 +229,7 @@ public void layout() {
// }

/**
* This is duplicated to {@link REPL#source(BufferedReader)},
* This is duplicated to {@link LPhyParserDictionary#source(BufferedReader, String[])},
* but has extra code using {@link LineCodeColorizer}
* and panel function.
*
Expand All @@ -240,7 +239,7 @@ public void layout() {
public void source(BufferedReader reader) throws IOException {

LPhyParserDictionary metaData = component.getParserDictionary();
metaData.source(reader);
metaData.source(reader, null);

repaint();
}
Expand Down
6 changes: 4 additions & 2 deletions lphy/src/main/java/lphy/core/io/FileConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ public static class Utils {

public static FileConfig createSimulationFileConfig(File lphyFile, File outDir, int numReplicates,
Long seed ) throws IOException {

String currentDir = lphyFile.getAbsoluteFile().getParent();
// if user.dir is not the parent folder of lphyFile, then set to it
if (! UserDir.getUserDir().toAbsolutePath().equals(lphyFile.getParentFile())) {
UserDir.setUserDir(lphyFile.getParentFile().getAbsolutePath());
if (! UserDir.getUserDir().toAbsolutePath().equals(currentDir)) {
UserDir.setUserDir(currentDir);
}

if (seed != null)
Expand Down
81 changes: 60 additions & 21 deletions lphy/src/main/java/lphy/core/io/MacroProcessor.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package lphy.core.io;

import lphy.core.logger.LoggerUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
Expand All @@ -15,44 +17,77 @@ public class MacroProcessor {
public static final String CONST_MACRO = "\\{\\{\\s*(\\w+)\\s*=\\s*(.*?)\\s*\\}\\}";
private final Pattern pattern;

public MacroProcessor(String regex) {
// init by user input (e.g. cmd args), if map key does not have the var name when per-processing script,
// then use the default from macro.
Map<String, String> macroMap;

public MacroProcessor(String regex, String[] constants) {
this.pattern = Pattern.compile(regex);
macroMap = new HashMap<>();
if (constants != null)
// init macroMap based on user input
parseConstants(constants);
}

public MacroProcessor() {
this(CONST_MACRO);
/**
* Process the marco in a lphy script, such as {{n = 10}}.
* @param constants constants inputted by user (e.g. command line arguments),
* where each string element looks like n=10.
* Null, if no user input.
*/
public MacroProcessor(String[] constants) {
this(CONST_MACRO, constants);
}

// Singleton
private static MacroProcessor INSTANCE;
public static MacroProcessor getInstance() {
if(INSTANCE == null) {
INSTANCE = new MacroProcessor();
}
return INSTANCE;
}

public static String process(String input) {
Map<String, String> macroMap = getInstance().getMacroMap(input);
// TODO modify the macroMap based on user input?
return getInstance().getResult(input, macroMap);
/**
* @param input lphy string before preprocessing macros.
* @return a lphy string after replacing all macro into actual values.
*/
public String process(String input) {
assignDefaultValues(input);
return getResult(input);
}

public Map<String, String> getMacroMap(String input) {
Map<String, String> macroMap = new HashMap<>();

/**
* Parse command line argument into {@link #macroMap}.
* E.g. -D n=12;L=100 or -D n=20"
*
* @param constants
*/
private void parseConstants(String[] constants) {
for (String arg : constants) {
// n = 12
if (arg.contains("=")) {
// the pattern will be applied at most limit - 1 times,
// the array's length will be no greater than limit,
// and the array's last entry will contain all input beyond the last matched delimiter.
String[] varVal = arg.split("=", 2);
if (varVal.length == 2) {
// trim spaces
macroMap.put(varVal[0].trim(), varVal[1].trim());
}
}
}
}

private void assignDefaultValues(String input) {
// Populate the macroMap based on the initial matches
Matcher matcher = pattern.matcher(input);

while (matcher.find()) {
String identifier = matcher.group(1);
String literal = matcher.group(2);
macroMap.put(identifier, literal);
// use the default from macro, only when the var name not exist
if (!macroMap.containsKey(identifier))
macroMap.put(identifier, literal);
}
return macroMap;
}

public String getResult(String input, Map<String, String> macroMap) {
private String getResult(String input) {
if (macroMap.isEmpty()) return input;

Matcher replaceMatcher = pattern.matcher(input);
StringBuilder stringBuffer = new StringBuilder();

Expand All @@ -63,6 +98,9 @@ public String getResult(String input, Map<String, String> macroMap) {
// Slashes ('\') and dollar signs ('$') will be given no special meaning.
replacement = Matcher.quoteReplacement(replacement);
replaceMatcher.appendReplacement(stringBuffer, replacement);

LoggerUtils.log.info("Macro replaces the value of constant var : " + replaceMatcher.group(1) +
" = " + replacement);
}
}
replaceMatcher.appendTail(stringBuffer);
Expand All @@ -73,7 +111,8 @@ public String getResult(String input, Map<String, String> macroMap) {
public static void main(String[] args) {
String input = "{{identifier1 = value1}} some text {{identifier2 = value2}}";

String result = MacroProcessor.process(input);
MacroProcessor macroProcessor = new MacroProcessor(null);
String result = macroProcessor.process(input);

System.out.println(result);
}
Expand Down
16 changes: 12 additions & 4 deletions lphy/src/main/java/lphy/core/parser/LPhyParserDictionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,12 @@ default List<String> getKeywords() {

/**
* @param sourceFile the lphy source file
* @param constants constants inputted by user using macro
*/
default void source(File sourceFile) throws IOException {
default void source(File sourceFile, String[] constants) throws IOException {
FileReader reader = new FileReader(sourceFile);
BufferedReader bufferedReader = new BufferedReader(reader);
source(bufferedReader);
source(bufferedReader, constants);
reader.close();
}

Expand All @@ -92,16 +93,23 @@ default void source(File sourceFile) throws IOException {
* Before {@link #parse(String)}, the line will be preprocessed by
* {@link MacroProcessor}, so that the marco templating language
* will be replaced by the actual values.
*
* @param bufferedReader the lphy source
* @param constants constants inputted by user (e.g. command line arguments),
* where each string element looks like n=10.
* Null, if no user input.
*/
default void source(BufferedReader bufferedReader) throws IOException {
default void source(BufferedReader bufferedReader, String[] constants) throws IOException {
// init processor by use input if there is any
MacroProcessor macroProcessor = new MacroProcessor(constants);

StringBuilder builder = new StringBuilder();

String lineProcessed;
String line = bufferedReader.readLine();
while (line != null) {
// process macro here
lineProcessed = MacroProcessor.process(line);
lineProcessed = macroProcessor.process(line);
builder.append(lineProcessed);
builder.append("\n");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,19 @@ public NamedRandomValueSimulator() {
simulatorListener = new ValueFileLoggerListener();
}

public Map<Integer, List<Value>> simulateAndSaveResults(File lphyFile, int numReplicates, Long seed) throws IOException {
simulatorListener.start(numReplicates, lphyFile);
return simulate(lphyFile, numReplicates, seed);
}

public Map<Integer, List<Value>> simulateAndSaveResults(FileConfig fileConfig) throws IOException {
// must have File lphyFile, int numReplicates, Long seed
public Map<Integer, List<Value>> simulate(FileConfig fileConfig, String[] constants) throws IOException {
simulatorListener.start(fileConfig);

File lphyFile = fileConfig.lphyInputFile;
int numReplicates = fileConfig.numReplicates;
Long seed = fileConfig.seed;

return simulate(lphyFile, numReplicates, seed);
}

private Map<Integer, List<Value>> simulate(File lphyFile, int numReplicates, Long seed) throws IOException {
// TODO duplicate to maps in ValueFileLoggerListener
Map<Integer, List<Value>> simResMap = new HashMap<>();

// create Sampler given a lphy script file
sampler = Sampler.createSampler(lphyFile);
sampler = Sampler.createSampler(lphyFile, constants);

long start = System.currentTimeMillis();

Expand Down
10 changes: 8 additions & 2 deletions lphy/src/main/java/lphy/core/simulator/SLPhy.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public class SLPhy implements Callable<Integer> {
"usually to create data for well-calibrated study.") int numReps = 1;
@CommandLine.Option(names = {"-seed", "--seed"}, description = "the seed.") Long seed;

@CommandLine.Option(names = {"-D", "--data"}, split = ";",
description = "Replace the constant value in the lphy script, multiple constants can be split by ';', " +
"but no ';' at the last: e.g. -D \"n=12;L=100\" or -D n=20")
String[] lphyConst = null;

// enum SPI { loggers } //TODO functions, gendists
// // arity = "0" not working
// @CommandLine.Option(names = {"-ls", "--list"},
Expand All @@ -62,8 +67,9 @@ public Integer call() throws PicocliException {
.createSimulationFileConfig(infile.toFile(), outDir, numReps, seed);

simulator = new NamedRandomValueSimulator();
simulator.simulateAndSaveResults(fileConfig);

// must provide File lphyFile, int numReplicates, Long seed
simulator.simulate(fileConfig, lphyConst);
// TODO save Map<Integer, List<Value>> simResMap ?
} catch (IOException e) {
throw new PicocliException(e.getMessage(), e);
}
Expand Down
5 changes: 3 additions & 2 deletions lphy/src/main/java/lphy/core/simulator/Sampler.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ public Sampler(LPhyParserDictionary parser) {

/**
* @param lphyFile a File containing LPhy script.
* @param constants constants inputted by user using macro
* @return a Sampler created by the given LPhy script.
* @throws IOException
*/
public static Sampler createSampler(File lphyFile) throws IOException {
public static Sampler createSampler(File lphyFile, String[] constants) throws IOException {
//*** Parse LPhy file ***//
LPhyParserDictionary parser = new REPL();
parser.source(lphyFile);
parser.source(lphyFile, constants);

// Sampler requires GraphicalLPhyParser
Sampler sampler = new Sampler(parser);
Expand Down
5 changes: 3 additions & 2 deletions lphy/src/main/java/lphy/core/simulator/Simulate.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ public Value<Map<String, Object>> apply() {
FileConfig fileConfig = FileConfig.Utils
.createSimulationFileConfig(infile, outDir, SimulatorListener.REPLICATES_START_INDEX, seed);

values = simulator.simulateAndSaveResults(fileConfig)
.get(SimulatorListener.REPLICATES_START_INDEX);
Map<Integer, List<Value>> allReps = simulator.simulate(fileConfig, null);
// only take the last replicate
values = allReps.get(SimulatorListener.REPLICATES_START_INDEX);

} catch (IOException e) {
LoggerUtils.log.severe("Cannot parse LPhy script file ! " + infile.getAbsolutePath());
Expand Down

0 comments on commit 2443d39

Please sign in to comment.