From e1d03da99e30069198d74ec32d4271ba30d8ffa6 Mon Sep 17 00:00:00 2001 From: Eoin Houlihan Date: Mon, 13 Mar 2017 12:00:11 +0000 Subject: [PATCH 01/37] Make the web more semantic --- panacea/web/static/js/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panacea/web/static/js/view.js b/panacea/web/static/js/view.js index fa2625f5..5100af51 100644 --- a/panacea/web/static/js/view.js +++ b/panacea/web/static/js/view.js @@ -45,7 +45,7 @@ export const displayDdis = (ddis, urisToLabels) => { const drugA = urisToLabels[uriA]; const drugB = urisToLabels[uriB]; - return `
  • ${drugA} and ${drugB}
  • `; + return `
  • ${drugA} and ${drugB}
  • `; }).join(''); ddisTextElement.innerHTML = `

    ${preamble}

    `; From fc84961168b8cfcf6ea93fa7a0431ba7cff718aa Mon Sep 17 00:00:00 2001 From: Eoin Houlihan Date: Tue, 14 Mar 2017 00:34:57 +0000 Subject: [PATCH 02/37] Service logs are build artifacts (#135) --- circle.yml | 19 ++++++++++++------- end-2-end.sh | 4 ---- 2 files changed, 12 insertions(+), 11 deletions(-) delete mode 100755 end-2-end.sh diff --git a/circle.yml b/circle.yml index e835b78e..7703a03c 100644 --- a/circle.yml +++ b/circle.yml @@ -12,17 +12,22 @@ dependencies: compile: override: - - echo "panacea asclepius athloi" | parallel --trim r -d " " "docker build --rm=false -t tomtoothfairies/{} {}" + - docker build --rm=false -t tomtoothfairies/panacea panacea + - docker build --rm=false -t tomtoothfairies/asclepius asclepius + - docker build --rm=false -t tomtoothfairies/athloi athloi test: pre: - - mkdir -p $CIRCLE_TEST_REPORTS/exunit - - mkdir -p $CIRCLE_TEST_REPORTS/pytest - - mkdir -p $CIRCLE_TEST_REPORTS/cucumber - - mkdir -p $CIRCLE_ARTIFACTS/screenshots + - mkdir -p $CIRCLE_TEST_REPORTS/{exunit,pytest,cucumber} + - mkdir -p $CIRCLE_ARTIFACTS/{screenshots,logs} override: - - docker-compose -f docker-compose.e2e.yml -f docker-compose.e2e.ci.yml up -d - docker run -t -e "MIX_ENV=test" -e "CI=true" -v $CIRCLE_TEST_REPORTS/exunit:/test-reports tomtoothfairies/panacea mix test - docker run -t -v $CIRCLE_TEST_REPORTS/pytest:/test-reports tomtoothfairies/asclepius pytest --junitxml=/test-reports/test-junit-report.xml - - ./end-2-end.sh + - docker-compose -f docker-compose.e2e.yml -f docker-compose.e2e.ci.yml run athloi + + post: + - docker logs pathways_panacea_1 > $CIRCLE_ARTIFACTS/logs/panacea.log + - docker logs pathways_asclepius_1 > $CIRCLE_ARTIFACTS/logs/asclepius.log + - docker logs pathways_chiron_1 > $CIRCLE_ARTIFACTS/logs/chiron.log + - docker logs pathways_selenium_1 > $CIRCLE_ARTIFACTS/logs/selenium.log diff --git a/end-2-end.sh b/end-2-end.sh deleted file mode 100755 index 9f5559c9..00000000 --- a/end-2-end.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -docker logs -f pathways_athloi_1 -exit `docker wait pathways_athloi_1` From 9f68d469ec0983a43e6b8ac3e997ef3fbc029b97 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Tue, 14 Mar 2017 12:44:38 +0000 Subject: [PATCH 03/37] follow PML spec in repo Parser and lexer have been changed to adhere to the PML sepcification given here: https://github.com/andrewbutterfield/CS4098-2017/blob/84a0cbfb7931071f5ef8c2ed516f3289e1f6df83/DocPML.pdf Our implementation differs in a few small regards: `manual` and `executable` are allowed as variable names. These are supposedly keywords in the language but there is some sample pml from PEOS where this happens: https://github.com/jnoll/peos/blob/master/compiler/models/edit-compile-test.pml#L9 The expression productions outlined in the spec have some odd characteristics. For example, comparing a variable to a value is not allowed: `variable == "string"` But comparing an attribute of a variable is: `variable.attribute == "string"` Our implementation allows variables and values to be compared (seems more sensisble). --- panacea/src/pml_lexer.xrl | 34 +++++-- panacea/src/pml_parser.yrl | 203 +++++++++++++++++++++++++++++-------- 2 files changed, 185 insertions(+), 52 deletions(-) diff --git a/panacea/src/pml_lexer.xrl b/panacea/src/pml_lexer.xrl index eb544b36..ab7db8a7 100644 --- a/panacea/src/pml_lexer.xrl +++ b/panacea/src/pml_lexer.xrl @@ -2,14 +2,14 @@ Definitions. IDENT = [a-zA-Z0-9_]+ WS = [\s\t\n\r]+ +NUMBER = [0-9]+(\.[0-9]+)? STRING = "[^"]*" -ACTION_TYPE = (manual|executable) COMMENT = \/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\/ Rules. -\{ : {token, {'{', TokenLine}}. -\} : {token, {'}', TokenLine}}. +% keywords + drug : {token, {drug, TokenLine}}. process : {token, {process, TokenLine}}. task : {token, {task, TokenLine}}. @@ -25,12 +25,32 @@ script : {token, {script, TokenLine}}. tool : {token, {tool, TokenLine}}. input : {token, {input, TokenLine}}. output : {token, {output, TokenLine}}. -{ACTION_TYPE} : {token, {action_type, TokenLine}}. +manual : {token, {manual, TokenLine}}. +executable : {token, {executable, TokenLine}}. + +% symbols + +\{ : {token, {'{', TokenLine}}. +\} : {token, {'}', TokenLine}}. +\( : {token, {'(', TokenLine}}. +\) : {token, {')', TokenLine}}. +\|\| : {token, {'||', TokenLine}}. +&& : {token, {'&&', TokenLine}}. +== : {token, {'==', TokenLine}}. +< : {token, {'<', TokenLine}}. +> : {token, {'>', TokenLine}}. +<= : {token, {'<=', TokenLine}}. +>= : {token, {'>=', TokenLine}}. +!= : {token, {'!=', TokenLine}}. +! : {token, {'!', TokenLine}}. +\. : {token, {'.', TokenLine}}. + +% primitives + {COMMENT} : skip_token. +{WS} : skip_token. {STRING} : {token, {string, TokenLine, TokenChars}}. -\. : {token, {dot, TokenLine}}. -== : {token, {equals, TokenLine}}. +{NUMBER} : {tokem, {number, TokenLine}}. {IDENT} : {token, {ident, TokenLine}}. -{WS} : skip_token. Erlang code. diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index 0b42335f..d8d4f539 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -1,65 +1,129 @@ Nonterminals -pml primitive_block primitive_list primitive -action_block action_attributes action_attribute -expression variable operator requires_expr. +pml +primitive_block +primitive_list +primitive +optional_name +action_block +action_attributes +action_attribute +optional_type +expression +expr +logical_combination +operator +requires_expr +operation +value +variable +accessor +identifier +prefix_list +prefix. Terminals -'process' '{' '}' 'task' 'action' 'branch' -'selection' 'iteration' 'sequence' 'provides' -'requires' 'agent' 'script' 'tool' 'string' -'input' 'output' 'ident' 'dot' 'equals' -'action_type' 'drug'. +% keywords + +'process' +'task' +'action' +'branch' +'selection' +'iteration' +'sequence' +'provides' +'requires' +'agent' +'script' +'tool' +'input' +'output' +'manual' +'executable' + +% symbols +'{' +'}' +'(' +')' +'==' +'!=' +'||' +'&&' +'.' +'<' +'>' +'<=' +'>=' +'!' + +% primitives +'string' +'ident' +'drug' +'number'. Rootsymbol pml. +% Associativity of 'operator' symbols. +% Allows shift/reduce conflicts to be resolved at compile time +% See https://www.gnu.org/software/bison/manual/html_node/Shift_002fReduce.html +Nonassoc 200 '==' '!=' '||' '&&' '<' '>' '<=' '>=' '!' '.' ')'. + pml -> 'process' 'ident' primitive_block : '$3'. primitive_block -> - '{' primitive_list : '$2'. + '{' primitive_list '}' : '$2'. primitive_list -> - '}' : []. + '$empty' : []. primitive_list -> primitive primitive_list : '$1' ++ '$2'. primitive -> - 'task' ident primitive_block : '$3'. -primitive -> - 'action' ident 'action_type' action_block : '$4'. -primitive -> - 'action' ident action_block : '$3'. -primitive -> - 'branch' primitive_block : '$2'. + 'branch' optional_name primitive_block : '$3'. primitive -> - 'branch' ident primitive_block : '$3'. + 'selection' optional_name primitive_block : '$3'. primitive -> - 'selection' primitive_block : '$2'. + 'iteration' optional_name primitive_block : '$3'. primitive -> - 'selection' ident primitive_block : '$3'. + 'sequence' optional_name primitive_block : '$3'. primitive -> - 'sequence' primitive_block : '$2'. + 'task' optional_name primitive_block : '$3'. +% action names are required and have a different block +% to other primitives primitive -> - 'sequence' ident primitive_block : '$3'. -primitive -> - 'iteration' primitive_block : '$2'. -primitive -> - 'iteration' ident primitive_block : '$3'. + 'action' ident optional_type action_block : '$4'. + +optional_name -> + '$empty' : []. +optional_name -> + 'ident' : []. + +optional_type -> + '$empty' : []. +optional_type -> + 'manual' : []. +optional_type -> + 'executable' : []. action_block -> - '{' action_attributes : '$2'. + '{' action_attributes '}' : '$2'. action_attributes -> - '}' : []. + '$empty' : []. action_attributes -> action_attribute action_attributes : '$1' ++ '$2'. -action_attribute -> - 'requires' '{' requires_expr '}' : '$3'. action_attribute -> 'provides' '{' expression '}' : []. +% `requires` uses a different expression production as +% drugs are only allowed in `requires `blocks +action_attribute -> + 'requires' '{' requires_expr '}' : '$3'. action_attribute -> 'agent' '{' expression '}' : []. action_attribute -> @@ -67,9 +131,9 @@ action_attribute -> action_attribute -> 'tool' '{' 'string' '}' : []. action_attribute -> - 'input' '{' expression '}' : []. + 'input' '{' 'string' '}' : []. action_attribute -> - 'output' '{' expression '}' : []. + 'output' '{' 'string' '}' : []. requires_expr -> 'drug' '{' 'string' '}' : extract_drug('$3'). @@ -78,26 +142,75 @@ requires_expr -> expression : []. expression -> + expr logical_combination : []. + +logical_combination -> + '&&' expr logical_combination : []. +logical_combination -> + '||' expr logical_combination : []. +logical_combination -> + '$empty' : []. + +expr -> + value operation : []. + +operation -> + operator value : []. +operation -> + '$empty' : []. + +value -> + '!' expression : []. +value -> + '(' expression ')' :[]. +value -> 'string' : []. - -expression -> +value -> + 'number' : []. +value -> variable : []. -expression -> - variable operator variable : []. - variable -> - 'ident' : []. + identifier accessor : []. variable -> - 'action_type' : []. -variable -> - 'ident' 'dot' variable : []. -variable -> - 'action_type' 'dot' variable : []. + prefix prefix_list accessor : []. -operator -> - 'equals' : []. +identifier -> + 'ident' : []. +% some of jnoll's sample pml has these keywords +% on the RHS of expressions! +identifier -> + 'manual' : []. +identifier -> + 'executable' : []. + +prefix -> + '(' ident ')' : []. + +prefix_list -> + ident : []. +prefix_list -> + prefix prefix_list : []. +prefix_list -> + '$empty' : []. + +accessor -> + '$empty' : []. +accessor -> + '.' 'ident' : []. +operator -> + '==' : []. +operator -> + '!=' : []. +operator -> + '<' : []. +operator -> + '>' : []. +operator -> + '<=' : []. +operator -> + '>=' : []. Erlang code. From 37f2edb747d130fd390cfb3c4331729076b5760e Mon Sep 17 00:00:00 2001 From: c-brenn Date: Tue, 14 Mar 2017 12:49:45 +0000 Subject: [PATCH 04/37] more sample pml for thorough testing --- .../jnolls_pml/Clinical_Assessment.pml | 23 + .../jnolls_pml/Dementia_management.pml | 143 +++++ .../fixtures/jnolls_pml/Lab_Assessment.pml | 15 + .../jnolls_pml/Patient_assessment.pml | 12 + .../fixtures/jnolls_pml/TeamAB_Sprint.pml | 223 ++++++++ .../test/fixtures/jnolls_pml/build_test.pml | 10 + .../fixtures/jnolls_pml/commit_changes.pml | 51 ++ .../fixtures/jnolls_pml/contradiction.pml | 5 + .../fixtures/jnolls_pml/incremental_test.pml | 275 ++++++++++ .../jnolls_pml/netbeans_req_release.pml | 306 +++++++++++ .../fixtures/jnolls_pml/recursion_bug.pml | 180 +++++++ panacea/test/fixtures/jnolls_pml/run_peos.pml | 99 ++++ panacea/test/fixtures/jnolls_pml/simple.pml | 23 +- .../test/fixtures/jnolls_pml/test_commit.pml | 36 ++ panacea/test/fixtures/jnolls_pml/web_test.pml | 505 ++++++++++++++++++ 15 files changed, 1892 insertions(+), 14 deletions(-) create mode 100644 panacea/test/fixtures/jnolls_pml/Clinical_Assessment.pml create mode 100644 panacea/test/fixtures/jnolls_pml/Dementia_management.pml create mode 100644 panacea/test/fixtures/jnolls_pml/Lab_Assessment.pml create mode 100644 panacea/test/fixtures/jnolls_pml/Patient_assessment.pml create mode 100644 panacea/test/fixtures/jnolls_pml/TeamAB_Sprint.pml create mode 100644 panacea/test/fixtures/jnolls_pml/build_test.pml create mode 100644 panacea/test/fixtures/jnolls_pml/commit_changes.pml create mode 100644 panacea/test/fixtures/jnolls_pml/contradiction.pml create mode 100644 panacea/test/fixtures/jnolls_pml/incremental_test.pml create mode 100644 panacea/test/fixtures/jnolls_pml/netbeans_req_release.pml create mode 100644 panacea/test/fixtures/jnolls_pml/recursion_bug.pml create mode 100644 panacea/test/fixtures/jnolls_pml/run_peos.pml create mode 100644 panacea/test/fixtures/jnolls_pml/test_commit.pml create mode 100644 panacea/test/fixtures/jnolls_pml/web_test.pml diff --git a/panacea/test/fixtures/jnolls_pml/Clinical_Assessment.pml b/panacea/test/fixtures/jnolls_pml/Clinical_Assessment.pml new file mode 100644 index 00000000..0d35c39a --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/Clinical_Assessment.pml @@ -0,0 +1,23 @@ +process Clinical_Assessment { + iteration { + iteration { + action PresentToSpecialistClinician { + requires { reported_symptoms } + provides { scheduled_examination } + } + action Examine { + requires { scheduled_examination } + provides { examination_results } + } + } + action Diagnose { + requires { examination_results } + provides { diagnosis } + } + action Treat { + requires { diagnosis } + provides { treatment } + } + + } +} diff --git a/panacea/test/fixtures/jnolls_pml/Dementia_management.pml b/panacea/test/fixtures/jnolls_pml/Dementia_management.pml new file mode 100644 index 00000000..7d90c956 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/Dementia_management.pml @@ -0,0 +1,143 @@ +/* + * Dementia management pathway, from Pádraig O’Leary, John Noll, and Ita Richardson, + * "A Resource Flow Approach to Modelling Care Pathways," FHEIS 2013, Macao. + */ + +process Dementia_management { + + action identify_causal_or_exacerbating_factors { + requires { Guidelines_For_Treatment_Of_Patients } + } + action provide_patient_carer_with_information { + agent {GP && patient && carer} + requires { + patient_record.Confirmed_Dementia + && patient_record.requests_privacy == "false" + } + provides { information_to_carer } + } + action create_treatment_or_care_plan { + agent { + memory_assessment_service + && GP && clinical_psychologiest && nurses + && occupational_therapists && phsiotherapists + && speech_and_language_therapists + && social_workers && voluntary_organisation + } + requires { patient_history } + provides { care_plan } + } + action formal_review_of_care_plan { + agent { person && carer } + requires { care_plan } + provides { care_plan.reviewed == "true" } + } + action assess_carer_needs { + agent { carer} + provides { care_plan.respite_care } + } + + branch { + + branch cognitive_symptom_mgt { + + action non_medication_interventions { + provides { + support_for_carer && info_about_servicesAndInterventions + && (optional) cognitive_simulation + } + } + action medication_interventions { + agent {specialist && carer} + requires { (intangible)carer_view_on_patient_condition } + provides { prescription } + } + + } /* end of management_of_cognitive_symptoms */ + + branch non_cognitive_symptom_mgt { + + action non_medication_interventions { + agent {carer && patient} + requires { (intangible)non_cognitive_symptoms || (intangible) challenging_behaviour } + provides { early_assessment } + } + action medication_interventions { + requires { (intangible) risk_of_harm_or_distress} + provides { medication } + } + + } /* end of management_of_non_cognitive_symptoms */ + + } /* end cognitive/non-cognitive symptoms branch */ + + + + action obtain_carers_view { + agent { carer } + provides { (intangible) view_on_condition} + } + action document_symptoms { + agent { patient } + provides { patient_record.symptoms } + } + /* optional, if required */ + action assess_behavioural_disturbances { + agent { patient } + requires { (intangible) risk_of_behavioural_disturbance } + provides { care_plan.appropriate_setting } + } + + + + + branch { + iteration { + action record_changes { + agent { patient } + provides { patient_record.symptoms } + provides { (optional) medication } + } + } + + iteration { + action monitor_patients_on_medication{ + agent { patient } + provides { (optional)care_plan.medication } + } + action consider_alt_assessments { + requires { + patient_record.disability + || patient_record.sensory_impairment + || patient_record.lingustic_problems + || patient_record.speech_problems + } + provides { care_plan.alternative_assessment_method } + } + } + + iteration { + + action manage_comorbidity { + /*requires { }*/ + provides { + comorbidity.depression + && comorbidity.psychosis + && comorbidity.delirium + && comorbidity.parkinsons_disease + && comorbidity.stroke + } + } + + action psychosocial_interventions { + requires { comorbidity.depression || comorbidity.anxiety } + agent { carer} + } + + } + + } /* branch */ + +} /* process */ + + diff --git a/panacea/test/fixtures/jnolls_pml/Lab_Assessment.pml b/panacea/test/fixtures/jnolls_pml/Lab_Assessment.pml new file mode 100644 index 00000000..8785ca35 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/Lab_Assessment.pml @@ -0,0 +1,15 @@ +process Lab_Assessment{ + action PresentToSpecialistLab { + requires { symptoms } + provides { test_plan } + } + action PrepareTests { + requires { test_plan } + provides { test_suite } + } + action RunTests { + requires { test_suite && examination_results + && diagnosis } + provides { diagnosis.status == "tested" } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/Patient_assessment.pml b/panacea/test/fixtures/jnolls_pml/Patient_assessment.pml new file mode 100644 index 00000000..e6ee4857 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/Patient_assessment.pml @@ -0,0 +1,12 @@ +process Diabetes_assessment { + + action Get_patient_symptoms { + requires {patient_record} + provides {patient_symptoms} + } + + action Assess_patient_symptoms { + requires {patient_symptoms} + provides {assessment.diagnosis} + } +} \ No newline at end of file diff --git a/panacea/test/fixtures/jnolls_pml/TeamAB_Sprint.pml b/panacea/test/fixtures/jnolls_pml/TeamAB_Sprint.pml new file mode 100644 index 00000000..e2870117 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/TeamAB_Sprint.pml @@ -0,0 +1,223 @@ +process BAU_Sprint_AsIs { + + sequence sprint_planning { + action agree_sprint_goal { + provides { sprint_goal } + script { "Team agree on objectives for current sprint." } + agent { Developer && QA && ProductOwner && ScrumMaster } + } + + action agree_effort_target { + requires { velocity } + provides { sprint_target } + script { "Scrum master sets initial target (in points) based on + velocity of last three sprints, then subtracts points if + people will be off on leave or training, or working on other tasks. + Process adjustments from retrospective will be factored in somehow." + } + agent { ScrumMaster && Developer && QA } + } + + iteration create_sprint_backlog { + action propose_issue { + script { "Product Owner proposes dev_issue for sprint backlog." } + requires { dev_issue.on_candidate_backlog } + provides { dev_issue.on_candidate_backlog } + agent { ProductOwner } + } + + action eval_issue { + script { "Team decides whether issue will fit into + current sprint. + + Scrum master guides this decision. If + there is a large high-priority task already on the + backlog, lower-priorty tasks will be added to fill the backlog. + + Also, backlog should contain both large and small + tasks so developers can see progress, and should not + load QA at the end while leaving him idle at + beginning." + } + requires { dev_issue.on_candidate_backlog } + provides { dev_issue.ok_to_impl || dev_issue.too_large } + agent { Developer && QA && ScrumMaster} + } + + selection { + action add_issue_to_backlog { + script { "If issue will fit into current sprint window, PO adds issue to sprint backlog. " } + requires { dev_issue.ok_to_impl } + provides { dev_issue.on_sprint_backlog } + agent { ProductOwner } + } + } + } + } + + iteration sprint_execution { + sequence implement_change { + action begin_change { /* Does this really happen? */ + script { "Developer chooses a ticket from the sprint backlog." } + requires { dev_issue.on_sprint_backlog /* in sprint_backlog */ } + provides { dev_issue.status == "started" } + agent { Developer } + } + + action understand_issue { + script { "Developer investigates issue and gathers background to design implementation of change." } + requires { dev_issue.status == "started" } + provides { dev_issue } + agent { Developer && (ProductOwner || QA || Architect || SrDeveloper) } + } + + action identify_unit { + script { "Developer identifies code unit to be modified. May involve asking either QA or Dan." } + requires { dev_issue} + provides { dev_issue && unit_to_be_changed } + agent { Developer && (Architect || SrDeveloper) } + } + + action implement_change { + script { "Developer modifies unit to implement change." } + requires { dev_issue && unit_to_be_changed } + provides { unit_to_be_changed.implemented } + agent { Developer } + } + + action test_change { + script { "Developer manually unit tests modified unit." } + requires { unit_to_be_changed.implemented } + provides { unit_to_be_changed.unit_tested } + agent { Developer } + } + + action build_for_alternate_product { + script { "Developer builds alternate product to ensure change works on both." } + requires { unit_to_be_changed.unit_tested } + provides { product_build } + agent { Developer } + } + + action unit_test_alternate_product { + script { "Developer manually unit tests modified unit." } + requires { product_build } + provides { product_build.unit_tested } + agent { Developer } + } + + action commit_change { + script { "Developer commits change to trunk." } + requires { dev_issue && product_build.unit_tested } + provides { dev_issue.status == "ready_for_QA" } + agent { Developer } + } + } + + sequence test_change { + action request_build { + script { "QA lead requests new build. Normally this + would be for each change, but if the changes are + small/cosmetic they can accumulate so the build effort is + less than the QA effort." } + requires { dev_issue.status == "ready_for_QA" } + provides { build_request } + agent { QA } + } + + action create_build { + script { "Developer or Senior Developer creates build for testing." } + agent { SrDeveloper || Developer } + requires { build_request } + provides { build } + + } + + action perform_system_test { + script { "QA lead performs system test to verify correct behavior." } + agent { QA } + requires { build } + provides { (maybe) new_issue } + } + + iteration { + selection { + action mark_resolved { + requires { build.status == "passed_system_test" && dev_issue.status == "ready_for_QA"} + provides { dev_issue.status == "resolved" } + script { "QA marks issue as tested successfully." } + agent { QA } + } + action add_to_PMQ { /* Product Management Queue */ + requires { build.status == "failed_system_test" && new_issue} + provides { new_dev_issue } + script { "QA lead adds new issue to Product Management Queue" } + agent { QA } + } + action return_to_dev { + requires { build.status == "failed_system_test" && dev_issue.status == "ready_for_QA" } + provides { dev_issue.status == "none" } + agent { QA } + } + } + } + } + } + + + iteration { + branch { + action report_to_customer { + script { "Tech support notifies customer that issue is changed and will be part of a future release." } + requires { dev_issue.status == "resolved" } + provides { dev_issue.fed_back_to_cust } + agent { SupportTech } + } + + selection { + sequence branch_exists { + action copy_to_branch { + script { "If change should be included in branch, developer must copy code to unit on branch." } + requires { dev_issue.status == "resolved" && unit.on_branch && !rel_branch.blocked} + provides { dev_issue.status == "resolved" && unit.changed_on_branch } + agent { Developer } + } + + action test_change_on_branch { + script { "? tests change on branch" } /* Who does this? Who creates build? */ + requires { unit.changed_on_branch } + provides { unit.tested_on_branch } + agent { ExternalQA } + } + } + + action mark_for_future { /* XXX what happens to resolved issues that are blocked due to protected branch? */ + script { "Developer marks issue as needing to be copied to branch but blocked because branch is protected." } + requires { dev_issue.status == "resolved" && unit.on_branch && rel_branch.blocked } + provides { dev_issue.status == "resolved" && unit.on_branch && dev_issue.needs_copy_to_branch } + agent { Developer } + } + } + } + } + + sequence sprint_retrospective { + iteration { + action select_issue_for_demo { + script { "Product Owner selects issue for demonstration in retrospective." } + requires { dev_issue.status == "resolved" } + provides { dev_issue.to_demo } + agent { ProductOwner } + } + } + + iteration { + action demo_issue { + script { "Developers demonstrate issue." } + requires { dev_issue.to_demo } + provides { dev_issue.demoed } + agent { Developer } + } + } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/build_test.pml b/panacea/test/fixtures/jnolls_pml/build_test.pml new file mode 100644 index 00000000..1bbb108d --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/build_test.pml @@ -0,0 +1,10 @@ + process build_test { + action compile { + requires { code } + provides { code.compiles == "true" } + } + action test { + requires { code.compiles == "true" } + provides { test_report } + } + } diff --git a/panacea/test/fixtures/jnolls_pml/commit_changes.pml b/panacea/test/fixtures/jnolls_pml/commit_changes.pml new file mode 100644 index 00000000..a735efbe --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/commit_changes.pml @@ -0,0 +1,51 @@ +process commit_change { + iteration { + iteration { + action update_workspace { + script { "run `cvs update' in workspace." } + } + action resolve_conflicts { + script { + "If any files are preceded by `C' in cvs output, update + caused a conflict. Resolve by looking for strings + `<<<<<<<', `=======', and `>>>>>>>' in updated files; these + delimit areas of conflict." + } + } + action test_changes { + script { "Run `make test' in workspace." } + } + action fix_failures { + script { "Fix any failures uncovered by tests." } + } + } + + action commit_changes { + script { "run `cvs commit -m log-message'. " } + } + + sequence test_integration { + action login_as_testuser { + script { "Login to test host as `jntestuser'." } + } + action delete_old_workspace { + script { "Run `cvs release peos; rm -r peos'." } + } + action checkout_workspace { + script { "Run `cvs checkout peos-test'." } + provides { workspace } + } + action run_tests { + script { "Run `make test' in src/os/kernel, src/ui/web2." } + requires { workspace } + provides { workspace.tests == "passed" || workspace.tests == "failed" } + } + } + } + action complete_commit { + requires { workspace.tests == "passed" } + script { "If all tests passed, you are finished. If not, go back and fix any failures uncovered." } + + } + +} diff --git a/panacea/test/fixtures/jnolls_pml/contradiction.pml b/panacea/test/fixtures/jnolls_pml/contradiction.pml new file mode 100644 index 00000000..1019dbef --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/contradiction.pml @@ -0,0 +1,5 @@ +process contradiction { + action a { + requires { r && !r } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/incremental_test.pml b/panacea/test/fixtures/jnolls_pml/incremental_test.pml new file mode 100644 index 00000000..300e61f1 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/incremental_test.pml @@ -0,0 +1,275 @@ +process incremental_test { + action introduction { + script { + "What happens when you are tasked with testing a non-trivial product +with which you have no familiarity? Tamres describes an +``incremental approach'' to handling this situation, which builds a +test suite from a very small baseline. + +The intent of this exercise is to provide first-hand experience with +the problems of testing a software product under these conditions as +noted by Tamres: +* a large, complex application; +* limited or no knowledge of the product; +* inadequate user documentation; +* limited testing expertise; +* limited guidance or help with the testing task; +* unrealistic deadline. +[Tamres, p.5] + +You must perform this exercise on a Linux-based platform (The SCU +Design Center servers (linux.dc.engr.scu.edu) meet this +requirement) + +The incremental approach has eight phases, as described in Chapter 1 +of Tamres. Your assignment is to perform each of these phases, as +described below. +" + } + } + /* This is a true iteration: It continues until the tester decides he + has sufficient knowledge of the application to write a baseline test. */ + iteration explore { + action develop_exploratory_test { + requires { product_documentation } + provides { exploratory_test } + script { +"Develop tests that help you become familiar with the product and it's +behavior in response to various inputs. Use whatever resources are +available: +* tutorials or training material +* engineering or user documentation +* have the project authority demonstrate the product +* random experimentation +* try all options, one at a time +* observe behavior from previous tests, and develop new ones in response +[Tamres, p.7] + +The intent is to see how the product works by running it. Repeat these +steps until you can predict the product's behavior to specific input +before running the test." + } + } + action run_exploratory_test { + requires { exploratory_test } + provides { exploratory_test_results } + } + action analyze_exploratory_test_output { + requires { exploratory_test_results } + provides { exploratory_test_analysis } + } + action record_exploratory_results { + requires { exploratory_test_results && test_report } + provides { test_report } + } + } + + /* Note: in practice, this is iterative, until results are as expected. + But the intent is to be able to write a baseline test derived from the + knowledge uncovered during exploration. */ + sequence baseline { + action develop_baseline { + requires { test_report && product_documentation} + provides { baseline_test } + script { +"Now you have to write a baseline test. This should be the simplest +input that could be expected to work, using the default options. + +Before running the baseline, you must define the expected output. +Using the results from the exploratory phases recorded in the test +report ($test_report) and the product documentation +($product_documentation), define the expected output for your test +input. Have the project authority validate this expectation." + } + } + action run_baseline { + requires { baseline_test } + provides { baseline_test_results } + } + action analyze_baseline_test_output { + requires { baseline_test_results } + provides { baseline_test_analysis } + } + } + action record_baseline_results { + requires { baseline_test_results && test_report } + provides { test_report } + + } + + iteration trends_analysis { + action develop_trend_test { + requires { test_report && product_documentation } + provides { trend_test } + script { +"This is an optional phase. Do these steps if +* input and output are numeric values +* data value boundaries are not specified +* calculating expected results is difficult +* you have an idea of the range of expected values +* you need more familiarity with the product [Tamres, p.9] +" + } + } + action run_trend_test { + requires { trend_test } + provides { trend_test_results } + } + action analyze_trend_test_output { + requires { trend_test_results } + provides { trend_test_analysis } + } + action record_trends_results { + requires { trends_test_results && test_report } + provides { test_report } + } + } + + sequence inventory { + action enumerate_inputs { + requires { product_documentation } + provides { input_inventory } + script { +"In this stage, you catalog all of the product inputs, and enumerate +the states each of those inputs can have. The goal is to test each +input in each state at least once, by incrementally modifying the +baseline test. Use the product documentation ($product_documentation) as +a guide for discovering the valid inputs." + } + } + /* Note: following is really a 'foreach'. */ + iteration do_inventory_tests { + action develop_inventory_test { + requires { /* input in */ input_inventory } + provides { inventory_test } + script { +"For each input in the input inventory ($input_inventory), define an +input case and expected result(s)." + } + } + action run_inventory_test { + requires { inventory_test } + provides { inventory_test_results } + } + action analyze_inventory_test_output { + requires { inventory_test_results } + provides { inventory_test_analysis } + } + action record_inventory_results { + requires { inventory_test_results && test_report } + provides { test_report } + } + } + } + + sequence inventory_combinations { + action enumerate_input_combinations {} + iteration do_inventory_combination_tests { + action develop_combination_test { + requires { input_inventory } + provides { combination_test } + } + action run_combination_test { + requires { combination_test } + provides { combination_test_results } + } + action analyze_combination_test_output { + requires { combination_test_results } + provides { combinatino_test_analysis } + } + action record_combination_results { + requires { combination_test_results && test_report } + provides { test_report } + } + } + } + + iteration probe_boundaries { + action develop_boundary_test {} + action run_boundary_test {} + action analyze_boundary_test_output {} + action record_boundary_results { + requires { stress_test_results && test_report } + provides { test_report } + } + } + + + iteration devious_tests { + action develop_devious_test { + script { +"Using your evolving knowledge of the product, attempt to break it by +developing test cases with devious input. For example: +* no input data +* invalid data (negative number, alphanumeric strings) +* illegal formats +* unusual data combinations +* corner cases, such as zero as a value +[Tamres, p. 20] +Note that the expected result may be an error message or graceful exit." + } + } + action run_devious_test {} + action analyze_devious_test_output {} + action record_deviousresults { + requires { devious_test_results && test_report } + provides { test_report } + } + } + + iteration stress_environment { + action develop_stress_test { + script { +"Some applications should be tested with restricted resources: +* reduced memory +* limited or no disk space +* multiple concurrent instances of application +* running while backup in progress +* many asynchronous event driven processes +[Tamres, p21] +Also consider adverse events, to assess recovery and resilience: +* abort in middle of an operation +* disconnecting cables (i.e., network) +* power loss +" + } + } + action run_stress_test {} + action analyze_stress_test_output {} + action record_stress_results { + requires { stress_test_results && test_report } + provides { test_report } + } + } + + sequence write_test_report { + action write_title_page {} + action insert_test_results {} + action format_document {} + action spell_check_document { + requires { test_report } + provides { test_report.spell_checked } + } + action proofread_document{ + requires { test_report } + provides { test_report.proofread } + } + } + + /* Note: This is really a separate process. */ + selection submit_document { + action submit_hardcopy { + requires { pdf_test_report && + test_report.spell_checked && + test_report.proofread + } + } + action submit_electronic_copy { + requires { pdf_test_report && + test_report.spell_checked && + test_report.proofread + } + provides { ack_message } + } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/netbeans_req_release.pml b/panacea/test/fixtures/jnolls_pml/netbeans_req_release.pml new file mode 100644 index 00000000..ab7d8db6 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/netbeans_req_release.pml @@ -0,0 +1,306 @@ +process RequirementsAndRelease { + sequence Requirements { + sequence SetProjectTimeline { + action ReviewNetBeans { + requires { NetBeansRoadmap } +/* provides { }*/ + tool { "WebBrowser " } + agent { "Previous release manager, Sun ONE Studio development team (e.g. Forte'), Sun ONE Studio Marketing Manager " } + script { + "available at http://www.netbeans.org/devhome/docs/roadmap.html" + } + } + action SetReleaseDate { + requires { NetBeansRoadmap } + provides { ReleaseDate } + tool { "Pen, paper " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + sequence DetermineProject { + branch SunONEStudioDevelopmentMeeting { + action ReviewNetBeansVisionStatmenet { + requires { NetBeansVisionStatement } + /*provides { }*/ + tool { "Web browser " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { + "available at http://www.netbeans.org/about/os/vision.html" + } + } + action ReviewUncompletedMilestonesFromPreviousRelease { + requires { PreviousVersionReleaseDocuments } + provides { ProspectiveFeaturesForUpcomingRelease } + tool { "Web browser " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { + "Available at http://www.netbeans.org/devhome/docs/releases/33/features/nb33-features-overview.html" + } + } + action ReviewIssuzillaFeatureRequests { + requires { IssuzillaIssueRepository } + provides { ProspectiveFeaturesForUpcomingRelease } + tool { "Web browser, Issuezilla " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { + "Go to Issuezilla + Search for feature requests/enhancements from current release" + } + } + } + iteration EstablishFeatureSet { + action CompileListOfPossibleFeaturesToInclude { + requires { ProspectiveFeaturesGatheredFromIssuezilla && ProspectiveFeaturesFromPreviousReleases } + provides { FeatureSetForUpcomingRelease } + tool { "Pen, paper " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + action CategorizeFeaturesProposedFeatureSet /*into"Must Have," "Should Have," and "Nice to Have"*/ { + requires { FeatureSetForUpcomingRelease } + provides { WeightedListOfFeaturesToImplement /* (signifying relative importance) */ } + tool { "Pen, paper " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + action SendMessageToCommunityForFeedback /* {nbdev, nbusers, qa, nbdiscuss }*/ { + requires { WeightedListOfFeaturesToImplement } + /*provides { }*/ + tool { "Email client " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + action ReviewFeedbackFromCommunity /* r.e. proposed feature set*/ { + requires { FeebackMessagesOnMail } + provides { PotentialRevisionsToDevelopmentProposal } + tool { "Web browser " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + action ReviseProposalBasedOnFeedback { + requires { PotentialRevisionsToDevelopmentProposal } + provides { RevisedDevelopmentProposal } + tool { "Pen, paper " } + agent { "Previous release manager, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + } + action PostFinalDevelopmentProposalToNetBeansWebsite { + requires { RevisedDevelopmentProposal } + provides { FinalDevelopmentProposal } + tool { "Web editor " } + agent { "NetBeans web team, Sun ONE Studio Development team, Sun ONE Studio Marketing Manager " } + script { "" } + } + action AssignDevelopersToCompleteProjectMilestones { + requires { RevisedDevelopmentProposal } + /* provides { }*/ + tool { "Email client " } + agent { "NetBeans developers (Sun ONE Studio developers, module developers) " } + script { "" } + } + } + sequence SetReleaseStageCompletionDates { + action SetFeatureFreezeDate { + requires { ReleaseDate } + provides { FeatureFreezeDate } + tool { "" } + agent { "Previous release manager, Sun ONE Studio Development team " } + script { "" } + } + action SetMilestoneCompletionDates { + requires { FeatureFreezeDate && ReleaseDate } + provides { MilestoneCompletionDates } + tool { "" } + agent { "Developer(s) responsible for feature completion " } + script { "" } + } + } + } + sequence EstablishReleaseManager { + action EmailSolicitationForReleaseManager { + /*requires { }*/ + provides { ReleaseManagerRequest } + tool { "Email client " } + agent { "PreviousReleaseManager " } + script { "" } + } + action WaitForVolunteer { +/* requires { } + provides { }*/ + tool { "" } + agent { "" } + script { "Wait for community members to volunteer to be the release manager " } + } + iteration CollectCandidacyNominations { + action SendCandidacyAnnouncement { + requires { ReleaseManagerRequest } + provides { ReleaseManagerCandidacyAnnouncement } + tool { "Email client " } + agent { "Release manager candidate " } + script { "" } + } + } + action EstablishReleaseManagerConsensus { + requires { ReleaseManagerCandidacyAnnouncements } + provides { ReleaseManagerDecision } + tool { "Email client " } + agent { "NetBeans community (nbdev mailing list) " } + script { "" } + } + action AnnounceNewReleaseManager { + requires { ReleaseManagerDecision } + provides { ReleaseManagerAnnoucementToNbdevMailingList } + tool { "Email client " } + agent { "Previous release manager " } + script { "" } + } + } + action SolicitModuleMaintainersForInclusionInUpcomingRelease { + requires { FeatureFreezeDate } + provides { ModuleInclusionNoticeToNbdevMailingList} + tool { "Email client " } + agent { "Release manager " } + script { "" } + } + } + sequence Release { + iteration Stabilization { + sequence Build { + action ChangeBuildBranchName { + requires { CvsCodeRepository } + provides { NewBranchForCurrentBuild } + tool { "CVS code repository " } + agent { "Release manager, module maintainers " } + script { "" } + } + iteration MakeInstallTar { + action MakeInstallTarForEachPlatform { + requires { DevelopmentSourceForEachPlatform } + provides { InstallExecutableTar } + tool { "Ant, Build tools, Install tools, Tar, Zip tools " } + agent { "Release manager, build automation script" } + script { "" } + } + } + } + sequence Deploy { + action UploadInstallTarFilesToWebRepository { + requires { BinaryReleaseDownloads && WebRepository } + /* provides { }*/ + tool { "FTP client " } + agent { "Release manager " } + script { "" } + } + action UpdateWebPage { + requires { ProjectWeb } + provides { UpdatedWeb } + tool { "Web editor " } + agent { "Web team " } + script { "" } + } + action MakeReadmeInstallationNotesAndChangelog { + requires { ChangesFromIndividualModules} + provides { README && InstallationNotes && Changelog} + tool { "Text editor " } + agent { "Release manager " } + script { "" } + } + action SendReleaseNotificationToCommunityInvitingTheCommunityToDownloadAndTestIt { + /* requires { }*/ + provides { ReleaseNotice } + tool { "Email client" } + agent { "Release manager" } + script { "" } + } + } + sequence Test { + action ExecuteAutomaticTestScripts{ + requires { TestScripts && ReleaseBinaries} + provides { TestResults} + tool { "Automated test suite (xtest, others) " } + agent { "Sun ONE Studio QA team " } + script { "" } + } + action ExecuteManualTestScripts{ + requires { ReleaseBinaries} + provides { TestResults} + tool { "NetBeans IDE " } + agent { "users, developers, Sun ONE Studio QA team, Sun ONE Studio developers " } + script { "" } + } + iteration UpdateIssuezilla{ + action ReportIssuesToIssuezilla{ + requires { TestResults} + provides { IssuezillaEntry} + tool { "Web browser " } + agent { "users, developers, Sun ONE Studio QA team, Sun ONE Studio developers " } + script { + "Navigate to Issuezilla. + Select issue number/component to update ." + } + } + action UpdateStandingIssueStatus{ + requires { St&&ingIssueFromIssuezilla && TestResults} + provides { UpdatedIssuezillaIssueRepository} + tool { "Web browser " } + agent { "users, developers, Sun ONE Studio QA team, Sun ONE Studio developers " } + script { "" } + } + } + action PostBugStats{ + requires { TestResults} + provides { BugStatusReport && TestResultReport} + tool { "Web editor, JFreeChart " } + agent { "Release manager " } + script { "" } + } + } + sequence Debug { + action ExamineTestReport { + requires { TestReport && BugStats } + /* provides { }*/ + tool { "Web browser" } + agent { "developers, Sun ONE development team, module maintainers, module contributors, release manager " } + script { "" } + } + action WriteBugFix { + requires { ErroneousSource } + provides { PotentialBugFix } + tool { "Source editor " } + agent { "developers, Sun ONE development team, module maintainers, module contributors, release manager " } + script { "" } + } + action VerifyBugFix { + requires { PotentialBugFix } + provides { WorkingBugFix } + tool { "Source editor, source compiler, test tools " } + agent { "developers, Sun ONE development team, module maintainers, module contributors, release manager " } + script { + "Compile source locally. + Execute module locally. + Perform automatic, manual unit testing. + Perform automatic, manual integration testing." + } + } + action CommitCodeToCvsCodeRepository { + requires { WorkingBugFix && CVSCodeRepsository} + provides { UpdatedSource} + tool { "CVS client " } + agent { "developers, Sun ONE development team, module maintainers, module contributors, release manager " } + script { + "Upload revised source to the CVS code repository in respective branch." + } + } + action UpdateIssuezillaToReflectChanges{ + requires { IssuezillaIssueRepository} + provides { UpdateIssueStatus} + tool { "Web browser " } + agent { "developers, Sun ONE development team, module maintainers, module contributors, release manager " } + script { "" } + } + } + } + } +} + diff --git a/panacea/test/fixtures/jnolls_pml/recursion_bug.pml b/panacea/test/fixtures/jnolls_pml/recursion_bug.pml new file mode 100644 index 00000000..515f67c7 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/recursion_bug.pml @@ -0,0 +1,180 @@ +process Diabetes_Review { + action review_progress_of_all_diabeties_patients { + requires {Guidelines_for_Diabetes} + requires {patient_record.diabetes_symptoms == "true"} + } + iteration { + action review_patient_diabetes { + agent {GP} + requires {patient_record.diabetes_symptoms == "true"} + } + action send_AutoText_to_patient { + requires {patient_appointment.invitation == "accept"} + provides {patient_appointment.arrangement} + } + action patient_appointment { + requires {patient_appointment.attend == "true"} + provides {blood_sample} + } + action transport_blood_sample { + agent {courier} + requires {patient.blood_sample} + provides {safe_transportation_of_blood_sample} + } + action take_blood_sample { + agent {medical_scientist} + requires {patient.blood_sample} + provides {analysis_of_blood_sample} + } + branch{ + action analyse_blood_sample { + agent {centrifugation_machine} + agent {medical_scientist} + requires {patient.blood_sample} + provides {blood_sample.analytics} + } + + action authorise_blood_sample { + agent {medical_scientist} + agent {principal_biochemist} + requires {patient.blood_sample} + provides {blood_sample.authorisation} + + } + action take_blood_sample{ + agent {GP} + requires {patient.blood_sample} + provides {blood_sample.biometric_analysis} + provides {info_on_blood_sample} + provides {consultation_on_blood_sample} + provides {informs_decision_on_need_for_blood_pressure_test} + } + + action arrange_patient_appointment{ + agent {receptionist} + requires {patient_record} + provides {patient_appointment.arrangement} + } + + action patient_appointment { + agent {patient} + requires {patient_appointment.attend == "true"} + provides {patient_progress_review} + } + action assessment_of_patient_blood_test_results { + agent {Community_Nurse && patient} + requires {blood_sample.biometric_analysis} + provides {information_on_blood_sample} + } + action consult_with_patient { + requires {blood_sample.results} + provides {advice_on_psychosocial_self_monitoring} + provides {consultation_on_weight_management} + provides {consultation_on_exercise} + provides {consultation_on_self_management} + } + action update_patient_record { + requires {Patient_Record} + provides {Patient_Record.update} + } + action patient_lifestyle_consultation { + agent {dietician && patient} + requires {blood_sample.results} + provides {information_to_guide_consultation} + } + action setting_goals { + requires {tangible_milestones_for_patient} + provides {self_management_plan_for_patient} + + } + action take_blood_sample { + agent {phlebotomist && patient} + requires {blood_sample} + provides {patient.blood_sample} + provides {blood_sample.results} + } + action fill_out_patient_details { + requires {patient.blood_sample_details} + provides {tracability_of_blood_sample} + } + action fill_out_patient_details{ + agent {phlebotomist && porter} + requires {patient.blood_sample} + provides {transport_patient.blood_sample} + } + } + action analyse_blood_sample{ + agent {medical_scientisit && centrifugation_machine} + requires {patient.blood_sample} + provides {analysis_of_blood_sample} + } + action authorise_blood_sample { + agent {medical_scientist && principal_biochemist} + requires {patient.blood_sample} + provides {blood_sample.authorisation} + } + } + + action update_patient_information{ + agent {receptionist} + requires {patient_record} + } + action check_patient_information { + agent {receptionist && patient} + requires {patient_confirmation_of_details} + provides {accurate_patient_record} + } + action arrange_patient_appointment { + agent {receptionist && patient} + requires {new_appointment_details} + provides {patient_appointment} + } + + action assessment_of_blood_test_results { + agent {Community_Nurse && patient} + requires {blood_sample.biometric_analysis} + provides {information_on_blood_sample} + } + action consult_with_patient { + requires {blood_sample.results} + provides {advice_on_psychosocial_self_monitoring} + provides {consultation_on_weight_management} + provides {consultation_on_exercise} + provides {consultation_on_self_management} + } + action update_patient_record { + requires {Patient_Record} + provides {Patient_Record.update} + } + action lifestyle_consultation { + agent {dietician && patient} + requires {blood_sample.results} + provides {information_to_guide_consultation} + } + action setting_goals{ + requires {tangible_milestones_for_patient} + provides {self_management_plan_for_patient} + } + action optional_retinopathy_screening { + agent {receptionist && patient} + requires {eye_test} /* optional, if required*/ + provides {visual_test_results} + } + action visual_check { + agent {photographer && patient} + requires {patient_record} + requires {consultation && eye_drops} + provides {image_of_eyes} + provides {eye_dilation_analysis} + } + action grade_eye_test_analysis { + agent {grader} + requires {eye_dilation_analysis} + requires {analysis_and_spot_checks} + provides {eye_test_results} + provides {results_on_whether_linking_to_diabetes_condition} + } +} + + + diff --git a/panacea/test/fixtures/jnolls_pml/run_peos.pml b/panacea/test/fixtures/jnolls_pml/run_peos.pml new file mode 100644 index 00000000..a49207c4 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/run_peos.pml @@ -0,0 +1,99 @@ +process implement_peos { + action checkout { + requires { repository } + provides { workspace } + script { "cvs -d $repository checkout peos-test" } + } + action check_tcltk { + script { "Check if '/home/jntestuser/tcl_install/include/tcl.h' exists. If not change machine." } + } + action goto_kernel_dir { + requires { workspace } + script { "run 'cd $workspace/src/os/kernel'" } + } + sequence run_app + { + iteration { + action build_kernel { + requires { workspace } + script { "run make" } + } + action fix_kernel_failures { + script { "fix any failures" } + } + } + selection { + sequence run_gtk_app { + action goto_gtk_dir { + requires { workspace } + script { "run 'cd $workspace/src/ui/GUI'" } + } + iteration { + action build_gtk_app { + requires { workspace } + script { "run make" } + } + action fix_gtk_failures{ + script { "fix any failures" } + } + } + action run_gtk_app { + requires { workspace } + script { "run ./gtkpeos" } + } + } + sequence run_java_app { + action goto_gtk_dir { + requires { workspace } + script { "run 'cd $workspace/src/ui/java-gui'" } + } + action set_java_env { + script { " + run . /etc/profile + run setup jdk-1.4.0 + run export CLASSPATH=/home/jnoll/lib/xmlParserAPIs.jar:/home/jnoll/lib/junit/junit.jar:/home/jnoll/lib/xercesImpl.jar" + } + } + iteration { + action build_java_app { + requires { workspace } + script { "run make" } + } + action fix_java_failures{ + script { "fix any failures" } + } + } + action run_java_app { + requires { workspace } + script { "run ./runpeos" } + } + } + sequence run_web_app { + action goto_web_dir { + requires { workspace } + script { "run 'cd $workspace/src/ui/web2'" } + } + action set_html_dir { + requires { html_dir } + script { "run export HTML_DIR=$html_dir" } + } + iteration { + action build_web_app { + requires { workspace } + script { "run make install" } + } + action fix_web_failures{ + script { "fix any failures" } + } + } + action run_web_app { + requires { peos_url } + script { " + run a web browser + goto $peos_url" + } + } + } + } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/simple.pml b/panacea/test/fixtures/jnolls_pml/simple.pml index a794dbdd..a3c60cb2 100644 --- a/panacea/test/fixtures/jnolls_pml/simple.pml +++ b/panacea/test/fixtures/jnolls_pml/simple.pml @@ -1,15 +1,10 @@ -/* Simple process to smoke test kernel. */ process simple { - action a { - requires { a } - provides { a } - } - action b { - requires { a } - provides { a } - } - action c { - requires { a } - provides { a } - } -} + action a { + requires { foo } + provides { foo } + } + action b { + requires { foo } + provides { bar } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/test_commit.pml b/panacea/test/fixtures/jnolls_pml/test_commit.pml new file mode 100644 index 00000000..10adf3d7 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/test_commit.pml @@ -0,0 +1,36 @@ +/* + * Test changes committed to CVS. + * Initial version, without resource attributes. + * $ID$ + */ +process test_commit { + iteration { + action login_as_testuser { + requires { test_user } + script { "Login to test host as $test_user." } + } + action delete_old_workspace { + requires { working_dir } + script { "Run `cvs release $working_dir; rm -r $working_dir." } + } + action checkout_workspace { + script { "Run `cvs checkout $test_module'." } + provides { working_dir } + } + action run_tests { + script { "Run `make test' in `$working_dir/src' directory." } + requires { working_dir } + } + } + action update_status_report { + requires { working_dir } + script { "If all tests passed, you are finished; add this to your + list of accomplishments for today. If not, go back and fix any + failures uncovered." } + + } + action complete_commit { + requires { working_dir } + script { "You are finished. Get a cup of coffee!" } + } +} diff --git a/panacea/test/fixtures/jnolls_pml/web_test.pml b/panacea/test/fixtures/jnolls_pml/web_test.pml new file mode 100644 index 00000000..591d3689 --- /dev/null +++ b/panacea/test/fixtures/jnolls_pml/web_test.pml @@ -0,0 +1,505 @@ +process web_test { +action overview { +script { "

    In this exercise, you will create a set of tests for the Web + interface to the PML virtual machine, using JUnit and HTTPUnit. + The procedure is an abbreviated version of Tamres incremental + approach; the baseline is provided for you, you just need to do + the inventory.

    JUnit is a Java framework for creating and running unit and + functional tests. HTTPUnit is a set of java classes that one + uses in conjunction with JUnit for testing web sites.

    " } +} +sequence setup_environment { +action create_working_directory { +provides { working_directory } +script { "

    Create a working directory to contain the java files that + implement your tests. You must set permissions on the + path to your working directory so that the PEOS web interface + can traverse the path and read your test files.

    +      % cd
    +      % chmod a+X .
    +      % mkdir coen286
    +      % chmod a+X coen286
    +      % cd coen286
    +      % mkdir web_test
    +      % chmod a+Xr web_test
    +      

    Please pay particular attention to the last chmod; you + must make your working directory both executable (`+X') + and readable (`+r'), + so that the PML Web interface can read and display your files + when requested. The other directories need only be + executable.

    Note: this only grants read access to your working directory; + and only allows others to traverse, but not read, the + directories in the path leading to your working directory. This + enables the PEOS web interface to find your test files and other + resources in your working directory, but does not allow anyone to + actually list any of your directories except for your working + directory.

    " } +} +action create_test_file { +requires { working_directory } +provides { junit_test_file } +script { " + + HTTPUnit and JUnit are written in Java, and require tests to be + written in Java as well. Create a Java file to contain your test + code (called a Test Case in JUnit terminology). You must also + set permissions on the test file so that the PEOS web interface + can traverse the path and read your test files. +
    +      % cd ~/coen286/web_test
    +      % touch WebUITest.java
    +      % chmod a+r WebUITest.java
    +      
    + Note: the last step is necessary to grant access to the PEOS web + ui. + + Put the following skeleton in your java test file, then modify + the values of the login and password variables. + Set the login variable to the test id you received + via email; the password is the same for both IDs. +
    +      import java.lang.*;
    +      import com.meterware.httpunit.*;
    +
    +      import java.io.IOException;
    +      import java.net.MalformedURLException;
    +
    +      import org.xml.sax.*;
    +      import org.w3c.dom.*;
    +
    +      import junit.framework.*;
    +
    +
    +      /**
    +       * An example of testing web2 using httpunit and JUnit.
    +       **/
    +      public class WebUITest extends TestCase {
    +
    +	  String site = "http://linux.students.engr.scu.edu/~jnoll/PEOS/cgi-bin/";
    +	  String login = "(your test login)";
    +	  String passwd = "(your password)";
    +	  // Static, so initialization in Baseline persists.
    +	  static String proc_table; 
    +
    +
    +	  public static void main(String args[]) {
    +	      junit.textui.TestRunner.run( suite() );
    +	  }
    +
    +	  public static Test suite() {
    +	      return new TestSuite( WebUITest.class );
    +	  }
    +
    +	  public WebUITest( String name ) {
    +	      super( name );
    +	  }
    +
    +	  public void testBaseline () throws Exception {
    +	      assertTrue(0 == 0);
    +	  }
    +      }
    +      

    Be sure to include the main()and suite()methods in + addition to the constructor; JUnit uses these to run your tests.

    Note: The JUnit TestCase class corresponds to our notion + of test procedures. The methods of this class are close to our + concept of test case.

    To verify that everything is set up correctly, this skeleton + includes a simple test method to your class that will be run by + JUnit when the test is run:

    +      assertTrue(0 == 0);
    +      
    + will result in a `.' appearing in the output, indicating a test + was run and passed. + + " } +} +action create_makefile { +requires { working_directory } +provides { Makefile } +script { " + + Create a Makefile to automate the build and run cycle: +
    +      % touch Makefile
    +      % chmod a+r Makefile
    +      
    + Put the following macros and rules in the Makefile (please be + sure to include the 'test' rule, so I can easily run your tests by + typing 'make test'). +
    +      HTTPUNIT = /home/jnoll/lib/httpunit-1.5.4
    +      CLASSPATH = .:..:$(HTTPUNIT)/lib/httpunit.jar:$(HTTPUNIT)/jars/junit.jar:$(HTTPUNIT)/jars/nekohtml.jar:$(HTTPUNIT)/jars/Tidy.jar:$(HTTPUNIT)/jars/xmlParserAPIs.jar:$(HTTPUNIT)/jars/xercesImpl.jar:$(HTTPUNIT)/jars/js.jar
    +      
    +      JAVAC = javac
    +      JAVA = java
    +      
    +      test: WebUITest.class
    +      	       $(JAVA) -classpath $(CLASSPATH) WebUITest
    +      
    +      %.class: %.java
    +	       $(JAVAC) -classpath $(CLASSPATH) $<
    +      

    Note: be sure the lines containing JAVA and JAVAC above are + preceded by a tab character; make will be confused otherwise. +

    " } +} +action verify_setup { +requires { working_directory } +script { " + + Verify that your environment has been set up correctly by + compiling and running the test. To do this, you need to add + javac and java to your path, which is easily done + using the setup command: +
    +      % setup jdk
    +      % setup gcc
    +      
    + This will automatically add the appropriate environment + variables for the current version of JDK, as well as + gmake, to your environment. + +

    Now, test your implementation:

    +      % gmake test
    +      
    + You should see something like the following: +
    +[jnoll@linux101] ~/src/webtest :make
    +javac -classpath .:..:/home/jnoll/lib/httpunit-1.5.4/lib/httpunit.jar:/home/jnoll/lib/httpunit-1.5.4/jars/junit.jar:/home/jnoll/lib/httpunit-1.5.4/jars/nekohtml.jar:/home/jnoll/lib/httpunit-1.5.4/jars/Tidy.jar:/home/jnoll/lib/httpunit-1.5.4/jars/xmlParserAPIs.jar:/home/jnoll/lib/httpunit-1.5.4/jars/xercesImpl.jar:/home/jnoll/lib/httpunit-1.5.4/jars/js.jar WebUITest.java
    +java -classpath .:..:/home/jnoll/lib/httpunit-1.5.4/lib/httpunit.jar:/home/jnoll/lib/httpunit-1.5.4/jars/junit.jar:/home/jnoll/lib/httpunit-1.5.4/jars/nekohtml.jar:/home/jnoll/lib/httpunit-1.5.4/jars/Tidy.jar:/home/jnoll/lib/httpunit-1.5.4/jars/xmlParserAPIs.jar:/home/jnoll/lib/httpunit-1.5.4/jars/xercesImpl.jar:/home/jnoll/lib/httpunit-1.5.4/jars/js.jar WebUITest
    +.
    +Time: 0.003
    +
    +OK (1 test)
    +
    +[jnoll@linux101] ~/src/webtest :
    +      
    + Notice the lone '.' before the ``Time: 0.026''. This indicates + a test was run and passed. + + " } +} +} +action create_baseline { +requires { junit_test_file } +script { " + + Once you have a working test setup, augment it to interact with + the PEOS system. HTTPUnit provides several methods for + simulating interactions with a web site. You must first create a + ``conversation'' object to encapsulate the interactions with the + web site. First, set the login and passwd + variables to your test login id and passwd: +
    +       String login = "(your test login)";
    +       String passwd = "(your password)";
    +    
    + Then, replace the body of your testBaseline() with the + following: +
    +    public void testBaseline() throws Exception {
    +    	  WebConversation conversation = new WebConversation();
    +    	  conversation.setAuthorization(login, passwd);
    +    	  WebRequest request = 
    +	      new GetMethodWebRequest(site + "action_list.cgi");
    +    	  WebResponse response = conversation.getResponse(request);
    +    
    +    	  // Verify title and heading of response page.
    +    	  String title = response.getTitle();
    +    	  assertNotNull(title);
    +    	  assertEquals("Action List", title);
    +    	  assertTrue(-1 != response.getText().indexOf("Action List"));
    +    
    +    	  // Save the name of the process table; required for future
    +    	  // tests that have to send process table name in the url.
    +    	  WebForm form = response.getForms()[0];
    +    	  proc_table = form.getParameterValue("process_filename");
    +    	  assertNotNull(proc_table);
    +    }
    +    

    First, note the WebConversation object in the above method + definition. This object manages the sending and receiving of + requests and responses to and from the server. The conversation + also manages authentication, which is necessary for interacting + with the PEOS web site. Therefore, you must include your test + login id and password in the code.

    Next, observe how we sent a request to the web server. This is + done using a request object that is returned by the + GetMethodWebRequest() method, which takes a URL as + argument. We pass this object to the WebConversation to + send to the web server. We get the reply contents by asking the + WebConversation for the response object.

    The responseobject represents the reply from the web + server. This object can be queried for various constructs that + are part of the web page returned, including forms in the page, + and parameters in the forms. An important parameter you will + want to retrieve is the process_filename, which is the + name of the file that stores the process table for your + processes. (Each user gets a separate process table, with name + derived from the encrypted user name. This is to provide a + measure of privacy, so others can't easily obtain your process + state, or identify who belongs to a given process table.) + Declare an instance variable (say, proc_table) to hold + this name (you will need it later), and retrieve it from the + response:

    +        WebForm form = response.getForms()[0];
    +    	  proc_table = form.getParameterValue("process_filename");
    +    	  assertNotNull(proc_table);
    +    
    + Note the use of assertNotNull(). This is a JUnit + assertion to assert that its argument is not Null. + +

    Finally, we employ several HTTPUnit methods to examine the + response. In particular, getTitle() returns the pages + title (obviously), and getText() returns the text (not the + header) of the page, as html if it is an html page. For a + complete list of HTTPUnit operations, see the HTTPUnit + API documentation.

    " } +} +iteration create_inventory_tests { +action select_inventory_item { +script { "

    As mentioned previously, the test procedure is an abbreviated + form of Tamres incremental approach. In this phase, you create + a test for each ``inventory'' item in the product's input.

    The following cgi pages represent the inventory items to + test:

    • action_list.cgi
    • create_process.cgi
    • action_page.cgi
    • bind_resources.cgi
    • delete_proc_table.cgi

    Select an item to test, then create a test method named after + the item. For example, if you choose to test + action_list.cgi, name your method testActionList. + Note: it is necessary to create a name that begins with the + string ``test''; JUnit uses reflection to find the test methods + to call, by looking for this prefix string. It won't run + methods that don't begin with this string.

    Then, select one or more of the following tests to add to + your test method, depending on the page under test. +

    Note: if you're clever about the order in which you implement + your tests, you can leverage one test to set up the next. For + example, don't test delete_proc_table.cgi until the end; + then, you can use successful results of + create_process.cgi tests to set up the environment for + the others.

    " } +} +iteration { +selection { +action retrieve_page { +requires { junit_test_file } +script { " + + The baseline test is an example of how to test simple + retrieval of a web page: create a request bound to a + specific url, use the WebConversation to submit the + request, then examine the response object representing + the reply. + + " } +} +action create_process { +requires { junit_test_file } +script { " + + You will need to create a process instance to test some + operations, such as the action_page.cgi. This is + easy: just submit a request for create_process.cgi + with the name of the process: +
    +	  WebConversation conversation = new WebConversation();
    +	  conversation.setAuthorization(login, passwd);
    +	  WebRequest request = 
    +	  	  new GetMethodWebRequest(site 
    +	  				  + "create_process.cgi?"
    +	  				  + "model=test_action.pml"
    +	  				  + "&process_filename=" + proc_table);
    +	  
    +	  // Submit request and get reply.
    +	  WebResponse response = conversation.getResponse(request);
    +	  
    +	  // Verify title and heading of response page.
    +	  // The response to create_process is the Action List page.
    +	  String title = response.getTitle();
    +	  assertNotNull(title);
    +	  assertEquals("Action List", title);
    +	  assertTrue(-1 != response.getText().indexOf("<h1>Action List</h1>"));
    +	  
    " } +} +action reset_process_table { +requires { junit_test_file } +script { " + + Some tests will require a ``clean'' environment, in which no + processes have been created. Use + delete_proc_table.cgi to delete the process table and + thus reset the environment: +
    +	  WebConversation conversation = new WebConversation();
    +	  conversation.setAuthorization(login, passwd);
    +	  assertNotNull(proc_table);
    +	  WebRequest request = 
    +	      new GetMethodWebRequest(site 
    +				      + "delete_proc_table.cgi?"
    +				      + "process_filename=" + proc_table);
    +
    +	  // Submit request and get reply.
    +	  WebResponse response = conversation.getResponse(request);
    +
    +	  // Verify title and heading of response page.
    +	  // The response to delete_proc_table.cgi is a message confirming
    +	  // delete.  
    +	  String title = response.getTitle();
    +	  assertNotNull(title);
    +	  assertEquals("Delete Process Table", title);
    +	  assertTrue(-1 != response.getText().indexOf("<h1;>Delete Process Table</h1>"));
    +	  
    " } +} +action verify_links { +requires { junit_test_file } +script { " + + A page containing links can be verified by examining the text + between the anchor tags, or the link url. + +

    Use the WebResponse object's getLinkWith() + method to find a link with specific text within anchor tags. + For example, to find the ``overview'' link in a page, do

    +	  // Get 'overview' link from form.
    +	  WebLink link = response.getLinkWith("overview");
    +	  assertNotNull(link);
    +	  

    Then, you can verify the the link's url with + getURLString():

    +	  assertEquals("action_page.cgi?pid=0&act_name=overview" + 
    +		       "&process_filename=" + proc_table,
    +		       link.getURLString());
    +	  

    Another way to look at links is to retrieve all of the + links in a page, then look at their attributes one at a + time:

    +	  // Verify links.  This process only has two links: ``test_script''
    +	  // and ``Create Process''.
    +	  WebLink links[] = response.getLinks();
    +	  int i = 0;
    +	  assertEquals("test_script", links[i].asText());
    +	  assertEquals("action_page.cgi?pid=0&act_name=test_script" + 
    +		       "&process_filename=" + proc_table,
    +		       links[i].getURLString());
    +	  i++;
    +	  // Next link is ``Create Process'' link at bottom of page.
    +	  assertEquals("Create Process", links[i].asText());
    +	  assertEquals("process_listing.cgi?" + 
    +		       "process_filename=" + proc_table,
    +		       links[i].getURLString());
    +	  
    " } +} +action follow_link { +requires { junit_test_file } +script { "

    Links aren't much use if they can't be followed. HTTPUnit + provides a facility for following links, simulating a mouse + click on the anchor text. This is achieved through the + WebLink object's click()method:

    +	  // See if there's anything on the other end.
    +	  WebResponse linkEnd = links[i].click();
    +	  assertNotNull(linkEnd.getTitle());
    +	  assertEquals("test_script", linkEnd.getTitle());
    +	  assertTrue(-1 != linkEnd.getText().indexOf("<h1>test_script</h1>"));
    +	  
    " } +} +action verify_parameters { +requires { junit_test_file } +script { "

    Most of the web pages we will test are actually forms. + HTTPUnit provides many facilities for examining and + manipulating forms.

    For example, you might want to examine a form's + parameters; we used this in the baseline test to obtain the + process table name, which is a ``hidden'' parameter in most + of our forms:

    +	   
    +	  

    We retrieved this parameter using the WebForm + object's getParameterValue() method, which takes the + parameter name as argument and returns the parameter's value + attribute:

    +	   WebForm form = response.getForms()[0];
    +	   proc_table = form.getParameterValue("process_filename");
    +	  
    " } +} +action submit_form { +requires { junit_test_file } +script { " + + You can also submit a form, once you have obtained it using + the WebResponse objects getForms() method. + +

    WebForm provides a setParameter() method to set + the values of a forms parameters, and a submit() + method that simulates form submission.

    +
    +	  WebConversation conversation = new WebConversation();
    +	  conversation.setAuthorization(login, passwd);
    +	  WebRequest  request = 
    +	      new GetMethodWebRequest(site + "handle_run.cgi?"
    +				      + "resource_type=requires" 
    +				      + "&process_filename=" + proc_table
    +				      + "&pid=0"
    +				      + "&act_name=test_script");
    +
    +	  WebResponse response = conversation.getResponse( request );
    +	  WebForm bindingForm = response.getForms()[0];
    +	  bindingForm.setParameter("test_resource", "/home/jnoll/lib/httpunit");
    +	  bindingForm.submit();
    +
    +	  // The response is now the conversation's current page.
    +	  response = conversation.getCurrentPage();
    +	  assertEquals("test_script", response.getTitle());
    +
    +	  
    " } +} +action examine_table { +requires { junit_test_file } +} +} +} +action verify_test_method { +requires { junit_test_file } +script { " + + Verify that your test compiles and runs before proceeding to the + next test: +
    +      % make test
    +      
    " } +} +} +action create_readme { +requires { working_directory } +provides { readme } +script { " + Create a README file in your working directory, that identifies + you and answers a few questions about your experience. Use the + following template as a starting point: +
    +      Ed Student
    +      123456
    +
    +      Questions:
    +      1. Did you use the ``Check to Indicate Done'' feature of the
    +      Action List?
    +
    +      2. Did you perform any tasks out of order?
    +
    +      3. Did you ever click the ``Run'' and/or ``Done'' buttons of
    +      the Action Page?
    +
    +      4a. Please assess the PEOS interface as more or less helpful than the
    +      static web pages we have used to explain earlier assignments
    +      (put an `X' in the box next to your assessment):
    +      [ ] definitely more helpful 
    +      [ ] somewhat more helpful 
    +      [ ] somewhat less helpful 
    +      [ ] definitely less helpful
    +
    +      4b. Please write a brief explanation for your answer to part
    +      (a).
    +
    +      5. Please write any other impressions about your experience
    +      here.
    +
    +    
    " } +} +action submit_results { +requires { junit_test_file && readme } +script { " + + Once you are satisfied with your test suite (and only after you + have verified that ALL tests compile and run), submit a tar file + via email, according to the submit + procedure. + + " } +} +} From 29c98526ab5567f9112f01a323e1c5a4a17f6c0a Mon Sep 17 00:00:00 2001 From: c-brenn Date: Tue, 14 Mar 2017 12:58:47 +0000 Subject: [PATCH 05/37] tidy up parser. * removes unneccesary erlang blocks on the RHS of productions (`: [].` -> `.`). * productions with no erlang code are now on a single-line. * removes `action_block` production. --- panacea/src/pml_parser.yrl | 118 +++++++++++++------------------------ 1 file changed, 40 insertions(+), 78 deletions(-) diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index d8d4f539..3b2fd625 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -5,7 +5,6 @@ primitive_block primitive_list primitive optional_name -action_block action_attributes action_attribute optional_type @@ -96,22 +95,14 @@ primitive -> % action names are required and have a different block % to other primitives primitive -> - 'action' ident optional_type action_block : '$4'. + 'action' ident optional_type '{' action_attributes '}' : '$5'. -optional_name -> - '$empty' : []. -optional_name -> - 'ident' : []. - -optional_type -> - '$empty' : []. -optional_type -> - 'manual' : []. -optional_type -> - 'executable' : []. +optional_name -> '$empty'. +optional_name -> 'ident'. -action_block -> - '{' action_attributes '}' : '$2'. +optional_type -> '$empty'. +optional_type -> 'manual'. +optional_type -> 'executable'. action_attributes -> '$empty' : []. @@ -141,76 +132,47 @@ requires_expr -> requires_expr -> expression : []. -expression -> - expr logical_combination : []. +expression -> expr logical_combination. -logical_combination -> - '&&' expr logical_combination : []. -logical_combination -> - '||' expr logical_combination : []. -logical_combination -> - '$empty' : []. +logical_combination -> '&&' expr logical_combination. +logical_combination -> '||' expr logical_combination. +logical_combination -> '$empty'. -expr -> - value operation : []. +expr -> value operation. -operation -> - operator value : []. -operation -> - '$empty' : []. +operation -> operator value. +operation -> '$empty'. + +value -> '!' expression. +value -> '(' expression ')'. +value -> 'string'. +value -> 'number'. +value -> variable. -value -> - '!' expression : []. -value -> - '(' expression ')' :[]. -value -> - 'string' : []. -value -> - 'number' : []. -value -> - variable : []. - -variable -> - identifier accessor : []. -variable -> - prefix prefix_list accessor : []. - -identifier -> - 'ident' : []. +variable -> identifier accessor. +variable -> prefix prefix_list accessor. + +identifier -> 'ident'. % some of jnoll's sample pml has these keywords % on the RHS of expressions! -identifier -> - 'manual' : []. -identifier -> - 'executable' : []. - -prefix -> - '(' ident ')' : []. - -prefix_list -> - ident : []. -prefix_list -> - prefix prefix_list : []. -prefix_list -> - '$empty' : []. +identifier -> 'manual'. +identifier -> 'executable'. -accessor -> - '$empty' : []. -accessor -> - '.' 'ident' : []. - -operator -> - '==' : []. -operator -> - '!=' : []. -operator -> - '<' : []. -operator -> - '>' : []. -operator -> - '<=' : []. -operator -> - '>=' : []. +prefix -> '(' ident ')'. + +prefix_list -> ident. +prefix_list -> prefix prefix_list. +prefix_list -> '$empty'. + +accessor -> '$empty'. +accessor -> '.' 'ident'. + +operator -> '=='. +operator -> '!='. +operator -> '<'. +operator -> '>'. +operator -> '<='. +operator -> '>='. Erlang code. From 8c266b3f2fa88638e39dcd5c03132cc0e302e07e Mon Sep 17 00:00:00 2001 From: c-brenn Date: Tue, 14 Mar 2017 13:27:03 +0000 Subject: [PATCH 06/37] use his NUMBER regex --- panacea/src/pml_lexer.xrl | 4 ++-- panacea/test/panacea/pml/parser_test.exs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/panacea/src/pml_lexer.xrl b/panacea/src/pml_lexer.xrl index ab7db8a7..ae87855b 100644 --- a/panacea/src/pml_lexer.xrl +++ b/panacea/src/pml_lexer.xrl @@ -2,7 +2,7 @@ Definitions. IDENT = [a-zA-Z0-9_]+ WS = [\s\t\n\r]+ -NUMBER = [0-9]+(\.[0-9]+)? +NUMBER = ([0-9]+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? STRING = "[^"]*" COMMENT = \/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\/ @@ -50,7 +50,7 @@ executable : {token, {executable, TokenLine}}. {COMMENT} : skip_token. {WS} : skip_token. {STRING} : {token, {string, TokenLine, TokenChars}}. -{NUMBER} : {tokem, {number, TokenLine}}. +{NUMBER} : {token, {number, TokenLine}}. {IDENT} : {token, {ident, TokenLine}}. Erlang code. diff --git a/panacea/test/panacea/pml/parser_test.exs b/panacea/test/panacea/pml/parser_test.exs index cd8e2dd3..64d300be 100644 --- a/panacea/test/panacea/pml/parser_test.exs +++ b/panacea/test/panacea/pml/parser_test.exs @@ -13,7 +13,7 @@ defmodule Panacea.Pml.ParserTest do action baz { tool { "drill" } script { "drill a hole" } - agent { "driller" } + agent { 44.1234532E-123342 } requires { "wall" } provides { "a hole" } } From 491ea6abeb631e8271015a61b111e0e11ba04978 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 15 Mar 2017 11:10:33 +0000 Subject: [PATCH 07/37] Add initial DDI enrichment module --- asclepius/asclepius/enrich.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 asclepius/asclepius/enrich.py diff --git a/asclepius/asclepius/enrich.py b/asclepius/asclepius/enrich.py new file mode 100644 index 00000000..14a81987 --- /dev/null +++ b/asclepius/asclepius/enrich.py @@ -0,0 +1,17 @@ +def enrich(ddis): + def _agonist(uri): + return bool(hash(uri) & 2) + + def _spacing(uri): + return (hash(uri) & 0xf) + 1 + + def _enrich(ddi): + result = { + 'agonistic': _agonist(ddi['uri']), + 'spacing': _spacing(ddi['uri']), + } + + result.update(ddi) + return result + + return [_enrich(ddi) for ddi in ddis] From 7b7fb3218dafd0dc8daea4a92d41df5603089f25 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 15 Mar 2017 11:34:07 +0000 Subject: [PATCH 08/37] make the DDIs more rich --- asclepius/asclepius/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index ae6f88ca..a38a7589 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -5,7 +5,7 @@ from app import app import dinto - +from enrich import enrich class InvalidUsage(Exception): status_code = 400 @@ -61,7 +61,7 @@ def ddis(): except ValueError as e: raise InvalidUsage(str(e)) - return jsonify(dinto_res) + return jsonify(enrich(dinto_res)) @app.route('/uris', methods=["POST"]) From a1d6daeb2e81243102ea47f5aa32fadfc2f91c1d Mon Sep 17 00:00:00 2001 From: Eoin Houlihan Date: Thu, 16 Mar 2017 22:03:38 +0000 Subject: [PATCH 09/37] WIP PML AST LOL --- panacea/src/pml_lexer.xrl | 2 +- panacea/src/pml_parser.yrl | 142 +++++++++++++++++++++---------------- 2 files changed, 81 insertions(+), 63 deletions(-) diff --git a/panacea/src/pml_lexer.xrl b/panacea/src/pml_lexer.xrl index ae87855b..933bca7a 100644 --- a/panacea/src/pml_lexer.xrl +++ b/panacea/src/pml_lexer.xrl @@ -51,6 +51,6 @@ executable : {token, {executable, TokenLine}}. {WS} : skip_token. {STRING} : {token, {string, TokenLine, TokenChars}}. {NUMBER} : {token, {number, TokenLine}}. -{IDENT} : {token, {ident, TokenLine}}. +{IDENT} : {token, {ident, TokenLine, TokenChars}}. Erlang code. diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index 3b2fd625..50f376ef 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -25,22 +25,22 @@ Terminals % keywords -'process' -'task' -'action' -'branch' -'selection' -'iteration' -'sequence' -'provides' -'requires' -'agent' -'script' -'tool' -'input' -'output' -'manual' -'executable' +process +task +action +branch +selection +iteration +sequence +provides +requires +agent +script +tool +input +output +manual +executable % symbols '{' @@ -59,10 +59,10 @@ Terminals '!' % primitives -'string' -'ident' -'drug' -'number'. +string +ident +drug +number. Rootsymbol pml. @@ -72,7 +72,7 @@ Rootsymbol pml. Nonassoc 200 '==' '!=' '||' '&&' '<' '>' '<=' '>=' '!' '.' ')'. pml -> - 'process' 'ident' primitive_block : '$3'. + process ident primitive_block : {process, [{ident, extract_ident('$2')}, line_number('$1')], '$3'}. primitive_block -> '{' primitive_list '}' : '$2'. @@ -80,54 +80,54 @@ primitive_block -> primitive_list -> '$empty' : []. primitive_list -> - primitive primitive_list : '$1' ++ '$2'. + primitive primitive_list : ['$1'|'$2']. primitive -> - 'branch' optional_name primitive_block : '$3'. + branch optional_name primitive_block : primitive('$1', '$2', '$3'). primitive -> - 'selection' optional_name primitive_block : '$3'. + selection optional_name primitive_block : primitive('$1', '$2', '$3'). primitive -> - 'iteration' optional_name primitive_block : '$3'. + iteration optional_name primitive_block : primitive('$1', '$2', '$3'). primitive -> - 'sequence' optional_name primitive_block : '$3'. + sequence optional_name primitive_block : primitive('$1', '$2', '$3'). primitive -> - 'task' optional_name primitive_block : '$3'. + task optional_name primitive_block : primitive('$1', '$2', '$3'). % action names are required and have a different block % to other primitives primitive -> - 'action' ident optional_type '{' action_attributes '}' : '$5'. + action ident optional_type '{' action_attributes '}' : action(extract_ident('$2'), line_number('$1'), '$3', '$5'). -optional_name -> '$empty'. -optional_name -> 'ident'. +optional_name -> '$empty' : nil. +optional_name -> ident : extract_ident('$1'). -optional_type -> '$empty'. -optional_type -> 'manual'. -optional_type -> 'executable'. +optional_type -> '$empty' : nil. +optional_type -> manual : manual. +optional_type -> executable : executable. action_attributes -> '$empty' : []. action_attributes -> - action_attribute action_attributes : '$1' ++ '$2'. + action_attribute action_attributes : ['$1'|'$2']. action_attribute -> - 'provides' '{' expression '}' : []. + provides '{' expression '}' : action_attribute('$1', '$3'). % `requires` uses a different expression production as % drugs are only allowed in `requires `blocks action_attribute -> - 'requires' '{' requires_expr '}' : '$3'. + requires '{' requires_expr '}' : action_attribute('$1', '$3'). action_attribute -> - 'agent' '{' expression '}' : []. + agent '{' expression '}' : action_attribute('$1', '$3'). action_attribute -> - 'script' '{' 'string' '}' : []. + script '{' string '}' : action_attribute('$1', '$3'). action_attribute -> - 'tool' '{' 'string' '}' : []. + tool '{' string '}' : action_attribute('$1', '$3'). action_attribute -> - 'input' '{' 'string' '}' : []. + input '{' string '}' : action_attribute('$1', '$3'). action_attribute -> - 'output' '{' 'string' '}' : []. + output '{' string '}' : action_attribute('$1', '$3'). requires_expr -> - 'drug' '{' 'string' '}' : extract_drug('$3'). + drug '{' string '}' : extract_string('$3'). requires_expr -> expression : []. @@ -143,20 +143,20 @@ expr -> value operation. operation -> operator value. operation -> '$empty'. -value -> '!' expression. -value -> '(' expression ')'. -value -> 'string'. -value -> 'number'. -value -> variable. +value -> '!' expression : {negate, '$2'}. +value -> '(' expression ')': '$2'. +value -> string : extract_string('$1'). +value -> number : list_to_float('$1'). +value -> variable : '$1'. variable -> identifier accessor. variable -> prefix prefix_list accessor. -identifier -> 'ident'. +identifier -> ident. % some of jnoll's sample pml has these keywords % on the RHS of expressions! -identifier -> 'manual'. -identifier -> 'executable'. +identifier -> manual. +identifier -> executable. prefix -> '(' ident ')'. @@ -165,21 +165,39 @@ prefix_list -> prefix prefix_list. prefix_list -> '$empty'. accessor -> '$empty'. -accessor -> '.' 'ident'. +accessor -> '.' ident : {accessor, extract_ident('$2')}. -operator -> '=='. -operator -> '!='. -operator -> '<'. -operator -> '>'. -operator -> '<='. -operator -> '>='. +operator -> '==' : equal. +operator -> '!=' : not_equal. +operator -> '<' : less_than. +operator -> '>' : greater_than. +operator -> '<=' : less_than_equal. +operator -> '>=' : greater_than_equal. Erlang code. -extract_drug({_,Line,DrugStr}) -> - Drug = strip_quotes(DrugStr), - [{Drug, Line}]. +extract_string({_, Line, Str}) -> + Stripped = strip_quotes(Str), + [{Stripped, Line}]. -strip_quotes(Drug) -> - CharList = string:strip(Drug,both,$"), +strip_quotes(Str) -> + CharList = string:strip(Str, both, $"), list_to_binary(CharList). + +extract_ident({ident, _, Ident}) -> Ident. + +line_number({_, Line}) -> {line, Line}; +line_number({_, Line, _}) -> {line, Line}. + +primitive({PrimType, Line}, nil, Rest) -> + {PrimType, [{line, Line}], Rest}; +primitive({PrimType, Line}, Ident, Rest) -> + {PrimType, [{ident, Ident}, {line, Line}], Rest}. + +action(Ident, Line, nil, Rest) -> + {action, [{ident, Ident}, Line], Rest}; +action(Ident, Line, Type, Rest) -> + {action, [{ident, Ident}, Line, {type, Type}], Rest}. + +action_attribute({AttrType, Line}, Rest) -> + {AttrType, [{line, Line}], Rest}. From b5e770e13612508f25d4b96cc01da9b8aa50ce4b Mon Sep 17 00:00:00 2001 From: c-brenn Date: Thu, 16 Mar 2017 23:11:07 +0000 Subject: [PATCH 10/37] generalise AST helper functions [WIP][skip ci] also adds a testing function to Parser bc typing in iex is hard. --- panacea/lib/panacea/pml/parser.ex | 9 +++++ panacea/src/pml_parser.yrl | 62 +++++++++++++------------------ 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/panacea/lib/panacea/pml/parser.ex b/panacea/lib/panacea/pml/parser.ex index 7cd0ee69..a1210c79 100644 --- a/panacea/lib/panacea/pml/parser.ex +++ b/panacea/lib/panacea/pml/parser.ex @@ -36,4 +36,13 @@ defmodule Panacea.Pml.Parser do {:ok, result} end + + def test do + {:ok, f} = File.read("test/fixtures/ddis.pml") + f + |> to_charlist() + |> :pml_lexer.string() + |> elem(1) + |> :pml_parser.parse() + end end diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index 50f376ef..0bb2bf79 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -72,7 +72,7 @@ Rootsymbol pml. Nonassoc 200 '==' '!=' '||' '&&' '<' '>' '<=' '>=' '!' '.' ')'. pml -> - process ident primitive_block : {process, [{ident, extract_ident('$2')}, line_number('$1')], '$3'}. + process ident primitive_block : construct('$1', [{name, ident('$2')}], '$3'). primitive_block -> '{' primitive_list '}' : '$2'. @@ -83,22 +83,22 @@ primitive_list -> primitive primitive_list : ['$1'|'$2']. primitive -> - branch optional_name primitive_block : primitive('$1', '$2', '$3'). + branch optional_name primitive_block : construct('$1', [{name, '$2'}], '$3'). primitive -> - selection optional_name primitive_block : primitive('$1', '$2', '$3'). + selection optional_name primitive_block : construct('$1', [{name, '$2'}], '$3'). primitive -> - iteration optional_name primitive_block : primitive('$1', '$2', '$3'). + iteration optional_name primitive_block : construct('$1', [{name, '$2'}], '$3'). primitive -> - sequence optional_name primitive_block : primitive('$1', '$2', '$3'). + sequence optional_name primitive_block : construct('$1', [{name, '$2'}], '$3'). primitive -> - task optional_name primitive_block : primitive('$1', '$2', '$3'). + task optional_name primitive_block : construct('$1', [{name, '$2'}], '$3'). % action names are required and have a different block % to other primitives primitive -> - action ident optional_type '{' action_attributes '}' : action(extract_ident('$2'), line_number('$1'), '$3', '$5'). + action ident optional_type '{' action_attributes '}' : construct('$1', [{name, ident('$2')}, {type, '$3'}], '$5'). optional_name -> '$empty' : nil. -optional_name -> ident : extract_ident('$1'). +optional_name -> ident : ident('$1'). optional_type -> '$empty' : nil. optional_type -> manual : manual. @@ -110,27 +110,27 @@ action_attributes -> action_attribute action_attributes : ['$1'|'$2']. action_attribute -> - provides '{' expression '}' : action_attribute('$1', '$3'). + provides '{' expression '}' : construct('$1', [], '$3'). % `requires` uses a different expression production as % drugs are only allowed in `requires `blocks action_attribute -> - requires '{' requires_expr '}' : action_attribute('$1', '$3'). + requires '{' requires_expr '}' : construct('$1', [], '$3'). action_attribute -> - agent '{' expression '}' : action_attribute('$1', '$3'). + agent '{' expression '}' : construct('$1', [], '$3'). action_attribute -> - script '{' string '}' : action_attribute('$1', '$3'). + script '{' string '}' : construct('$1', [], extract_string('$3')). action_attribute -> - tool '{' string '}' : action_attribute('$1', '$3'). + tool '{' string '}' : construct('$1', [], extract_string('$3')). action_attribute -> - input '{' string '}' : action_attribute('$1', '$3'). + input '{' string '}' : construct('$1', [], extract_string('$3')). action_attribute -> - output '{' string '}' : action_attribute('$1', '$3'). + output '{' string '}' : construct('$1', [], extract_string('$3')). requires_expr -> - drug '{' string '}' : extract_string('$3'). + drug '{' string '}' : construct('$1', [], extract_string('$3')). requires_expr -> - expression : []. + expression : '$1'. expression -> expr logical_combination. @@ -165,7 +165,7 @@ prefix_list -> prefix prefix_list. prefix_list -> '$empty'. accessor -> '$empty'. -accessor -> '.' ident : {accessor, extract_ident('$2')}. +accessor -> '.' ident : {accessor, ident('$2')}. operator -> '==' : equal. operator -> '!=' : not_equal. @@ -176,28 +176,16 @@ operator -> '>=' : greater_than_equal. Erlang code. -extract_string({_, Line, Str}) -> - Stripped = strip_quotes(Str), - [{Stripped, Line}]. +extract_string({_, _, Str}) -> + strip_quotes(Str). strip_quotes(Str) -> CharList = string:strip(Str, both, $"), list_to_binary(CharList). -extract_ident({ident, _, Ident}) -> Ident. +construct({Type, Line}, Attributes, Value) -> + Attrs = lists:filter(fun({_,X}) -> X /= nil end, Attributes), + {Type, [{line, Line}|Attrs], Value}. -line_number({_, Line}) -> {line, Line}; -line_number({_, Line, _}) -> {line, Line}. - -primitive({PrimType, Line}, nil, Rest) -> - {PrimType, [{line, Line}], Rest}; -primitive({PrimType, Line}, Ident, Rest) -> - {PrimType, [{ident, Ident}, {line, Line}], Rest}. - -action(Ident, Line, nil, Rest) -> - {action, [{ident, Ident}, Line], Rest}; -action(Ident, Line, Type, Rest) -> - {action, [{ident, Ident}, Line, {type, Type}], Rest}. - -action_attribute({AttrType, Line}, Rest) -> - {AttrType, [{line, Line}], Rest}. +ident({ident, _, Ident}) -> + Ident. From 842f263036fc8aac88484034964b7d63367a6351 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 09:52:00 +0000 Subject: [PATCH 11/37] extend AST [WIP][skip ci] * expressions are now represented in the AST `{func, attrs, [arg1, arg2]}`. * removes the ident special case for `manual` and `executable` - these are now keywords. --- panacea/src/pml_lexer.xrl | 2 +- panacea/src/pml_parser.yrl | 68 +++++++++---------- panacea/test/fixtures/ddis.pml | 2 +- .../fixtures/jnolls_pml/edit-compile-test.pml | 2 +- panacea/test/fixtures/jnolls_pml/sample.pml | 2 +- panacea/test/fixtures/jnolls_pml/sample2.pml | 2 +- 6 files changed, 38 insertions(+), 40 deletions(-) diff --git a/panacea/src/pml_lexer.xrl b/panacea/src/pml_lexer.xrl index 933bca7a..ce0f564d 100644 --- a/panacea/src/pml_lexer.xrl +++ b/panacea/src/pml_lexer.xrl @@ -50,7 +50,7 @@ executable : {token, {executable, TokenLine}}. {COMMENT} : skip_token. {WS} : skip_token. {STRING} : {token, {string, TokenLine, TokenChars}}. -{NUMBER} : {token, {number, TokenLine}}. +{NUMBER} : {token, {number, TokenLine, TokenChars}}. {IDENT} : {token, {ident, TokenLine, TokenChars}}. Erlang code. diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index 0bb2bf79..de246085 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -17,7 +17,6 @@ operation value variable accessor -identifier prefix_list prefix. @@ -110,7 +109,7 @@ action_attributes -> action_attribute action_attributes : ['$1'|'$2']. action_attribute -> - provides '{' expression '}' : construct('$1', [], '$3'). + provides '{' expression '}' : construct('$1', [], '$3'). % `requires` uses a different expression production as % drugs are only allowed in `requires `blocks action_attribute -> @@ -132,51 +131,45 @@ requires_expr -> requires_expr -> expression : '$1'. -expression -> expr logical_combination. +expression -> expr logical_combination : function_application('$1', '$2'). -logical_combination -> '&&' expr logical_combination. -logical_combination -> '||' expr logical_combination. -logical_combination -> '$empty'. +logical_combination -> '&&' expr logical_combination : construct('$1', [], function_application('$2', '$3')). +logical_combination -> '||' expr logical_combination : construct('$1', [], function_application('$2', '$3')). +logical_combination -> '$empty' : nil. -expr -> value operation. +expr -> value operation : function_application('$1', '$2'). -operation -> operator value. -operation -> '$empty'. +operation -> operator value : construct('$1', [], '$2'). +operation -> '$empty' : nil. -value -> '!' expression : {negate, '$2'}. -value -> '(' expression ')': '$2'. -value -> string : extract_string('$1'). -value -> number : list_to_float('$1'). -value -> variable : '$1'. +value -> '!' expression : {negate, [], '$2'}. +value -> '(' expression ')' : {parenthesised, [], '$2'}. +value -> string : extract_string('$1'). +value -> number : extract_string('$1'). +value -> variable : '$1'. -variable -> identifier accessor. -variable -> prefix prefix_list accessor. +variable -> ident accessor : function_application('$1', '$2'). +variable -> prefix prefix_list accessor : function_application(['$1'|'$2'], '$3'). -identifier -> ident. -% some of jnoll's sample pml has these keywords -% on the RHS of expressions! -identifier -> manual. -identifier -> executable. +prefix -> '(' ident ')' : {prefix, extract_string('$2')}. -prefix -> '(' ident ')'. +prefix_list -> ident : [{ident, extract_string('$1')}]. +prefix_list -> prefix prefix_list : ['$1'|'$2']. +prefix_list -> '$empty' : []. -prefix_list -> ident. -prefix_list -> prefix prefix_list. -prefix_list -> '$empty'. +accessor -> '$empty' : nil. +accessor -> '.' ident : construct('$1', [], extract_string('$2')). -accessor -> '$empty'. -accessor -> '.' ident : {accessor, ident('$2')}. - -operator -> '==' : equal. -operator -> '!=' : not_equal. -operator -> '<' : less_than. -operator -> '>' : greater_than. -operator -> '<=' : less_than_equal. -operator -> '>=' : greater_than_equal. +operator -> '==' : '$1'. +operator -> '!=' : '$1'. +operator -> '<' : '$1'. +operator -> '>' : '$1'. +operator -> '<=' : '$1'. +operator -> '>=' : '$1'. Erlang code. -extract_string({_, _, Str}) -> +extract_string({_,_,Str}) -> strip_quotes(Str). strip_quotes(Str) -> @@ -189,3 +182,8 @@ construct({Type, Line}, Attributes, Value) -> ident({ident, _, Ident}) -> Ident. + +function_application(Arg1, nil) -> + Arg1; +function_application(Arg1, {Func, Attrs, Arg2}) -> + {Func, Attrs, [Arg1, Arg2]}. diff --git a/panacea/test/fixtures/ddis.pml b/panacea/test/fixtures/ddis.pml index 4e833370..b9433751 100644 --- a/panacea/test/fixtures/ddis.pml +++ b/panacea/test/fixtures/ddis.pml @@ -12,7 +12,7 @@ process foo { action baz2 { tool { "pills" } script { "eat the pills" } - agent { "patient" } + agent { (intangible)(inscrutable) pml.wtf && ("foo" || 1 != 2) } requires { drug { "trandolapril" } } diff --git a/panacea/test/fixtures/jnolls_pml/edit-compile-test.pml b/panacea/test/fixtures/jnolls_pml/edit-compile-test.pml index 3fb2d96e..e167b16a 100644 --- a/panacea/test/fixtures/jnolls_pml/edit-compile-test.pml +++ b/panacea/test/fixtures/jnolls_pml/edit-compile-test.pml @@ -6,7 +6,7 @@ process develop { } action compile manual { requires { code.status == modified } - provides { progA.type == executable } + provides { progA.type == "executable" } } action test manual { requires { progA } diff --git a/panacea/test/fixtures/jnolls_pml/sample.pml b/panacea/test/fixtures/jnolls_pml/sample.pml index 9ff90cd9..ad3cc6f7 100644 --- a/panacea/test/fixtures/jnolls_pml/sample.pml +++ b/panacea/test/fixtures/jnolls_pml/sample.pml @@ -6,7 +6,7 @@ process develop { } action compile manual { requires { code.status == modified } - provides { progA.type == executable } + provides { progA.type == "executable "} } action test manual { requires { progA } diff --git a/panacea/test/fixtures/jnolls_pml/sample2.pml b/panacea/test/fixtures/jnolls_pml/sample2.pml index 012bc812..184af9f9 100644 --- a/panacea/test/fixtures/jnolls_pml/sample2.pml +++ b/panacea/test/fixtures/jnolls_pml/sample2.pml @@ -7,7 +7,7 @@ process example { } action compile { requires { code.status == modified } - provides { progA.type == executable } + provides { progA.type == "executable "} } } sequence test { From 89bf3b66bbbbade331bbd809fe99d70f495dec67 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 10:28:37 +0000 Subject: [PATCH 12/37] adds analysis module * changes parser.yrl so all tuples are 3-tuples * removes drug identification from parser.ex * adds analysis module (very basic atm) - can identify drugs * PmlController uses the analysis module after parse * fixes up tests --- panacea/lib/panacea/pml/analysis.ex | 27 ++++++++++++ panacea/lib/panacea/pml/parser.ex | 19 ++++----- panacea/src/pml_parser.yrl | 4 +- .../test/controllers/pml_controller_test.exs | 4 +- panacea/test/panacea/pml/analysis_test.exs | 41 +++++++++++++++++++ panacea/test/panacea/pml/parser_test.exs | 38 +---------------- panacea/web/controllers/pml_controller.ex | 6 ++- 7 files changed, 86 insertions(+), 53 deletions(-) create mode 100644 panacea/lib/panacea/pml/analysis.ex create mode 100644 panacea/test/panacea/pml/analysis_test.exs diff --git a/panacea/lib/panacea/pml/analysis.ex b/panacea/lib/panacea/pml/analysis.ex new file mode 100644 index 00000000..ca0a5f60 --- /dev/null +++ b/panacea/lib/panacea/pml/analysis.ex @@ -0,0 +1,27 @@ +defmodule Panacea.Pml.Analysis do + alias __MODULE__ + + defstruct drugs: [] + + def run(ast) do + {:ok, analyse(%Analysis{}, ast)} + end + + defp analyse(result, {:drug, [line: line], label}) do + %{result| drugs: [ %{label: label, line: line}| result.drugs ]} + end + defp analyse(result, {_, _,children}) when is_list(children) do + Enum.reduce(children, result, fn(child, acc) -> + analyse(acc, child) + end) + end + defp analyse(result, {_, _,child}) do + analyse(result, child) + end + defp analyse(result, _), do: result + + def test do + {:ok, ast} = Panacea.Pml.Parser.test + run(ast) + end +end diff --git a/panacea/lib/panacea/pml/parser.ex b/panacea/lib/panacea/pml/parser.ex index a1210c79..c6da9062 100644 --- a/panacea/lib/panacea/pml/parser.ex +++ b/panacea/lib/panacea/pml/parser.ex @@ -8,33 +8,30 @@ defmodule Panacea.Pml.Parser do str |> to_charlist() |> tokens() - |> do_parse() - |> to_result() + |> generate_ast() + |> log_result() end defp tokens(str) do :pml_lexer.string(str) end - defp do_parse({:ok, tokens, _}) do + defp generate_ast({:ok, tokens, _}) do :pml_parser.parse(tokens) end - defp do_parse({:error, reason, _}) do + defp generate_ast({:error, reason, _}) do {:error, reason} end - defp to_result({:error, reason}) do + defp log_result({:error, reason}) do formatted = Error.format(reason) Logger.error("PML Parsing error: #{formatted}") {:error, {:syntax_error, formatted}} end - defp to_result({:ok, drugs}) do - result = for {label, line} <- drugs, do: %{label: label, line: line} - - Logger.info(["PML Parsing success: ", inspect(result)]) - - {:ok, result} + defp log_result({:ok, ast}) do + Logger.info("PML Analysis success") + {:ok, ast} end def test do diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index de246085..2882dfff 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -151,9 +151,9 @@ value -> variable : '$1'. variable -> ident accessor : function_application('$1', '$2'). variable -> prefix prefix_list accessor : function_application(['$1'|'$2'], '$3'). -prefix -> '(' ident ')' : {prefix, extract_string('$2')}. +prefix -> '(' ident ')' : {prefix, [], extract_string('$2')}. -prefix_list -> ident : [{ident, extract_string('$1')}]. +prefix_list -> ident : [{ident, [], extract_string('$1')}]. prefix_list -> prefix prefix_list : ['$1'|'$2']. prefix_list -> '$empty' : []. diff --git a/panacea/test/controllers/pml_controller_test.exs b/panacea/test/controllers/pml_controller_test.exs index 0afbf0c1..c7fe81e6 100644 --- a/panacea/test/controllers/pml_controller_test.exs +++ b/panacea/test/controllers/pml_controller_test.exs @@ -51,8 +51,8 @@ defmodule Panacea.PmlControllerTest do assert conn.status == 200 assert response_body(conn) |> Map.get("drugs") == [ - %{"label" => "paracetamol", "line" => 8}, - %{"label" => "cocaine", "line" => 17} + %{"label" => "cocaine", "line" => 17}, + %{"label" => "paracetamol", "line" => 8} ] end end diff --git a/panacea/test/panacea/pml/analysis_test.exs b/panacea/test/panacea/pml/analysis_test.exs new file mode 100644 index 00000000..a717cba5 --- /dev/null +++ b/panacea/test/panacea/pml/analysis_test.exs @@ -0,0 +1,41 @@ +defmodule Panacea.Pml.AnalysisTest do + use ExUnit.Case + + describe "run/1" do + test "it identifies drugs in the AST" do + pml = """ + process foo { + task bar { + action baz { + tool { "pills" } + script { "eat the pills" } + agent { "patient" } + requires { + drug { "paracetamol" } + } + provides { "a cured patient" } + } + action baz2 { + tool { "pills" } + script { "eat the pills" } + agent { "patient" } + requires { + drug { "cocaine" } + } + provides { "a cured patient" } + } + } + } + """ + + {:ok, ast} = Panacea.Pml.Parser.parse(pml) + {:ok, analysis} = Panacea.Pml.Analysis.run(ast) + + assert analysis.drugs == + [ + %{label: "cocaine", line: 17}, + %{label: "paracetamol", line: 8} + ] + end + end +end diff --git a/panacea/test/panacea/pml/parser_test.exs b/panacea/test/panacea/pml/parser_test.exs index 64d300be..fe42e880 100644 --- a/panacea/test/panacea/pml/parser_test.exs +++ b/panacea/test/panacea/pml/parser_test.exs @@ -21,7 +21,7 @@ defmodule Panacea.Pml.ParserTest do } """ - assert Parser.parse(pml) == {:ok, []} + assert {:ok, _} = Parser.parse(pml) end test "it can parse all of jnoll's sample pml" do @@ -41,41 +41,5 @@ defmodule Panacea.Pml.ParserTest do assert Parser.parse(pml) == {:error, {:syntax_error, "line 1 -- syntax error before: '{'"}} end - - test "it identifies drugs" do - pml = """ - process foo { - task bar { - action baz { - tool { "pills" } - script { "eat the pills" } - agent { "patient" } - requires { - drug { "paracetamol" } - } - provides { "a cured patient" } - } - action baz2 { - tool { "pills" } - script { "eat the pills" } - agent { "patient" } - requires { - drug { "cocaine" } - } - provides { "a cured patient" } - } - } - } - """ - - assert Parser.parse(pml) == - { - :ok, - [ - %{label: "paracetamol", line: 8}, - %{label: "cocaine", line: 17} - ] - } - end end end diff --git a/panacea/web/controllers/pml_controller.ex b/panacea/web/controllers/pml_controller.ex index 0343654f..e9220d7b 100644 --- a/panacea/web/controllers/pml_controller.ex +++ b/panacea/web/controllers/pml_controller.ex @@ -6,6 +6,7 @@ defmodule Panacea.PmlController do |> File.read() |> validate() |> parse() + |> analyse() |> to_result() |> Panacea.BaseController.respond(conn) end @@ -21,6 +22,9 @@ defmodule Panacea.PmlController do defp parse({:ok, contents}), do: Panacea.Pml.Parser.parse(contents) defp parse({:error, reason}), do: {:error, reason} - defp to_result({:ok, drugs}), do: {:ok, %{drugs: drugs}} + defp analyse({:ok, ast}), do: Panacea.Pml.Analysis.run(ast) + defp analyse({:error, reason}), do: {:error, reason} + + defp to_result({:ok, analysis}), do: {:ok, %{drugs: analysis.drugs}} defp to_result({:error, reason}), do: {:error, reason} end From 7dc87d9472da801aaafac538231610ede09b4df9 Mon Sep 17 00:00:00 2001 From: Eoin Houlihan Date: Fri, 17 Mar 2017 12:57:51 +0000 Subject: [PATCH 13/37] More AST generation Convert lists to binaries Ident constructs Number conversion String expression constructs --- panacea/src/pml_parser.yrl | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index 2882dfff..e9b4f5d4 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -97,10 +97,10 @@ primitive -> action ident optional_type '{' action_attributes '}' : construct('$1', [{name, ident('$2')}, {type, '$3'}], '$5'). optional_name -> '$empty' : nil. -optional_name -> ident : ident('$1'). +optional_name -> ident : ident('$1'). -optional_type -> '$empty' : nil. -optional_type -> manual : manual. +optional_type -> '$empty' : nil. +optional_type -> manual : manual. optional_type -> executable : executable. action_attributes -> @@ -144,8 +144,8 @@ operation -> '$empty' : nil. value -> '!' expression : {negate, [], '$2'}. value -> '(' expression ')' : {parenthesised, [], '$2'}. -value -> string : extract_string('$1'). -value -> number : extract_string('$1'). +value -> string : string_expression('$1'). +value -> number : number_expression('$1'). value -> variable : '$1'. variable -> ident accessor : function_application('$1', '$2'). @@ -153,7 +153,7 @@ variable -> prefix prefix_list accessor : function_application(['$1'|'$2'], '$3' prefix -> '(' ident ')' : {prefix, [], extract_string('$2')}. -prefix_list -> ident : [{ident, [], extract_string('$1')}]. +prefix_list -> ident : [construct('$1', [], extract_string('$1'))]. prefix_list -> prefix prefix_list : ['$1'|'$2']. prefix_list -> '$empty' : []. @@ -169,19 +169,33 @@ operator -> '>=' : '$1'. Erlang code. -extract_string({_,_,Str}) -> +extract_string({_, _, Str}) -> strip_quotes(Str). strip_quotes(Str) -> CharList = string:strip(Str, both, $"), list_to_binary(CharList). -construct({Type, Line}, Attributes, Value) -> +number_expression({_, Line, Num}) -> + ParsedNum = list_to_number(Num), + construct({number, Line}, [], ParsedNum). + +list_to_number(L) -> + try list_to_float(L) + catch + error:badarg -> list_to_integer(L) + end. + +string_expression({_, Line, Str}) -> + Stripped = strip_quotes(Str), + construct({string, Line}, [], Stripped). + +construct(T, Attributes, Value) -> Attrs = lists:filter(fun({_,X}) -> X /= nil end, Attributes), - {Type, [{line, Line}|Attrs], Value}. + {element(1, T), [{line, element(2, T)}|Attrs], Value}. ident({ident, _, Ident}) -> - Ident. + list_to_binary(Ident). function_application(Arg1, nil) -> Arg1; From e8b74af30836e25af1c05d2b7116ecc72d70c87c Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 16:02:13 +0000 Subject: [PATCH 14/37] make the value of expressions a string We never need to worry about the contents/structure of an expression. So instead of builing out ASTs for expressions, this change just store them as strings. --- panacea/lib/panacea/pml/analysis.ex | 2 +- panacea/src/pml_parser.yrl | 96 ++++++++++++----------------- 2 files changed, 41 insertions(+), 57 deletions(-) diff --git a/panacea/lib/panacea/pml/analysis.ex b/panacea/lib/panacea/pml/analysis.ex index ca0a5f60..6faf9a8d 100644 --- a/panacea/lib/panacea/pml/analysis.ex +++ b/panacea/lib/panacea/pml/analysis.ex @@ -8,7 +8,7 @@ defmodule Panacea.Pml.Analysis do end defp analyse(result, {:drug, [line: line], label}) do - %{result| drugs: [ %{label: label, line: line}| result.drugs ]} + %{result| drugs: [ %{label: to_string(label), line: line}| result.drugs ]} end defp analyse(result, {_, _,children}) when is_list(children) do Enum.reduce(children, result, fn(child, acc) -> diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index e9b4f5d4..d9c9558e 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -71,7 +71,7 @@ Rootsymbol pml. Nonassoc 200 '==' '!=' '||' '&&' '<' '>' '<=' '>=' '!' '.' ')'. pml -> - process ident primitive_block : construct('$1', [{name, ident('$2')}], '$3'). + process ident primitive_block : construct('$1', [{name, value_of('$2')}], '$3'). primitive_block -> '{' primitive_list '}' : '$2'. @@ -94,10 +94,10 @@ primitive -> % action names are required and have a different block % to other primitives primitive -> - action ident optional_type '{' action_attributes '}' : construct('$1', [{name, ident('$2')}, {type, '$3'}], '$5'). + action ident optional_type '{' action_attributes '}' : construct('$1', [{name, value_of('$2')}, {type, '$3'}], '$5'). optional_name -> '$empty' : nil. -optional_name -> ident : ident('$1'). +optional_name -> ident : value_of('$1'). optional_type -> '$empty' : nil. optional_type -> manual : manual. @@ -131,73 +131,57 @@ requires_expr -> requires_expr -> expression : '$1'. -expression -> expr logical_combination : function_application('$1', '$2'). +expression -> expr logical_combination : construct(expression, [], join_with_spaces(['$1', '$2'])). -logical_combination -> '&&' expr logical_combination : construct('$1', [], function_application('$2', '$3')). -logical_combination -> '||' expr logical_combination : construct('$1', [], function_application('$2', '$3')). -logical_combination -> '$empty' : nil. +logical_combination -> '&&' expr logical_combination : join_with_spaces(["&&", '$2', '$3']). +logical_combination -> '||' expr logical_combination : join_with_spaces(["||", '$2', '$3']). +logical_combination -> '$empty' : "". -expr -> value operation : function_application('$1', '$2'). +expr -> value operation : join_with_spaces(['$1','$2']). -operation -> operator value : construct('$1', [], '$2'). -operation -> '$empty' : nil. +operation -> operator value : join_with_spaces(['$1', '$2']). +operation -> '$empty' : "". -value -> '!' expression : {negate, [], '$2'}. -value -> '(' expression ')' : {parenthesised, [], '$2'}. -value -> string : string_expression('$1'). -value -> number : number_expression('$1'). +value -> '!' expression : "!" ++ value_of('$2'). +value -> '(' expression ')' : "(" ++ value_of('$2') ++ ")". +value -> string : extract_string('$1'). +value -> number : value_of('$1'). value -> variable : '$1'. -variable -> ident accessor : function_application('$1', '$2'). -variable -> prefix prefix_list accessor : function_application(['$1'|'$2'], '$3'). +variable -> ident accessor : value_of('$1') ++ '$2'. +variable -> prefix prefix_list accessor : join_with_spaces(['$1','$2']) ++ '$3'. -prefix -> '(' ident ')' : {prefix, [], extract_string('$2')}. +prefix -> '(' ident ')' : "(" ++ value_of('$2') ++ ")". -prefix_list -> ident : [construct('$1', [], extract_string('$1'))]. -prefix_list -> prefix prefix_list : ['$1'|'$2']. -prefix_list -> '$empty' : []. +prefix_list -> ident : value_of('$1'). +prefix_list -> prefix prefix_list : join_with_spaces(['$1', '$2']). +prefix_list -> '$empty' : "". -accessor -> '$empty' : nil. -accessor -> '.' ident : construct('$1', [], extract_string('$2')). +accessor -> '$empty' : "". +accessor -> '.' ident : "." ++ value_of('$2'). -operator -> '==' : '$1'. -operator -> '!=' : '$1'. -operator -> '<' : '$1'. -operator -> '>' : '$1'. -operator -> '<=' : '$1'. -operator -> '>=' : '$1'. +operator -> '==' : "==". +operator -> '!=' : "!=". +operator -> '<' : "<". +operator -> '>' : ">". +operator -> '<=' : "<=". +operator -> '>=' : ">=". Erlang code. -extract_string({_, _, Str}) -> - strip_quotes(Str). - -strip_quotes(Str) -> - CharList = string:strip(Str, both, $"), - list_to_binary(CharList). - -number_expression({_, Line, Num}) -> - ParsedNum = list_to_number(Num), - construct({number, Line}, [], ParsedNum). - -list_to_number(L) -> - try list_to_float(L) - catch - error:badarg -> list_to_integer(L) - end. +value_of({_, _, Value}) -> + Value. -string_expression({_, Line, Str}) -> - Stripped = strip_quotes(Str), - construct({string, Line}, [], Stripped). +extract_string({_, _, Str}) -> + string:strip(Str, both, $"). -construct(T, Attributes, Value) -> +construct({Type, Line}, Attributes, Value) -> Attrs = lists:filter(fun({_,X}) -> X /= nil end, Attributes), - {element(1, T), [{line, element(2, T)}|Attrs], Value}. - -ident({ident, _, Ident}) -> - list_to_binary(Ident). + {Type, [{line, Line}|Attrs], Value}; +construct(Type, Attributes, Value) -> + Attrs = lists:filter(fun({_,X}) -> X /= nil end, Attributes), + {Type, Attrs, Value}. -function_application(Arg1, nil) -> - Arg1; -function_application(Arg1, {Func, Attrs, Arg2}) -> - {Func, Attrs, [Arg1, Arg2]}. +join_with_spaces(Strings) -> + NonEmpty = lists:filter(fun(X) -> X /= [] end, Strings), + string:join(NonEmpty, " "). From 0398b30f5cc4efb58704d59d5ea6a5083ceab906 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 16:56:38 +0000 Subject: [PATCH 15/37] don't strip any quotes while parsing the only time we want to strip quotes is when extracting drugs - so we're better off doing it in the analysis module --- panacea/lib/panacea/pml/analysis.ex | 8 +++++++- panacea/src/pml_parser.yrl | 15 ++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/panacea/lib/panacea/pml/analysis.ex b/panacea/lib/panacea/pml/analysis.ex index 6faf9a8d..9ab32757 100644 --- a/panacea/lib/panacea/pml/analysis.ex +++ b/panacea/lib/panacea/pml/analysis.ex @@ -8,7 +8,7 @@ defmodule Panacea.Pml.Analysis do end defp analyse(result, {:drug, [line: line], label}) do - %{result| drugs: [ %{label: to_string(label), line: line}| result.drugs ]} + %{result| drugs: [ %{label: strip_quotes(label), line: line}| result.drugs ]} end defp analyse(result, {_, _,children}) when is_list(children) do Enum.reduce(children, result, fn(child, acc) -> @@ -20,6 +20,12 @@ defmodule Panacea.Pml.Analysis do end defp analyse(result, _), do: result + defp strip_quotes(char_list) do + char_list + |> :string.strip(:both, ?") + |> to_string() + end + def test do {:ok, ast} = Panacea.Pml.Parser.test run(ast) diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index d9c9558e..ab7c369f 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -117,16 +117,16 @@ action_attribute -> action_attribute -> agent '{' expression '}' : construct('$1', [], '$3'). action_attribute -> - script '{' string '}' : construct('$1', [], extract_string('$3')). + script '{' string '}' : construct('$1', [], value_of('$3')). action_attribute -> - tool '{' string '}' : construct('$1', [], extract_string('$3')). + tool '{' string '}' : construct('$1', [], value_of('$3')). action_attribute -> - input '{' string '}' : construct('$1', [], extract_string('$3')). + input '{' string '}' : construct('$1', [], value_of('$3')). action_attribute -> - output '{' string '}' : construct('$1', [], extract_string('$3')). + output '{' string '}' : construct('$1', [], value_of('$3')). requires_expr -> - drug '{' string '}' : construct('$1', [], extract_string('$3')). + drug '{' string '}' : construct('$1', [], value_of('$3')). requires_expr -> expression : '$1'. @@ -144,7 +144,7 @@ operation -> '$empty' : "". value -> '!' expression : "!" ++ value_of('$2'). value -> '(' expression ')' : "(" ++ value_of('$2') ++ ")". -value -> string : extract_string('$1'). +value -> string : value_of('$1'). value -> number : value_of('$1'). value -> variable : '$1'. @@ -172,9 +172,6 @@ Erlang code. value_of({_, _, Value}) -> Value. -extract_string({_, _, Str}) -> - string:strip(Str, both, $"). - construct({Type, Line}, Attributes, Value) -> Attrs = lists:filter(fun({_,X}) -> X /= nil end, Attributes), {Type, [{line, Line}|Attrs], Value}; From 983e9afd07f18981ce81230714f554816077b853 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 17:13:12 +0000 Subject: [PATCH 16/37] adds module for turning an AST into PML --- panacea/lib/panacea/pml/ast.ex | 38 +++++++++++++++++++++++++++ panacea/test/panacea/pml/ast_test.exs | 34 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 panacea/lib/panacea/pml/ast.ex create mode 100644 panacea/test/panacea/pml/ast_test.exs diff --git a/panacea/lib/panacea/pml/ast.ex b/panacea/lib/panacea/pml/ast.ex new file mode 100644 index 00000000..9a7bb40b --- /dev/null +++ b/panacea/lib/panacea/pml/ast.ex @@ -0,0 +1,38 @@ +defmodule Panacea.Pml.Ast do + @multi_line_constructs ~w(process action task sequence selection branch iteration)a + @single_line_constructs ~w(requires provides agent tool script input output)a + + @doc """ + Takes an AST and returns the PML it represents in an IO-List + """ + def to_pml(ast) do + do_unquote(ast, 0) + end + + defp do_unquote({type, attrs, children}, depth) when type in @multi_line_constructs do + optional_name = get_with_default(attrs, :name) + optional_type = get_with_default(attrs, :type) + + [indent(depth), to_string(type), optional_name, optional_type, " {\n", + Enum.map(children, fn(child) -> [do_unquote(child, depth + 1), "\n"] end), + indent(depth), "}" + ] + end + + defp do_unquote({type, _, value}, depth) when type in @single_line_constructs do + [indent(depth), to_string(type), " { ", do_unquote(value), " }"] + end + + defp do_unquote({:expression, _, value}), do: value + defp do_unquote({:drug, _, value}), do: ["drug { ", value, " }"] + defp do_unquote(x) when is_list(x), do: x + + defp indent(depth), do: String.duplicate(" ", 2 * depth) + + defp get_with_default(attrs, key) do + case Keyword.get(attrs, key) do + nil -> "" + x -> [" ", x] + end + end +end diff --git a/panacea/test/panacea/pml/ast_test.exs b/panacea/test/panacea/pml/ast_test.exs new file mode 100644 index 00000000..511bb76c --- /dev/null +++ b/panacea/test/panacea/pml/ast_test.exs @@ -0,0 +1,34 @@ +defmodule Panacea.Pml.AstTest do + use ExUnit.Case + + describe "to_pml/2" do + test "it returns the correct PML" do + pml = """ + process foo { + task bar { + action baz { + tool { "pills" } + script { "eat the pills" } + agent { "patient" } + requires { drug { "torasemide" } } + provides { "a cured patient" } + } + action baz2 { + tool { "pills" } + script { "eat the pills" } + agent { (intangible) (inscrutable) pml.wtf && ("foo" || 1 != 2) } + requires { drug { "trandolapril" } } + provides { "a cured patient" } + } + } + } + """ + |> String.replace_trailing("\n", "") + + {:ok, ast} = Panacea.Pml.Parser.parse(pml) + generated_pml = Panacea.Pml.Ast.to_pml(ast) |> IO.chardata_to_string() + + assert generated_pml == pml + end + end +end From 2ae7fddba3aefb2e180aca711cabb0089ad5bf1c Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 17:16:08 +0000 Subject: [PATCH 17/37] remove test functions --- panacea/lib/panacea/pml/analysis.ex | 5 ----- panacea/lib/panacea/pml/parser.ex | 9 --------- 2 files changed, 14 deletions(-) diff --git a/panacea/lib/panacea/pml/analysis.ex b/panacea/lib/panacea/pml/analysis.ex index 9ab32757..d41004a6 100644 --- a/panacea/lib/panacea/pml/analysis.ex +++ b/panacea/lib/panacea/pml/analysis.ex @@ -25,9 +25,4 @@ defmodule Panacea.Pml.Analysis do |> :string.strip(:both, ?") |> to_string() end - - def test do - {:ok, ast} = Panacea.Pml.Parser.test - run(ast) - end end diff --git a/panacea/lib/panacea/pml/parser.ex b/panacea/lib/panacea/pml/parser.ex index c6da9062..89e3ecf9 100644 --- a/panacea/lib/panacea/pml/parser.ex +++ b/panacea/lib/panacea/pml/parser.ex @@ -33,13 +33,4 @@ defmodule Panacea.Pml.Parser do Logger.info("PML Analysis success") {:ok, ast} end - - def test do - {:ok, f} = File.read("test/fixtures/ddis.pml") - f - |> to_charlist() - |> :pml_lexer.string() - |> elem(1) - |> :pml_parser.parse() - end end From 26be54e161294edd82508107256ba4b9b4e71f58 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 17:25:49 +0000 Subject: [PATCH 18/37] sync up fixture files - athloi needs this inscrutable pml --- athloi/features/fixtures/ddis.pml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/athloi/features/fixtures/ddis.pml b/athloi/features/fixtures/ddis.pml index 4e833370..b9433751 100644 --- a/athloi/features/fixtures/ddis.pml +++ b/athloi/features/fixtures/ddis.pml @@ -12,7 +12,7 @@ process foo { action baz2 { tool { "pills" } script { "eat the pills" } - agent { "patient" } + agent { (intangible)(inscrutable) pml.wtf && ("foo" || 1 != 2) } requires { drug { "trandolapril" } } From f8d9acdf8f20ceeaf263194980bafdfdcc154ddf Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 18:04:43 +0000 Subject: [PATCH 19/37] adds ast to `/api/pml` response --- panacea/README.md | 3 ++- panacea/lib/panacea/pml/analysis.ex | 11 +++++++++-- panacea/web/controllers/pml_controller.ex | 8 +++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/panacea/README.md b/panacea/README.md index adec6e5e..d2fe6a77 100644 --- a/panacea/README.md +++ b/panacea/README.md @@ -36,7 +36,8 @@ Tokens can be generated using: `Panacea.AccessToken.generate()` "label": "cocaine", "line": 2 } - ] + ], + "ast": "AST representation of the provided PML file - base64 encoded" } ``` diff --git a/panacea/lib/panacea/pml/analysis.ex b/panacea/lib/panacea/pml/analysis.ex index d41004a6..6cf25704 100644 --- a/panacea/lib/panacea/pml/analysis.ex +++ b/panacea/lib/panacea/pml/analysis.ex @@ -1,10 +1,17 @@ defmodule Panacea.Pml.Analysis do alias __MODULE__ - defstruct drugs: [] + defstruct [:drugs, :ast] + + def new(ast) do + %Analysis{ + drugs: [], + ast: ast + } + end def run(ast) do - {:ok, analyse(%Analysis{}, ast)} + {:ok, analyse(Analysis.new(ast), ast)} end defp analyse(result, {:drug, [line: line], label}) do diff --git a/panacea/web/controllers/pml_controller.ex b/panacea/web/controllers/pml_controller.ex index e9220d7b..a3ecb98f 100644 --- a/panacea/web/controllers/pml_controller.ex +++ b/panacea/web/controllers/pml_controller.ex @@ -25,6 +25,12 @@ defmodule Panacea.PmlController do defp analyse({:ok, ast}), do: Panacea.Pml.Analysis.run(ast) defp analyse({:error, reason}), do: {:error, reason} - defp to_result({:ok, analysis}), do: {:ok, %{drugs: analysis.drugs}} + defp to_result({:ok, analysis}) do + ast = + analysis.ast + |> :erlang.term_to_binary() + |> Base.encode64() + {:ok, %{drugs: analysis.drugs, ast: ast}} + end defp to_result({:error, reason}), do: {:error, reason} end From bcf67f88c5211e4d979c79a9c856e88cbfa2535e Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 18:35:41 +0000 Subject: [PATCH 20/37] adds endpoint for converting an AST to PML `/api/ast` takes an AST and returns the corresponding PML as an attachment. The ast must be a valid erlang term, that has been encoded using `:erlang.term_to_binary` and then base64 encoded for http transmission --- panacea/README.md | 9 +++ .../test/controllers/ast_controller_test.exs | 57 +++++++++++++++++++ panacea/web/controllers/ast_controller.ex | 42 ++++++++++++++ panacea/web/router.ex | 1 + 4 files changed, 109 insertions(+) create mode 100644 panacea/test/controllers/ast_controller_test.exs create mode 100644 panacea/web/controllers/ast_controller.ex diff --git a/panacea/README.md b/panacea/README.md index d2fe6a77..c0cac59d 100644 --- a/panacea/README.md +++ b/panacea/README.md @@ -129,3 +129,12 @@ All error responses take have the following format: } } ``` + +### `/api/ast` + +| | | +|-------------|------------------------------------------------------------------------- | +| Description | Convert an PML AST into a PML Document | +| Methods | `POST` | +| Parameters | An object containing the *pml ast* to be converted | +| Returns | The PML document as an attachment named `pml-tx.pml`, or an error object | diff --git a/panacea/test/controllers/ast_controller_test.exs b/panacea/test/controllers/ast_controller_test.exs new file mode 100644 index 00000000..5c98b07a --- /dev/null +++ b/panacea/test/controllers/ast_controller_test.exs @@ -0,0 +1,57 @@ +defmodule Panacea.AstControllerTest do + use Panacea.AuthorizedConnCase + + describe "AstController.to_pml/2" do + test "raises an error when no ast is provided", %{conn: conn} do + assert_raise Phoenix.ActionClauseError, fn -> + post conn, ast_path(conn, :to_pml) + end + end + + test "returns an error for non base64 encoded asts", %{conn: conn} do + conn = post conn, ast_path(conn, :to_pml), %{ast: "foo"} + + assert conn.status == 422 + + error = response_body(conn) |> Map.get("error") + assert error["title"]=~ "Encoding error" + end + + test "turns the provided ast into pml", %{conn: conn} do + pml = """ + process foo { + task bar { + action baz { + tool { "pills" } + script { "eat the pills" } + agent { "patient" } + requires { drug { "torasemide" } } + provides { "a cured patient" } + } + action baz2 { + tool { "pills" } + script { "eat the pills" } + agent { (intangible) (inscrutable) pml.wtf && ("foo" || 1 != 2) } + requires { drug { "trandolapril" } } + provides { "a cured patient" } + } + } + } + """ + |> String.replace_trailing("\n", "") + + {:ok, ast} = Panacea.Pml.Parser.parse(pml) + + encoded_ast = + ast + |> :erlang.term_to_binary() + |> Base.encode64() + + conn = post conn, ast_path(conn, :to_pml), %{ast: encoded_ast} + + assert conn.status == 200 + assert conn |> get_resp_header("content-disposition") == [~s(attachment; filename="pml-tx.pml")] + assert conn.resp_body == pml + end + end +end diff --git a/panacea/web/controllers/ast_controller.ex b/panacea/web/controllers/ast_controller.ex new file mode 100644 index 00000000..496617d3 --- /dev/null +++ b/panacea/web/controllers/ast_controller.ex @@ -0,0 +1,42 @@ +defmodule Panacea.AstController do + use Panacea.Web, :controller + + def to_pml(conn, %{"ast" => encoded_ast}) do encoded_ast + |> decode() + |> to_erlang_term() + |> to_pml() + |> respond(conn) + end + + defp decode(encoded_ast) do + case Base.decode64(encoded_ast) do + {:ok, binary} -> + {:ok, binary} + :error -> + {:error, {:encoding_error, "AST is not base64 encoded."}} + end + end + + defp to_erlang_term({:ok, binary}) do + {:ok, :erlang.binary_to_term(binary)} + end + defp to_erlang_term({:error, reason}) do + {:error, reason} + end + + defp to_pml({:ok, ast}) do + {:ok, Panacea.Pml.Ast.to_pml(ast)} + end + defp to_pml({:error, reason}) do + {:error, reason} + end + + defp respond({:ok, pml}, conn) do + conn + |> put_resp_header("content-disposition", ~s(attachment; filename="pml-tx.pml")) + |> send_resp(200, pml) + end + defp respond({:error, reason}, conn) do + Panacea.BaseController.respond({:error, reason}, conn) + end +end diff --git a/panacea/web/router.ex b/panacea/web/router.ex index c22c12de..cd3e3de0 100644 --- a/panacea/web/router.ex +++ b/panacea/web/router.ex @@ -26,5 +26,6 @@ defmodule Panacea.Router do post "/pml", PmlController, :upload post "/uris", AsclepiusController, :uris_for_labels post "/ddis", AsclepiusController, :ddis + post "/ast", AstController, :to_pml end end From fb8c52c0619be19f0abd72764a8d29d9e0d3ba63 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Fri, 17 Mar 2017 18:44:52 +0000 Subject: [PATCH 21/37] adds AST test to Pml ControllerTest --- .../test/controllers/pml_controller_test.exs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/panacea/test/controllers/pml_controller_test.exs b/panacea/test/controllers/pml_controller_test.exs index c7fe81e6..9d05c0fe 100644 --- a/panacea/test/controllers/pml_controller_test.exs +++ b/panacea/test/controllers/pml_controller_test.exs @@ -55,5 +55,29 @@ defmodule Panacea.PmlControllerTest do %{"label" => "paracetamol", "line" => 8} ] end + + test "returns the AST representation of the pml", %{conn: conn} do + filename = "no_ddis.pml" + file_path = Path.join(@fixtures_dir, filename) + upload = %Plug.Upload{path: file_path, filename: filename} + + conn = post conn, pml_path(conn, :upload), %{upload: %{file: upload}} + + assert conn.status == 200 + + {:ok, ast} = + file_path + |> File.read!() + |> Panacea.Pml.Parser.parse() + + received_ast = + conn + |> response_body() + |> Map.get("ast") + |> Base.decode64!() + |> :erlang.binary_to_term() + + assert received_ast == ast + end end end From 662a836a7c3aa893f8b0027e84f68a76760322b3 Mon Sep 17 00:00:00 2001 From: shawa Date: Fri, 17 Mar 2017 19:24:37 +0000 Subject: [PATCH 22/37] Add enrichment to DDI data, update README accordingly --- asclepius/README.md | 6 ++++-- asclepius/asclepius/enrich.py | 13 ++----------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index 21eed65e..8a8e9fc2 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -79,7 +79,7 @@ Endpoints | Description | Find all drug-drug interactions (DDI) in the DINTO ontology which involve only the *given* drugs | | Methods | `POST` | | Request Body | An object containing a list of *DINTO URIs* | -| Returns | A list of DDI objects; its label, its URI, and the identifiers of the two drugs involved | +| Returns | A list of DDI objects; its label, its URI, the identifiers of the two drugs involved, the spacing between dosages required to avoid the DDI (in days) as well as whether or not it is a harmful interaction | #### Example ##### Request Body @@ -100,7 +100,9 @@ Endpoints "drug_a": "http://purl.obolibrary.org/obo/DINTO_DB00214", "drug_b": "http://purl.obolibrary.org/obo/DINTO_DB00519", "label": "torasemide/trandolapril DDI", - "uri": "http://purl.obolibrary.org/obo/DINTO_11031" + "uri": "http://purl.obolibrary.org/obo/DINTO_11031", + "harmful": false, + "spacing": 3 } ] ``` diff --git a/asclepius/asclepius/enrich.py b/asclepius/asclepius/enrich.py index 14a81987..69c26d42 100644 --- a/asclepius/asclepius/enrich.py +++ b/asclepius/asclepius/enrich.py @@ -1,16 +1,7 @@ def enrich(ddis): - def _agonist(uri): - return bool(hash(uri) & 2) - - def _spacing(uri): - return (hash(uri) & 0xf) + 1 - def _enrich(ddi): - result = { - 'agonistic': _agonist(ddi['uri']), - 'spacing': _spacing(ddi['uri']), - } - + result = {'harmful': bool(hash(ddi['uri']) % 2), + 'spacing': abs(hash(ddi['uri']))%14 + 1} result.update(ddi) return result From b518d3b5f15dea0a82a476ac77b717ffc84e64e1 Mon Sep 17 00:00:00 2001 From: shawa Date: Fri, 17 Mar 2017 19:34:13 +0000 Subject: [PATCH 23/37] Add new fields to response object in Panacea README --- panacea/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/panacea/README.md b/panacea/README.md index adec6e5e..cc58fc70 100644 --- a/panacea/README.md +++ b/panacea/README.md @@ -108,7 +108,9 @@ Tokens can be generated using: `Panacea.AccessToken.generate()` "drug_a": "http://purl.obolibrary.org/obo/DINTO_DB00214", "drug_b": "http://purl.obolibrary.org/obo/DINTO_DB00519", "label": "torasemide/trandolapril DDI", - "uri": "http://purl.obolibrary.org/obo/DINTO_11031" + "uri": "http://purl.obolibrary.org/obo/DINTO_11031", + "harmful": false, + "spacing": 3 } ] } From 45105991497fd90edcc4985d9400a0bf6275e8dc Mon Sep 17 00:00:00 2001 From: Peter Meehan Date: Fri, 17 Mar 2017 21:18:10 +0000 Subject: [PATCH 24/37] added pml-tx download button --- .../test/controllers/ast_controller_test.exs | 1 - panacea/web/controllers/ast_controller.ex | 1 - panacea/web/static/css/app.css | 4 +++ panacea/web/static/js/app.js | 32 +++++++++++++++++-- panacea/web/static/js/request.js | 4 +++ panacea/web/static/js/view.js | 10 ++++++ panacea/web/templates/page/index.html.eex | 8 +++++ 7 files changed, 55 insertions(+), 5 deletions(-) diff --git a/panacea/test/controllers/ast_controller_test.exs b/panacea/test/controllers/ast_controller_test.exs index 5c98b07a..43c44c43 100644 --- a/panacea/test/controllers/ast_controller_test.exs +++ b/panacea/test/controllers/ast_controller_test.exs @@ -50,7 +50,6 @@ defmodule Panacea.AstControllerTest do conn = post conn, ast_path(conn, :to_pml), %{ast: encoded_ast} assert conn.status == 200 - assert conn |> get_resp_header("content-disposition") == [~s(attachment; filename="pml-tx.pml")] assert conn.resp_body == pml end end diff --git a/panacea/web/controllers/ast_controller.ex b/panacea/web/controllers/ast_controller.ex index 496617d3..883141ea 100644 --- a/panacea/web/controllers/ast_controller.ex +++ b/panacea/web/controllers/ast_controller.ex @@ -33,7 +33,6 @@ defmodule Panacea.AstController do defp respond({:ok, pml}, conn) do conn - |> put_resp_header("content-disposition", ~s(attachment; filename="pml-tx.pml")) |> send_resp(200, pml) end defp respond({:error, reason}, conn) do diff --git a/panacea/web/static/css/app.css b/panacea/web/static/css/app.css index 2a0f300f..0d192594 100644 --- a/panacea/web/static/css/app.css +++ b/panacea/web/static/css/app.css @@ -34,3 +34,7 @@ height: 128px; margin: 40px; } + +.padded-bottom { + padding-bottom: 20px; +} diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index 97c7ec8b..abec865e 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -33,12 +33,12 @@ async function handleResponse(response, handle) { } async function handleDrugsResponse(response) { - await handleResponse(response, async function ({drugs}) { + await handleResponse(response, async function ({drugs, ast}) { const labels = drugs.map(x => x.label); if (labels.length > 0) { try { - await handleUrisResponse(await Request.uris(labels)); + await handleUrisResponse(await Request.uris(labels), ast); } catch (e) { View.displayNetworkError(e); } @@ -48,7 +48,30 @@ async function handleDrugsResponse(response) { }); } -async function handleUrisResponse(response) { +const triggerDownload = (fileName, fileContents) => { + const element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileContents)); + element.setAttribute('download', fileName); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); +}; + +const prepareDownloadButton = ast => { + const pmlDownloadButton = document.getElementById('pml-download-button'); + pmlDownloadButton.onclick = async function(){ + try { + const response = await Request.pml(ast); + const pml = await response.text(); + triggerDownload('pml-tx.pml', pml); + } catch (e) { + View.displayNetworkError(e); + } + } +}; + +async function handleUrisResponse(response, ast) { await handleResponse(response, async function ({uris: {found, not_found: unidentifiedDrugs}}) { const uris = found.map(x => x.uri); const labels = found.map(x => x.label); @@ -57,6 +80,9 @@ async function handleUrisResponse(response) { return acc; }, {}); + prepareDownloadButton(ast); + View.displayDownloadButton(); + if (labels.length > 0) { View.displayDrugs(labels); } diff --git a/panacea/web/static/js/request.js b/panacea/web/static/js/request.js index 2eb706b5..78ee7a76 100644 --- a/panacea/web/static/js/request.js +++ b/panacea/web/static/js/request.js @@ -18,6 +18,10 @@ export const drugs = formData => { return post('/api/pml', formData, formHeaders); }; +export const pml = ast => { + return post('/api/ast', JSON.stringify({ast}), defaultHeaders); +}; + export const uris = labels => { return post('/api/uris', JSON.stringify({labels}), defaultHeaders); }; diff --git a/panacea/web/static/js/view.js b/panacea/web/static/js/view.js index 5100af51..d09859de 100644 --- a/panacea/web/static/js/view.js +++ b/panacea/web/static/js/view.js @@ -2,6 +2,7 @@ const drugsPanel = document.getElementById('drugs-panel'); const unidentifiedDrugsPanel = document.getElementById('unidentified-drugs-panel'); const ddisPanel = document.getElementById('ddis-panel'); const errorPanel = document.getElementById('error-panel'); +const pmlDownloadContainer = document.getElementById('pml-download-container'); const hideElement = element => { element.classList.add('hidden'); @@ -27,6 +28,14 @@ export const displayDrugs = drugs => { showElement(drugsPanel); }; +export const displayDownloadButton = () => { + showElement(pmlDownloadContainer); +}; + +const hideDownloadButton = () => { + hideElement(pmlDownloadContainer); +}; + export const displayUnidentifiedDrugs = drugs => { const unidentifiedDrugsTextElement = document.getElementById('unidentified-drugs-text'); const preamble = 'I did not recognise the following drugs:'; @@ -99,4 +108,5 @@ const fileInputElement = document.getElementById('file-input'); fileInputElement.addEventListener('change', function(e) { filenameDisplayElement.value = this.files[0].name; hideResults(); + hideDownloadButton(); }); diff --git a/panacea/web/templates/page/index.html.eex b/panacea/web/templates/page/index.html.eex index d2df6981..58821376 100644 --- a/panacea/web/templates/page/index.html.eex +++ b/panacea/web/templates/page/index.html.eex @@ -21,6 +21,14 @@
    Drugs Found
    + + + -