/*******************************************************************************
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package hr.fer.zemris.vhdllab.service.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Klasa parsira VCD-datoteku koju generira GHDL (GHDL je GNU VHDL simulator)
* VCD-datoteka ima sljedeci format: 9 prvih linija predstavljaju osnovne
* informacije koje su konstantne za svaku datoteku. Nakon njih dolazi popis
* svih signala predstavljen u obliku ASCII simbola, npr:
* $var reg 1 ! tb_a $end
* Svaka linija koja pocinje s "$var" predstavlja jedan signal. U produzetku se
* nalazi tip signala (vektor, bit) predstavljen brojem iza podstringa "reg",
* i sam ASCII simbol signala (u ovom slucaju je to "!"). Na kraju se nalazi
* ime tog signala. Svaki se signal nalazi unutar svog scopea.
* S podstringom "#0" pocinje samo simulacija: svaka se tocka promjene oznacuje
* s '#' iza kojih slijedi vrijednost promjene. Vazno je napomenuti da vektori
* imaju poseban nacin oznacavanja: svaki vektor pocinje sa slovom 'b', a prije
* ASCII vrijednosti signala mora biti praznina.
*
* @author Boris Ozegovic
* @author Miro Bezjak
*/
public class VcdParser
{
/* Parser pocinje parsirati nakon osnovnih informacija */
private int index = 9;
/** Sadrzi sve linije datoteke */
private String[] vcdLines;
/**
* Mapa ciji su kljucevi kompletna imena signala (scope ukljuciv),
* te cije su vrijednosti poznate u svakoj od tocaka tranzicije
*/
private Map<String, List<String>> signalValues = new LinkedHashMap<String, List<String>>();
/** Sadrzi vrijednosti tocaka tranzicije (npr. 750-toj ns) */
private List<Long> transitionPoints = new ArrayList<Long>();
/** Sadrzi upotrebljene ASCII simbole, svaki od njih predstavlja signal */
private List<String> asciiSignalSimbols = new ArrayList<String>();
/** Predstavlja najduze ime od svih signala, potreban zbog duljine panela */
private int maximumSignalNameLength;
/** Predstavlja string koji se prenosi klijentu */
private StringBuffer resultInString = new StringBuffer("");
/**
* Upotrebljava se za interni format prilikom razdvajanja imena signala i
* razdvajanja vrijednosti za svaki od signala prilikom transformacije us
* mape u string
*/
private static final String LIMITER = "###";
/**
* Limiter koji razdvaja s jedne strane sva imena signala, njihove
* vrijednosti, tocke u kojima se dogada promjena vrijednosti i konacno
* duljinu u znakovima najduzeg imena signala
*/
private static final String HEAD_LIMITER = "%%%";
/** Limiter koji razdvaju svaku od trenutnih vrijednosti (0 */
private static final String VALUE_LIMITER = "&&&";
/**
* Constructor
*/
public VcdParser (String[] lines)
{
vcdLines = lines;
}
/**
* Metoda izdvaja ASCII reprezentante odgovarajucih signala i trazi
* puna imena svih signala. Mapa imena signala/njihove vrijednosti poslije
* koristenja ove metode sadrzavat ce samo imena signala kao kljuceve, bez
* vrijednosti. Koristi je metoda parse();
*/
public void parseSignals()
{
/* Pretrazuje sve linije dok ne nade kraj definiranja simbola. Ako linija
* pocinje s $var znaci da predstavlja signal, pa se pronalazi ASCII simbol
* i puno ime. Ako linija pocinje s $scope znaci da ulazimo u novi scope te
* se u listu (koja se ponasao kao stog) dodaje novi scope na prethodni.
* Inace brise zadnji dodan scope.
*/
LinkedList<String> scopeName = new LinkedList<String>();
scopeName.addFirst("/");
int endSignIndex; /* indeks na kojem pocinje $end */
String signalName; /* ime bez scopea */
StringBuffer completeSignalName = new StringBuffer(""); /* ime sa scopeom */
while (!vcdLines[index].equals("$enddefinitions $end"))
{
endSignIndex = vcdLines[index].indexOf("$end");
if (vcdLines[index].indexOf("$var") == 0)
{
// Popravio Marko Cupic - 2007-11-14 - 9 sati navecer, nakon dugog dana!
String[] parts = vcdLines[index].split(" ");
asciiSignalSimbols.add(parts[3]);
signalName = parts[4];
completeSignalName.setLength(0);
completeSignalName.append(scopeName.getFirst()).append(signalName);
if (!parts[2].equals("1"))
{
completeSignalName.insert(0, "+ ");
}
signalValues.put(completeSignalName.toString(), new ArrayList<String>());
if (completeSignalName.length() > maximumSignalNameLength)
{
maximumSignalNameLength = completeSignalName.length();
}
/*
* OVO ISPOD JE ORIGINAL OD BORISA, I MOJI KOMENTARI
asciiSignalSimbols.add(vcdLines[index].charAt(11));
// Ako je signal visebitni (viseznamenkasti), ime nije na poziciji 13!
signalName = vcdLines[index].substring(13, endSignIndex - 1);
completeSignalName.setLength(0);
completeSignalName.append(scopeName.getFirst()).append(signalName);
// signal koji je 16-bitni pocinje s 1 i treba dobiti +!!!
if (vcdLines[index].charAt(9) != '1')
{
completeSignalName.insert(0, "+ ");
}
signalValues.put(completeSignalName.toString(), new ArrayList<String>());
if (completeSignalName.length() > maximumSignalNameLength)
{
maximumSignalNameLength = completeSignalName.length();
}
*/
}
else if (vcdLines[index].indexOf ("$scope") == 0)
{
scopeName.addFirst(new StringBuffer(scopeName.getFirst())
.append(vcdLines[index].substring(14, endSignIndex - 1)).append("/").toString());
}
else
{
if(!scopeName.isEmpty()) {
scopeName.removeFirst ();
}
}
index++;
}
}
/**
* Metoda koja parsira cijelu VHDL-simulaciju. Nakon sto se upotrijebi ova
* metoda dobije se rezultat u obliku stringa kojeg vraca
* getResultInString() metoda. Za potpuno parsiranje dovoljna je upotreba
* samo ove metode.
*/
public void parse()
{
/* Pretrazuje sve do kraja datoteke, tj. polja Stringova. Izmedu svake
* tocke u kojima se dogada promjena (tocke tranzicije) cita red po red i
* provjerava vrijednosti signala. Ako vrijednost pocinje s slovom 'b'
* znaci da slijedi vrijednost vektora iza koje se nalazi znak praznine i
* tek onda ASCII simbol, inace obraduje normalni signal. Pronalazi ASCII
* simbol za svkai od signala i usporeduje koji je to signal u polju po redu
* tako da automatski moze dodati pod tim indeksom novu vrijednost u
* privremenom polju values. Nakon svake iteracije pune se mapa s
* vrijednostima polje values, s tim da oni signali koji nisu mijenjali
* vrijednost imaju vrijednost od prethodne iteracije.
*/
parseSignals();
/* vrijednosti pojedinih signala. Mogu biti 0, 1, U, X, Z... */
String[] values = new String[asciiSignalSimbols.size()];
String asciiSimbol;
int position;
int positionOfWhiteSpaceChar;
/* odmah dodaje nulu. Nula predstavlja pocetak simulacije */
transitionPoints.add(Long.valueOf(vcdLines[++index].substring(1)));
boolean isSharpFound = true;
for (++index; index < vcdLines.length; index++)
{
while (vcdLines[index].charAt(0) != '#')
{
if (vcdLines[index].charAt(0) != 'b')
{
asciiSimbol = vcdLines[index].substring(1);
position = asciiSignalSimbols.indexOf(asciiSimbol);
values[position] = String.valueOf(vcdLines[index].charAt(0));
}
else
{
positionOfWhiteSpaceChar = vcdLines[index].indexOf(' ');
asciiSimbol = vcdLines[index].substring(positionOfWhiteSpaceChar + 1);
position = asciiSignalSimbols.indexOf(asciiSimbol);
values[position] = vcdLines[index].substring (1, positionOfWhiteSpaceChar);
}
index++;
if (index == vcdLines.length) {
isSharpFound = false;
break;
}
}
if (isSharpFound) {
transitionPoints.add (Long.valueOf(vcdLines[index].substring(1)));
} else {
transitionPoints.add(transitionPoints.get(transitionPoints.size() - 1) * 2);
}
Iterator<List<String>> e = signalValues.values().iterator();
int i = 0;
while (e.hasNext())
{
(e.next()).add(values[i++]);
}
}
this.resultToString();
}
/**
* Getter koji vraca rezultat simulacije kao jedan string
*
* @return Rezultat simulacije predstavljen kao String
*/
public String getResultInString ()
{
return resultInString.toString();
}
/**
* Getter koji vraca mapu imena signala i njihovih vrijednosti
*/
public Map<String, List<String>> getSignalValues ()
{
return signalValues;
}
/**
* Getter koji vraca tocke promjene vrijednosti
*/
public List<Long> getTransitionPoints ()
{
return transitionPoints;
}
/**
* Getter koji vraca najduze ime signala
*/
public int getMaximumSignalNameLength ()
{
return maximumSignalNameLength;
}
/**
* Metoda transformira mapu sa imenima signala i njihovim vrijednostima, te
* listu sa svim tockama u kojima se dogada promjena vrijednosti signala u
* jedan string pomocu internog formata, npr: A###B%%%0&&&1&&&Z###1&&&Z&&&1
* Razlog je tome sto se pomocu HTTP-a ne mogu prenositi objekti
*/
private void resultToString ()
{
for (String key : signalValues.keySet())
{
resultInString.append(key).append(LIMITER);
}
/* ukloni zadnji limiter */
resultInString.delete(resultInString.length() - 3, resultInString.length());
/* stavi limiter izmedu imena signala i vrijednosti */
resultInString.append(HEAD_LIMITER);
for (List<String> values : signalValues.values())
{
for (String string : values)
{
resultInString.append(string).append(VALUE_LIMITER);
}
resultInString.delete(resultInString.length() - 3, resultInString.length());
resultInString.append(LIMITER);
}
resultInString.delete(resultInString.length() - 3, resultInString.length());
/*
* stavli limiter izmedu vrijednosti signala i tocaka u kojima se dogada
* promjena vrijednosti
*/
resultInString.append(HEAD_LIMITER);
for (Long point : transitionPoints)
{
resultInString.append(point).append(LIMITER);
}
/* ukloni zadnji limiter */
resultInString.delete(resultInString.length() - 3, resultInString.length());
/* broj znakova najduljeg imena signala */
resultInString.append(HEAD_LIMITER).append(maximumSignalNameLength);
}
}